Diff for /loncom/homework/grades.pm between versions 1.204.2.7 and 1.628

version 1.204.2.7, 2004/09/29 05:45:34 version 1.628, 2010/04/20 23:29:22
Line 25 Line 25
 #  #
 # http://www.lon-capa.org/  # http://www.lon-capa.org/
 #  #
 # 2/9,2/13 Guy Albertelli  
 # 6/8 Gerd Kortemeyer  
 # 7/26 H.K. Ng  
 # 8/20 Gerd Kortemeyer  
 # Year 2002  
 # June-August H.K. Ng  
 # Year 2003  
 # February, March H.K. Ng  
 # July, H. K. Ng  
 #  
   
 package Apache::grades;  package Apache::grades;
 use strict;  use strict;
Line 45  use Apache::loncommon; Line 37  use Apache::loncommon;
 use Apache::lonhtmlcommon;  use Apache::lonhtmlcommon;
 use Apache::lonnavmaps;  use Apache::lonnavmaps;
 use Apache::lonhomework;  use Apache::lonhomework;
   use Apache::lonpickcode;
 use Apache::loncoursedata;  use Apache::loncoursedata;
 use Apache::lonmsg qw(:user_normal_msg);  use Apache::lonmsg();
 use Apache::Constants qw(:common);  use Apache::Constants qw(:common);
 use Apache::lonlocal;  use Apache::lonlocal;
   use Apache::lonenc;
   use Apache::lonstathelpers;
 use String::Similarity;  use String::Similarity;
   use LONCAPA;
   
   use POSIX qw(floor);
   
   
   
 my %oldessays=();  
 my %perm=();  my %perm=();
   
 # ----- These first few routines are general use routines.----  #  These variables are used to recover from ssi errors
   
   my $ssi_retries = 5;
   my $ssi_error;
   my $ssi_error_resource;
   my $ssi_error_message;
   
   
   sub ssi_with_retries {
       my ($resource, $retries, %form) = @_;
       my ($content, $response) = &Apache::loncommon::ssi_with_retries($resource, $retries, %form);
       if ($response->is_error) {
    $ssi_error          = 1;
    $ssi_error_resource = $resource;
    $ssi_error_message  = $response->code . " " . $response->message;
       }
   
       return $content;
   
   }
   #
   #  Prodcuces an ssi retry failure error message to the user:
   #
   
   sub ssi_print_error {
       my ($r) = @_;
       my $helpurl = &Apache::loncommon::top_nav_help('Helpdesk');
       $r->print('
   <br />
   <h2>'.&mt('An unrecoverable network error occurred:').'</h2>
   <p>
   '.&mt('Unable to retrieve a resource from a server:').'<br />
   '.&mt('Resource:').' '.$ssi_error_resource.'<br />
   '.&mt('Error:').' '.$ssi_error_message.'
   </p>
   <p>'.
   &mt('It is recommended that you try again later, as this error may mean the server was just temporarily unavailable, or is down for maintenance.').'<br />'.
   &mt('If the error persists, please contact the [_1] for assistance.',$helpurl).
   '</p>');
       return;
   }
   
 #  #
 # --- Retrieve the parts from the metadata file.---  # --- Retrieve the parts from the metadata file.---
   # Returns an array of everything that the resources stores away
   #
   
 sub getpartlist {  sub getpartlist {
     my ($url,$symb) = @_;      my ($symb,$errorref) = @_;
     my $partorder = &Apache::lonnet::metadata($url, 'partorder');  
     my @parts;      my $navmap   = Apache::lonnavmaps::navmap->new();
     if ($partorder) {      unless (ref($navmap)) {
  for my $part (split (/,/,$partorder)) {          if (ref($errorref)) { 
     if (!&Apache::loncommon::check_if_partid_hidden($part,$symb)) {              $$errorref = 'navmap';
  push(@parts, $part);              return;
     }          }
  }      
     } else {  
  my $metadata = &Apache::lonnet::metadata($url, 'packages');  
  foreach (split(/\,/,$metadata)) {  
     if ($_ =~ /^part_(.*)$/) {  
  if (!&Apache::loncommon::check_if_partid_hidden($1,$symb)) {  
     push(@parts, $1);  
  }  
     }  
  }  
     }      }
       my $res      = $navmap->getBySymb($symb);
       my $partlist = $res->parts();
       my $url      = $res->src();
       my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys'));
   
     my @stores;      my @stores;
     foreach my $part (@parts) {      foreach my $part (@{ $partlist }) {
  my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys'));  
  foreach my $key (@metakeys) {   foreach my $key (@metakeys) {
     if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }      if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }
  }   }
Line 87  sub getpartlist { Line 124  sub getpartlist {
     return @stores;      return @stores;
 }  }
   
 # --- Get the symbolic name of a problem and the url  
 sub get_symb_and_url {  
     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 ();  
  }  
     }  
     return ($symb,$url);  
 }  
   
 # --- Retrieve the fullname for a user. Return lastname, first middle ---  
 # --- Generation is attached next to the lastname if it exists. ---  
 sub get_fullname {  
     my ($uname,$udom) = @_;  
     my %name=&Apache::lonnet::get('environment', ['lastname','generation',  
   'firstname','middlename'],  
                                   $udom,$uname);  
     my $fullname;  
     my ($tmp) = keys(%name);  
     if ($tmp !~ /^(con_lost|error|no_such_host)/i) {  
         $fullname = &Apache::loncoursedata::ProcessFullName  
             (@name{qw/lastname generation firstname middlename/});  
     } else {  
         &Apache::lonnet::logthis('grades.pm: no name data for '.$uname.  
                                  '@'.$udom.':'.$tmp);  
     }  
     return $fullname;  
 }  
   
 #--- 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><font color="#999999">(Username)</font>&nbsp;';   return '<b>&nbsp;'.&mt('Fullname').'&nbsp;</b><span class="LC_internal_info">('.&mt('Username').')</span>';
     } else {      } else {
  return '&nbsp;'.$fullname.'<font color="#999999">&nbsp;('.$uname.   return '&nbsp;'.$fullname.'<span class="LC_internal_info">&nbsp;('.$uname.
     ($ENV{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</font>';      ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</span>';
     }      }
 }  }
   
 #--- 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 ($url,$symb) = shift;      my ($symb,$response_error) = @_;
     $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url))) if ($symb eq '');  
     my $allkeys = &Apache::lonnet::metadata($url,'keys');      my $navmap = Apache::lonnavmaps::navmap->new();
     my %vPart;      unless (ref($navmap)) {
     foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) {          if (ref($response_error)) {
  $vPart{$partid}=1;              $$response_error = 1;
     }          }
     my %seen = ();          return;
     my (@partlist,%handgrade,%responseType);  
     foreach (split(/,/,&Apache::lonnet::metadata($url,'packages'))) {  
  if (/^\w+response_.*/) {  
     my ($responsetype,$part) = split(/_/,$_,2);  
     my ($partid,$respid) = split(/_/,$part);  
     if (&Apache::loncommon::check_if_partid_hidden($partid,$symb)) {  
  next;  
     }  
     if (%vPart && !exists($vPart{$partid})) {  
  next;  
     }  
     $responsetype =~ s/response$//; # make it compatible w/ navmaps - should move to that!!  
     my ($value) = &Apache::lonnet::EXT('resource.'.$part.'.handgrade',$symb);  
     $handgrade{$part} = ($value eq 'yes' ? 'yes' : 'no');   
     if (!exists($responseType{$partid})) { $responseType{$partid}={}; }  
     $responseType{$partid}->{$respid}=$responsetype;  
     next if ($seen{$partid} > 0);  
     $seen{$partid}++;  
     push @partlist,$partid;  
  }  
     }      }
     return \@partlist,\%handgrade,\%responseType;      my $res = $navmap->getBySymb($symb);
       unless (ref($res)) {
           $$response_error = 1;
           return;
       }
       my $partlist = $res->parts();
       my %vPart = 
    map { $_ => 1 } (&Apache::loncommon::get_env_multiple('form.vPart'));
       my (%response_types,%handgrade);
       foreach my $part (@{ $partlist }) {
    next if (%vPart && !exists($vPart{$part}));
   
    my @types = $res->responseType($part);
    my @ids = $res->responseIds($part);
    for (my $i=0; $i < scalar(@ids); $i++) {
       $response_types{$part}{$ids[$i]} = $types[$i];
       $handgrade{$part.'_'.$ids[$i]} = 
    &Apache::lonnet::EXT('resource.'.$part.'_'.$ids[$i].
        '.handgrade',$symb);
    }
       }
       return ($partlist,\%handgrade,\%response_types);
   }
   
   sub flatten_responseType {
       my ($responseType) = @_;
       my @part_response_id =
    map { 
       my $part = $_;
       map {
    [$part,$_]
    } sort(keys(%{ $responseType->{$part} }));
    } sort(keys(%$responseType));
       return @part_response_id;
 }  }
   
 sub get_display_part {  sub get_display_part {
     my ($partID,$url,$symb)=@_;      my ($partID,$symb)=@_;
     if (!defined($symb) || $symb eq '') {  
  $symb=$ENV{'form.symb'};  
  if ($symb eq '') { $symb=&Apache::lonnet::symbread($url) }  
     }  
     my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);      my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);
     &Apache::lonnet::logthis("\nsymb $symb\n url  $url\npartID $partID\ndisplay $display \n");  
     if (defined($display) and $display ne '') {      if (defined($display) and $display ne '') {
  $display.= " (<font color=\"#999900\">id $partID</font>)";          $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 reset_caches {
 sub showResourceInfo {      &reset_analyze_cache();
     my ($url,$probTitle,$checkboxes) = @_;      &reset_perm();
     my $col=3;  }
     if ($checkboxes) { $col=4; }  
     my $result ='<table border="0">'.  {
  '<tr><td colspan="'.$col.'"><font size="+1"><b>'.&mt('Current Resource').': </b>'.      my %analyze_cache;
  $probTitle.'</font></td></tr>'."\n";      my %analyze_cache_formkeys;
     my ($partlist,$handgrade,$responseType) = &response_type($url);  
     my %resptype = ();      sub reset_analyze_cache {
     my $hdgrade='no';   undef(%analyze_cache);
     my %partsseen;          undef(%analyze_cache_formkeys);
     for my $part_resID (sort keys(%$handgrade)) {      }
  my $handgrade=$$handgrade{$part_resID};  
  my ($partID,$resID) = split(/_/,$part_resID);      sub get_analyze {
  my $responsetype = $responseType->{$partID}->{$resID};   my ($symb,$uname,$udom,$no_increment,$add_to_hash)=@_;
  $hdgrade = $handgrade if ($handgrade eq 'yes');   my $key = "$symb\0$uname\0$udom";
  $result.='<tr>';   if (exists($analyze_cache{$key})) {
  if ($checkboxes) {              my $getupdate = 0;
     if (exists($partsseen{$partID})) {              if (ref($add_to_hash) eq 'HASH') {
  $result.="<td>&nbsp;</td>";                  foreach my $item (keys(%{$add_to_hash})) {
     } else {                      if (ref($analyze_cache_formkeys{$key}) eq 'HASH') {
  $result.="<td><input type='checkbox' name='vPart' value='$partID' checked='on' /></td>";                          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);
    $url=&Apache::lonnet::clutter($url);
           my %form = ('grade_target'      => 'analyze',
                       'grade_domain'      => $udom,
                       'grade_symb'        => $symb,
                       'grade_courseid'    =>  $env{'request.course.id'},
                       'grade_username'    => $uname,
                       'grade_noincrement' => $no_increment);
           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);
    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;
       }
   
       sub get_order {
    my ($partid,$respid,$symb,$uname,$udom,$no_increment)=@_;
    my $analyze = &get_analyze($symb,$uname,$udom,$no_increment);
    return $analyze->{"$partid.$respid.shown"};
       }
   
       sub get_radiobutton_correct_foil {
    my ($partid,$respid,$symb,$uname,$udom)=@_;
    my $analyze = &get_analyze($symb,$uname,$udom);
           my $foils = &get_order($partid,$respid,$symb,$uname,$udom);
           if (ref($foils) eq 'ARRAY') {
       foreach my $foil (@{$foils}) {
           if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {
       return $foil;
           }
     }      }
     $partsseen{$partID}=1;  
  }   }
  my $display_part=&get_display_part($partID,$url);  
  $result.='<td><b>Part: </b>'.$display_part.' <font color="#999999">'.  
     $resID.'</font></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 scantron_partids_tograde {
           my ($resource,$cid,$uname,$udom,$check_for_randomlist) = @_;
           my (%analysis,@parts);
           if (ref($resource)) {
               my $symb = $resource->symb();
               my $add_to_form;
               if ($check_for_randomlist) {
                   $add_to_form = { 'check_parts_withrandomlist' => 1,};
               }
               my $analyze = &get_analyze($symb,$uname,$udom,undef,$add_to_form);
               if (ref($analyze) eq 'HASH') {
                   %analysis = %{$analyze};
               }
               if (ref($analysis{'parts'}) eq 'ARRAY') {
                   foreach my $part (@{$analysis{'parts'}}) {
                       my ($id,$respid) = split(/\./,$part);
                       if (!&Apache::loncommon::check_if_partid_hidden($id,$symb,$udom,$uname)) {
                           push(@parts,$part);
                       }
                   }
               }
           }
           return (\%analysis,\@parts);
       }
   
 sub get_order {  
     my ($partid,$respid,$symb,$uname,$udom)=@_;  
     my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);  
     $url=&Apache::lonnet::clutter($url);  
     my $subresult=&Apache::lonnet::ssi($url,  
        ('grade_target' => 'analyze'),  
        ('grade_domain' => $udom),  
        ('grade_symb' => $symb),  
        ('grade_courseid' =>   
         $ENV{'request.course.id'}),  
        ('grade_username' => $uname));  
     (undef,$subresult)=split(/_HASH_REF__/,$subresult,2);  
     my %analyze=&Apache::lonnet::str2hash($subresult);  
     return ($analyze{"$partid.$respid.shown"});  
 }  }
   
 #--- Clean response type for display  #--- Clean response type for display
 #--- Currently filters option/rank/radiobutton/match/essay response types only.  #--- Currently filters option/rank/radiobutton/match/essay/Task
   #        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,
     my $grayFont = '<font color="#999999">';   $uname,$udom) = @_;
       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 %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});   my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
Line 250  sub cleanRecord { Line 318  sub cleanRecord {
     } else {      } else {
  $toprow.='<td><i>'.$answer{$foil}.'&nbsp;</i></td>';   $toprow.='<td><i>'.$answer{$foil}.'&nbsp;</i></td>';
     }      }
     $bottomrow.='<td>'.$grayFont.$foil.'</font>&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</font></td>'.      '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
     $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';      $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';
     } elsif ($response eq 'match') {      } elsif ($response eq 'match') {
  my %answer=&Apache::lonnet::str2hash($answer);   my %answer=&Apache::lonnet::str2hash($answer);
Line 265  sub cleanRecord { Line 333  sub cleanRecord {
     my $item=shift(@items);      my $item=shift(@items);
     if ($grading{$foil} == 1) {      if ($grading{$foil} == 1) {
  $toprow.='<td><b>'.$item.'&nbsp;</b></td>';   $toprow.='<td><b>'.$item.'&nbsp;</b></td>';
  $middlerow.='<td><b>'.$grayFont.$answer{$foil}.'&nbsp;</font></b></td>';   $middlerow.='<td><b>'.$grayFont.$answer{$foil}.'&nbsp;</span></b></td>';
     } else {      } else {
  $toprow.='<td><i>'.$item.'&nbsp;</i></td>';   $toprow.='<td><i>'.$item.'&nbsp;</i></td>';
  $middlerow.='<td><i>'.$grayFont.$answer{$foil}.'&nbsp;</font></i></td>';   $middlerow.='<td><i>'.$grayFont.$answer{$foil}.'&nbsp;</span></i></td>';
     }      }
     $bottomrow.='<td>'.$grayFont.$foil.'</font>&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</font></td>'.      '<tr valign="top"><td>'.$grayFont.&mt('Item ID').'</span></td>'.
     $middlerow.'</tr>'.      $middlerow.'</tr>'.
     '<tr valign="top"><td>'.$grayFont.'Option ID</font></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 ($toprow,$bottomrow);   my ($toprow,$bottomrow);
  my $correct=($order->[0])+1;   my $correct = 
  for (my $i=1;$i<=$#$order;$i++) {      &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom);
     my $foil=$order->[$i];   foreach my $foil (@$order) {
     if (exists($answer{$foil})) {      if (exists($answer{$foil})) {
  if ($i == $correct) {   if ($foil eq $correct) {
     $toprow.='<td><b>true</b></td>';      $toprow.='<td><b>'.&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.'</font>&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</font></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',
   $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'});
   
     my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'};      my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
     $ENV{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';      $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
     $ENV{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';      $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
     $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.'.$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 =~ s-\n-<br />-g;
  return '<br /><br /><blockquote><tt>'.&keywords_highlight($answer).'</tt></blockquote>';   return '<br /><br /><blockquote><tt>'.&keywords_highlight($answer).'</tt></blockquote>';
       } elsif ( $response eq 'organic') {
    my $result='Smile representation: "<tt>'.$answer.'</tt>"';
    my $jme=$record->{$version."resource.$partid.$respid.molecule"};
    $result.=&Apache::chemresponse::jme_img($jme,$answer,400);
    return $result;
       } elsif ( $response eq 'Task') {
    if ( $answer eq 'SUBMITTED') {
       my $files = $record->{$version."resource.$respid.$partid.bridgetask.portfiles"};
       my $result = &Apache::bridgetask::file_list($files,$uname,$udom);
       return $result;
    } elsif ( grep(/^\Q$version\E.*?\.instance$/, keys(%{$record})) ) {
       my @matches = grep(/^\Q$version\E.*?\.instance$/,
          keys(%{$record}));
       return join('<br />',($version,@matches));
          
          
    } else {
       my $result =
    '<p>'
    .&mt('Overall result: [_1]',
        $record->{$version."resource.$respid.$partid.status"})
    .'</p>';
       
       $result .= '<ul>';
       my @grade = grep(/^\Q${version}resource.$respid.$partid.\E[^.]*[.]status$/,
        keys(%{$record}));
       foreach my $grade (sort(@grade)) {
    my ($dim) = ($grade =~/[.]([^.]+)[.]status$/);
    $result.= '<li>'.&mt("Dimension: [_1], status [_2] ",
        $dim, $record->{$grade}).
     '</li>';
       }
       $result.='</ul>';
       return $result;
    }
       } elsif ( $response =~ m/(?:numerical|formula)/) {
    $answer = 
       &Apache::loncommon::format_previous_attempt_value('submission',
         $answer);
     }      }
     return $answer;      return $answer;
 }  }
Line 321  sub cleanRecord { Line 428  sub cleanRecord {
 #-- 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 350  sub commonJSfunctions { Line 456  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) = @_;      my ($getsec,$filterlist,$getgroup) = @_;
     $getsec = $getsec eq '' ? 'all' : $getsec;      my @getsec;
     my $classlist=&Apache::loncoursedata::get_classlist();      my @getgroup;
       my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
       if (!ref($getsec)) {
    if ($getsec ne '' && $getsec ne 'all') {
       @getsec=($getsec);
    }
       } else {
    @getsec=@{$getsec};
       }
       if (grep(/^all$/,@getsec)) { undef(@getsec); }
       if (!ref($getgroup)) {
    if ($getgroup ne '' && $getgroup ne 'all') {
       @getgroup=($getgroup);
    }
       } else {
    @getgroup=@{$getgroup};
       }
       if (grep(/^all$/,@getgroup)) { undef(@getgroup); }
   
       my ($classlist,$keylist)=&Apache::loncoursedata::get_classlist();
     # Bail out if we were unable to get the classlist      # Bail out if we were unable to get the classlist
     return if (! defined($classlist));      return if (! defined($classlist));
       &Apache::loncoursedata::get_group_memberships($classlist,$keylist);
     #      #
     my %sections;      my %sections;
     my %fullnames;      my %fullnames;
     foreach (keys(%$classlist)) {      foreach my $student (keys(%$classlist)) {
         # the following undefs are for 'domain', and 'username' respectively.          my $end      = 
  my (undef,undef,$end,$start,$id,$section,$fullname,$status)=              $classlist->{$student}->[&Apache::loncoursedata::CL_END()];
             @{$classlist->{$_}};          my $start    = 
               $classlist->{$student}->[&Apache::loncoursedata::CL_START()];
           my $id       = 
               $classlist->{$student}->[&Apache::loncoursedata::CL_ID()];
           my $section  = 
               $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
           my $fullname = 
               $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()];
           my $status   = 
               $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()];
           my $group   = 
               $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
  # filter students according to status selected   # filter students according to status selected
  if ($filterlist && $ENV{'form.Status'} ne 'Any') {   if ($filterlist && (!($stu_status =~ /Any/))) {
     if ($ENV{'form.Status'} ne $status) {      if (!($stu_status =~ $status)) {
  delete ($classlist->{$_});   delete($classlist->{$student});
  next;   next;
     }      }
  }   }
  $section = ($section ne '' ? $section : 'no');   # filter students according to groups selected
    my @stu_groups = split(/,/,$group);
    if (@getgroup) {
       my $exclude = 1;
       foreach my $grp (@getgroup) {
           foreach my $stu_group (@stu_groups) {
               if ($stu_group eq $grp) {
                   $exclude = 0;
                  } 
           }
              if (($grp eq 'none') && !$group) {
                  $exclude = 0;
           }
       }
       if ($exclude) {
           delete($classlist->{$student});
       }
    }
    $section = ($section ne '' ? $section : 'none');
  if (&canview($section)) {   if (&canview($section)) {
     if ($getsec eq 'all' || $getsec eq $section) {      if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {
  $sections{$section}++;   $sections{$section}++;
  $fullnames{$_}=$fullname;   if ($classlist->{$student}) {
       $fullnames{$student}=$fullname;
    }
     } else {      } else {
  delete($classlist->{$_});   delete($classlist->{$student});
     }      }
  } else {   } else {
     delete($classlist->{$_});      delete($classlist->{$student});
  }   }
     }      }
     my %seen = ();      my %seen = ();
Line 435  sub canview { Line 591  sub canview {
   
 #--- Retrieve the grade status of a student for all the parts  #--- Retrieve the grade status of a student for all the parts
 sub student_gradeStatus {  sub student_gradeStatus {
     my ($url,$symb,$udom,$uname,$partlist) = @_;      my ($symb,$udom,$uname,$partlist) = @_;
     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 %partstatus = ();      my %partstatus = ();
     foreach (@$partlist) {      foreach (@$partlist) {
  my ($status,undef)   = split(/_/,$record{"resource.$_.solved"},2);   my ($status,undef)   = split(/_/,$record{"resource.$_.solved"},2);
Line 452  sub student_gradeStatus { Line 608  sub student_gradeStatus {
 # Use by verifyscript and viewgrades  # Use by verifyscript and viewgrades
 # Shows a student's view of problem and submission  # Shows a student's view of problem and submission
 sub jscriptNform {  sub jscriptNform {
     my ($url,$symb) = @_;      my ($symb) = @_;
     my $jscript='<script type="text/javascript" language="javascript">'."\n".      my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
       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="'.$symb.'" />'."\n".   '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
  '<input type="hidden" name="url"     value="'.$url.'" />'."\n".   '<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\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="'.$ENV{'form.Status'}.'" />'."\n".  
  '<input type="hidden" name="command" value="submission" />'."\n".   '<input type="hidden" name="command" value="submission" />'."\n".
  '<input type="hidden" name="student" value="" />'."\n".   '<input type="hidden" name="student" value="" />'."\n".
  '<input type="hidden" name="userdom" value="" />'."\n".   '<input type="hidden" name="userdom" value="" />'."\n".
Line 473  sub jscriptNform { Line 627  sub jscriptNform {
     return $jscript;      return $jscript;
 }  }
   
   
   
   # Given the score (as a number [0-1] and the weight) what is the final
   # point value? This function will round to the nearest tenth, third,
   # or quarter if one of those is within the tolerance of .00001.
   sub compute_points {
       my ($score, $weight) = @_;
       
       my $tolerance = .00001;
       my $points = $score * $weight;
   
       # Check for nearness to 1/x.
       my $check_for_nearness = sub {
           my ($factor) = @_;
           my $num = ($points * $factor) + $tolerance;
           my $floored_num = floor($num);
           if ($num - $floored_num < 2 * $tolerance * $factor) {
               return $floored_num / $factor;
           }
           return $points;
       };
   
       $points = $check_for_nearness->(10);
       $points = $check_for_nearness->(3);
       $points = $check_for_nearness->(4);
       
       return $points;
   }
   
 #------------------ End of general use routines --------------------  #------------------ End of general use routines --------------------
   
 #  #
Line 480  sub jscriptNform { Line 663  sub jscriptNform {
 #  #
   
 sub most_similar {  sub most_similar {
     my ($uname,$udom,$uessay)=@_;      my ($uname,$udom,$uessay,$old_essays)=@_;
   
 # ignore spaces and punctuation  # ignore spaces and punctuation
   
     $uessay=~s/\W+/ /gs;      $uessay=~s/\W+/ /gs;
   
   # ignore empty submissions (occuring when only files are sent)
   
       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;
     my $sname='';      my $sname='';
Line 493  sub most_similar { Line 680  sub most_similar {
     my $scrsid='';      my $scrsid='';
     my $sessay='';      my $sessay='';
 # go through all essays ...  # go through all essays ...
     foreach my $tkey (keys %oldessays) {      foreach my $tkey (keys(%$old_essays)) {
  my ($tname,$tdom,$tcrsid)=split(/\./,$tkey);   my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey));
 # ... except the same student  # ... except the same student
         if (($tname ne $uname) || ($tdom ne $udom)) {          next if (($tname eq $uname) && ($tdom eq $udom));
     my $tessay=$oldessays{$tkey};   my $tessay=$old_essays->{$tkey};
             $tessay=~s/\W+/ /gs;   $tessay=~s/\W+/ /gs;
 # String similarity gives up if not even limit  # String similarity gives up if not even limit
             my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit);   my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit);
 # Found one  # Found one
             if ($tsimilar>$limit) {   if ($tsimilar>$limit) {
  $limit=$tsimilar;      $limit=$tsimilar;
                 $sname=$tname;      $sname=$tname;
                 $sdom=$tdom;      $sdom=$tdom;
                 $scrsid=$tcrsid;      $scrsid=$tcrsid;
                 $sessay=$oldessays{$tkey};      $sessay=$old_essays->{$tkey};
             }   }
         }   
     }      }
     if ($limit>0.6) {      if ($limit>0.6) {
        return ($sname,$sdom,$scrsid,$sessay,$limit);         return ($sname,$sdom,$scrsid,$sessay,$limit);
Line 522  sub most_similar { Line 708  sub most_similar {
   
 #------------------------------------ Receipt Verification Routines  #------------------------------------ Receipt Verification Routines
 #  #
   
   sub initialverifyreceipt {
      my ($request,$symb) = @_;
      &commonJSfunctions($request);
      return '<form name="gradingMenu"><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 $url      = $ENV{'form.url'};  
     my $symb     = $ENV{'form.symb'};  
     unless ($symb) {  
  $symb    = &Apache::lonnet::symbread($url);  
     }  
   
     my $title.='<h3><font color="#339933">Verifying Submission Receipt '.      my $title.=
  $receipt.'</h3></font>'."\n".   '<h3><span class="LC_info">'.
  '<font size=+1><b>Resource: </b>'.$ENV{'form.probTitle'}.'</font><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');
           
     my $receiptparts=0;      my $receiptparts=0;
     if ($ENV{"course.$courseid.receiptalg"} eq 'receipt2') { $receiptparts=1; }      if ($env{"course.$courseid.receiptalg"} eq 'receipt2' ||
    $env{"course.$courseid.receiptalg"} eq 'receipt3') { $receiptparts=1; }
     my $parts=['0'];      my $parts=['0'];
     if ($receiptparts) { ($parts)=&response_type($url,$symb); }      if ($receiptparts) {
     foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {          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 
        {
    if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
        return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
    }
    return $a cmp $b;
        } (keys(%$fullname))) {
  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>'.
     '<td>&nbsp;'.$udom.'&nbsp;</td>';      '<td>&nbsp;'.$udom.'&nbsp;</td>';
  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($url,$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,$url);      return $string;
 }  }
   
 #--- This is called by a number of programs.  #--- This is called by a number of programs.
Line 590  sub verifyreceipt { Line 810  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,$url) = &get_symb_and_url($request);  
     my $cdom      = $ENV{"course.$ENV{'request.course.id'}.domain"};  
     my $cnum      = $ENV{"course.$ENV{'request.course.id'}.num"};  
     my $getsec    = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'};  
     my $submitonly= $ENV{'form.submitonly'} eq '' ? 'all' : $ENV{'form.submitonly'};  
   
     my $viewgrade = $ENV{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View';  
     $ENV{'form.probTitle'} = $ENV{'form.probTitle'} eq '' ?   
  &Apache::lonnet::gettitle($symb) : $ENV{'form.probTitle'};  
   
     my $result='<h3><font color="#339933">&nbsp;'.$viewgrade.  
  ' Submissions for a Student or a Group of Students</font></h3>';  
   
     my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($url,$ENV{'form.probTitle'},($ENV{'form.showgrading'} eq 'yes'));      my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
       my $cnum      = $env{"course.$env{'request.course.id'}.num"};
     $request->print(<<LISTJAVASCRIPT);      my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
 <script type="text/javascript" language="javascript">      my $getgroup  = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
       unless ($submitonly) {
          $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
       }
   
       my $result='<h3><span class="LC_info">&nbsp;'
    .&mt("View/Grade/Regrade Submissions for a Student or a Group of Students")
    .'</span></h3>';
       my $res_error;
       my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
   
       my %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.',
        );
       $request->print(&Apache::lonhtmlcommon::scripttag(<<LISTJAVASCRIPT));
     function checkSelect(checkBox) {      function checkSelect(checkBox) {
  var ctr=0;   var ctr=0;
  var sense="";   var sense="";
Line 618  sub listStudents { Line 840  sub listStudents {
     ctr++;      ctr++;
  }   }
     }      }
     sense = "a student or group of students";      sense = '$lt{'multiple'}';
  } else {   } else {
     if (checkBox.checked) {      if (checkBox.checked) {
  ctr = 1;   ctr = 1;
     }      }
     sense = "the student";      sense = '$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 637  sub listStudents { Line 859  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' : '';  
     my $checklastsub = $checkhdgrade eq '' ? '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><input type="radio" name="vProb" value="no" checked="on" /> no '."\n".  
  '<input type="radio" name="vProb" value="yes" /> one student '."\n".      $gradeTable .= &Apache::lonhtmlcommon::start_pick_box();
  '<input type="radio" name="vProb" value="all" /> all students <br />'."\n".      $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Problem Text'))
  '&nbsp;<b>View Answer: </b><input type="radio" name="vAns" value="no"  /> no '."\n".                    .'<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n"
  '<input type="radio" name="vAns" value="yes" /> one student '."\n".                    .'<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n"
  '<input type="radio" name="vAns" value="all" checked="on" /> all students <br />'."\n".                    .'<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label><br />'."\n"
  '&nbsp;<b>Submissions: </b>'."\n";                    .&Apache::lonhtmlcommon::row_closure();
     if ($ENV{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {      $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Answer'))
  $gradeTable.='<input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> essay part only'."\n";                    .'<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"
     my $saveStatus = $ENV{'form.Status'} eq '' ? 'Active' : $ENV{'form.Status'};                    .&Apache::lonhtmlcommon::row_closure();
     $ENV{'form.Status'} = $saveStatus;  
       my $submission_options;
     $gradeTable.='<input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> last submission only'."\n".      my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
  '<input type="radio" name="lastSub" value="last" /> last submission & parts info'."\n".      my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status;
  '<input type="radio" name="lastSub" value="datesub" /> by dates and submissions'."\n".      $env{'form.Status'} = $saveStatus;
  '<input type="radio" name="lastSub" value="all" /> all details'."\n".      $submission_options.=
  '<input type="hidden" name="section"     value="'.$getsec.'" />'."\n".          '<span class="LC_nobreak">'.
           '<label><input type="radio" name="lastSub" value="lastonly" /> '.
           &mt('last submission only').' </label></span>'."\n".
           '<span class="LC_nobreak">'.
           '<label><input type="radio" name="lastSub" value="last" /> '.
           &mt('last submission &amp; parts info').' </label></span>'."\n".
           '<span class="LC_nobreak">'.
           '<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> '.
           &mt('by dates and submissions').'</label></span>'."\n".
           '<span class="LC_nobreak">'.
           '<label><input type="radio" name="lastSub" value="all" /> '.
           &mt('all details').'</label></span>';
       $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Submissions'))
                     .$submission_options
                     .&Apache::lonhtmlcommon::row_closure();
   
       $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Grading Increments'))
                     .'<select name="increment">'
                     .'<option value="1">'.&mt('Whole Points').'</option>'
                     .'<option value=".5">'.&mt('Half Points').'</option>'
                     .'<option value=".25">'.&mt('Quarter Points').'</option>'
                     .'<option value=".1">'.&mt('Tenths of a Point').'</option>'
                     .'</select>'
                     .&Apache::lonhtmlcommon::row_closure();
   
       $gradeTable .= 
           &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="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\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="url"  value="'.$url.'" />'."\n".  
  '<input type="hidden" name="symb" value="'.$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="'.$ENV{'form.Status'}.'" />'."\n";   $gradeTable .= '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n";
     } else {      } else {
  $gradeTable.='<b>Student Status:</b> '.          $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Student Status'))
     &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);').'<br />';                        .&Apache::lonhtmlcommon::StatusOptions(
     }                             $saveStatus,undef,1,'javascript:reLoadList(this.form);')
                         .&Apache::lonhtmlcommon::row_closure();
       }
   
       $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Check For Plagiarism'))
                     .'<input type="checkbox" name="checkPlag" checked="checked" />'
                     .&Apache::lonhtmlcommon::row_closure(1)
                     .&Apache::lonhtmlcommon::end_pick_box();
   
       $gradeTable .= '<p>'
                     .&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.")."\n"
                     .'<input type="hidden" name="command" value="processGroup" />'
                     .'</p>';
   
     $gradeTable.='To '.lc($viewgrade).' a submission or a group of submissions, click on the check box(es) '.  # checkall buttons
  'next to the student\'s name(s). Then click on the Next button.<br />'."\n".      $gradeTable.=&check_script('gradesub', 'stuinfo');
  '<input type="hidden" name="command" value="processGroup" />'."\n";  
     $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->" />'."\n";          'value="'.&mt('Next').' &rarr;" /> <br />'."\n";
     $gradeTable.='<input type="checkbox" name="checkPlag" checked="on">Check For Plagiarism</input>';      $gradeTable.=&check_buttons();
     my (undef, undef, $fullname) = &getclasslist($getsec,'1');        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').'</td>';      '<th>'.&nameUserString('header').'&nbsp;'.&mt('Section/Group').'</th>';
  if ($ENV{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {   if (($submitonly ne 'queued') && ($submitonly ne 'all')) {
     foreach (sort(@$partlist)) {      foreach my $part (sort(@$partlist)) {
  my $display_part=&get_display_part((split(/_/))[0],$url,$symb);   my $display_part=
  $gradeTable.='<td><b>&nbsp;Part: '.$display_part.      &get_display_part((split(/_/,$part))[0],$symb);
     ' Status&nbsp;</b></td>';   $gradeTable.=
       '<th>'.&mt('Part: [_1] Status',$display_part).'</th>';
     }      }
    } elsif ($submitonly eq 'queued') {
       $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 {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {      foreach my $student (sort 
    {
        if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
    return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
        }
        return $a cmp $b;
    }
    (keys(%$fullname))) {
  my ($uname,$udom) = split(/:/,$student);   my ($uname,$udom) = split(/:/,$student);
   
  my %status = ();   my %status = ();
  if ($ENV{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {  
     (%status) =&student_gradeStatus($url,$symb,$udom,$uname,$partlist);   if ($submitonly eq 'queued') {
       my %queue_status = 
    &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
    $udom,$uname);
       next if (!defined($queue_status{'gradingqueue'}));
       $status{'gradingqueue'} = $queue_status{'gradingqueue'};
    }
   
    if (($submitonly ne 'queued') && ($submitonly ne 'all')) {
       (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist);
     my $submitted = 0;      my $submitted = 0;
     my $graded = 0;      my $graded = 0;
       my $incorrect = 0;
     foreach (keys(%status)) {      foreach (keys(%status)) {
  $submitted = 1 if ($status{$_} ne 'nothing');   $submitted = 1 if ($status{$_} ne 'nothing');
  $graded = 1 if ($status{$_} !~ /^correct/);   $graded = 1 if ($status{$_} =~ /^ungraded/);
    $incorrect = 1 if ($status{$_} =~ /^incorrect/);
   
  my ($foo,$partid,$foo1) = split(/\./,$_);   my ($foo,$partid,$foo1) = split(/\./,$_);
  if ($status{'resource.'.$partid.'.submitted_by'} ne '') {   if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
     $submitted = 0;      $submitted = 0;
Line 729  LISTJAVASCRIPT Line 1002  LISTJAVASCRIPT
  $status{'resource.'.$partid.'.submitted_by'}.'" />';   $status{'resource.'.$partid.'.submitted_by'}.'" />';
  }   }
     }      }
       
     next if (!$submitted && ($submitonly eq 'yes' ||      next if (!$submitted && ($submitonly eq 'yes' ||
      $submitonly eq 'incorrect' ||       $submitonly eq 'incorrect' ||
      $submitonly eq 'graded'));       $submitonly eq 'graded'));
     next if (!$graded && ($submitonly eq 'graded' ||      next if (!$graded && ($submitonly eq 'graded'));
   $submitonly eq 'incorrect'));      next if (!$incorrect && $submitonly eq 'incorrect');
  }   }
   
  $ctr++;   $ctr++;
    my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
           my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
  if ( $perm{'vgr'} eq 'F' ) {   if ( $perm{'vgr'} eq 'F' ) {
     $gradeTable.='<tr bgcolor="#ffffe6">' if ($ctr%2 ==1);      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"><input type=checkbox name="stuinfo" value="'.                 '<td align="center"><label><input type="checkbox" name="stuinfo" value="'.
  $student.':'.$$fullname{$student}.'&nbsp;"></td>'."\n".                 $student.':'.$$fullname{$student}.':::SECTION'.$section.
  '<td>'.&nameUserString(undef,$$fullname{$student},$uname,$udom).'</td>'."\n";         ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.
          &nameUserString(undef,$$fullname{$student},$uname,$udom).
     if ($ENV{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {         '&nbsp;'.$section.($group ne '' ?'/'.$group:'').'</td>'."\n";
  foreach (sort keys(%status)) {  
     next if (/^resource.*?submitted_by$/);      if ($submitonly ne 'all') {
     $gradeTable.='<td align="middle">&nbsp;'.$status{$_}.'&nbsp;</td>'."\n";   foreach (sort(keys(%status))) {
       next if ($_ =~ /^resource.*?submitted_by$/);
       $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' && $submitonly ne 'all') {      if (($submitonly ne 'queued') && ($submitonly ne 'all')) {
  foreach (@$partlist) {   foreach (@$partlist) {
     $gradeTable.='<td>&nbsp;</td>';      $gradeTable.='<td>&nbsp;</td>';
  }   }
       } elsif ($submitonly eq 'queued') {
    $gradeTable.='<td>&nbsp;</td>';
     }      }
  $gradeTable.='</tr>';   $gradeTable.=&Apache::loncommon::end_data_table_row();
     }      }
   
     $gradeTable.='</table></td></tr></table>'.      $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;<font color="red">There are no students currently enrolled.</font>';      $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'; }
     $gradeTable='<br />&nbsp;<font color="red">'.      if ($submitonly eq 'queued'   ) { $submissions = 'queued submissions'; }
  'No '.$submissions.' found for this resource for any students. ('.$num_students.      $gradeTable='<br />&nbsp;<span class="LC_warning">'.
  ' students checked for '.$submissions.')</font><br />';   &mt('No '.$submissions.' found for this resource for any students. ([_1] students checked for '.$submissions.')',
       $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,$url);  
     $request->print($gradeTable);      $request->print($gradeTable);
     return '';      return '';
 }  }
   
 #---- Called from the listStudents routine  #---- Called from the listStudents routine
   
   sub check_script {
       my ($form, $type)=@_;
       my $chkallscript= &Apache::lonhtmlcommon::scripttag('
       function checkall() {
           for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
               ele = document.forms.'.$form.'.elements[i];
               if (ele.name == "'.$type.'") {
               document.forms.'.$form.'.elements[i].checked=true;
                                          }
           }
       }
   
       function checksec() {
           for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
               ele = document.forms.'.$form.'.elements[i];
              string = document.forms.'.$form.'.chksec.value;
              if
             (ele.value.indexOf(":::SECTION"+string)>0) {
                 document.forms.'.$form.'.elements[i].checked=true;
               }
           }
       }
   
   
       function uncheckall() {
           for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
               ele = document.forms.'.$form.'.elements[i];
               if (ele.name == "'.$type.'") {
               document.forms.'.$form.'.elements[i].checked=false;
                                          }
           }
       }
   
   '."\n");
       return $chkallscript;
   }
   
   sub check_buttons {
       my $buttons.='<input type="button" onclick="checkall()" value="'.&mt('Check All').'" />';
       $buttons.='<input type="button" onclick="uncheckall()" value="'.&mt('Uncheck All').'" />&nbsp;';
       $buttons.='<input type="button" onclick="checksec()" value="'.&mt('Check Section/Group').'" />';
       $buttons.='<input type="text" size="5" name="chksec" />&nbsp;';
       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;
   
     foreach (@stuchecked) {      foreach my $student (@stuchecked) {
  my ($uname,$udom,$fullname) = split(/:/);   my ($uname,$udom,$fullname) = split(/:/,$student);
  $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 815  sub processGroup { Line 1146  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">      $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 825  sub sub_page_js { Line 1156  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 939  sub sub_page_js { Line 1270  sub sub_page_js {
           
  }   }
  if (val == "Grade Student") {   if (val == "Grade Student") {
     formname.showgrading.value = "yes";  
     if (formname.Status.value == "") {      if (formname.Status.value == "") {
  formname.Status.value = "Active";   formname.Status.value = "Active";
     }      }
Line 984  sub sub_page_js { Line 1314  sub sub_page_js {
   
  formname.submit();   formname.submit();
     }      }
 </script>  
 SUBJAVASCRIPT  SUBJAVASCRIPT
 }  }
   
Line 993  sub sub_page_kw_js { Line 1322  sub sub_page_kw_js {
     my $request = shift;      my $request = shift;
     my $iconpath = $request->dir_config('lonIconsURL');      my $iconpath = $request->dir_config('lonIconsURL');
     &commonJSfunctions($request);      &commonJSfunctions($request);
     $request->print(<<SUBJAVASCRIPT);  
 <script type="text/javascript" language="javascript">      my $inner_js_msg_central= &Apache::lonhtmlcommon::scripttag(<<INNERJS);
       function checkInput() {
         opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value);
         var nmsg   = opener.document.SCORE.savemsgN.value;
         var usrctr = document.msgcenter.usrctr.value;
         var newval = opener.document.SCORE["newmsg"+usrctr];
         newval.value = opener.checkEntities(document.msgcenter.newmsg.value);
   
         var msgchk = "";
         if (document.msgcenter.subchk.checked) {
            msgchk = "msgsub,";
         }
         var includemsg = 0;
         for (var i=1; i<=nmsg; i++) {
             var opnmsg = opener.document.SCORE["savemsg"+i];
             var frmmsg = document.msgcenter["msg"+i];
             opnmsg.value = opener.checkEntities(frmmsg.value);
             var showflg = opener.document.SCORE["shownOnce"+i];
             showflg.value = "1";
             var chkbox = document.msgcenter["msgn"+i];
             if (chkbox.checked) {
                msgchk += "savemsg"+i+",";
                includemsg = 1;
             }
         }
         if (document.msgcenter.newmsgchk.checked) {
            msgchk += "newmsg"+usrctr;
            includemsg = 1;
         }
         imgformname = opener.document.SCORE["mailicon"+usrctr];
         imgformname.src = "$iconpath/"+((includemsg) ? "mailto.gif" : "mailbkgrd.gif");
         var includemsg = opener.document.SCORE["includemsg"+usrctr];
         includemsg.value = msgchk;
   
         self.close()
   
       }
   INNERJS
   
       my $inner_js_highlight_central= &Apache::lonhtmlcommon::scripttag(<<INNERJS);
       function updateChoice(flag) {
         opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr);
         opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize);
         opener.document.SCORE.kwstyle.value = opener.radioSelection(document.hlCenter.kwdstyle);
         opener.document.SCORE.refresh.value = "on";
         if (opener.document.SCORE.keywords.value!=""){
            opener.document.SCORE.submit();
         }
         self.close()
       }
   INNERJS
   
       my $start_page_msg_central = 
           &Apache::loncommon::start_page('Message Central',$inner_js_msg_central,
          {'js_ready'  => 1,
    'only_body' => 1,
    'bgcolor'   =>'#FFFFFF',});
       my $end_page_msg_central = 
    &Apache::loncommon::end_page({'js_ready' => 1});
   
   
       my $start_page_highlight_central = 
           &Apache::loncommon::start_page('Highlight Central',
          $inner_js_highlight_central,
          {'js_ready'  => 1,
    'only_body' => 1,
    'bgcolor'   =>'#FFFFFF',});
       my $end_page_highlight_central = 
    &Apache::loncommon::end_page({'js_ready' => 1});
   
       my $docopen=&Apache::lonhtmlcommon::javascript_docopen();
       $docopen=~s/^document\.//;
       my $alertmsg = &mt('Please select a word or group of words from document and then click this link.');
       $request->print(&Apache::lonhtmlcommon::scripttag(<<SUBJAVASCRIPT));
   
 //===================== Show list of keywords ====================  //===================== Show list of keywords ====================
   function keywords(formname) {    function keywords(formname) {
Line 1025  sub sub_page_kw_js { Line 1427  sub sub_page_kw_js {
     else return;      else return;
     var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");      var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");
     if (cleantxt=="") {      if (cleantxt=="") {
  alert("Please select a word or group of words from document and then click this link.");   alert("$alertmsg");
  return;   return;
     }      }
     var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);      var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);
Line 1105  sub sub_page_kw_js { Line 1507  sub sub_page_kw_js {
     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='+scrollbar+',screenx='+xpos+',screeny='+ypos+',width=600,height='+height);
     pWin.focus();      pWin.focus();
     pDoc = pWin.document;      pDoc = pWin.document;
     pDoc.open('text/html','replace');      pDoc.$docopen;
     pDoc.write("<html><head>");      pDoc.write('$start_page_msg_central');
     pDoc.write("<title>Message Central</title>");  
   
     pDoc.write("<script language=javascript>");  
     pDoc.write("function checkInput() {");  
     pDoc.write("  opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value);");  
     pDoc.write("  var nmsg   = opener.document.SCORE.savemsgN.value;");  
     pDoc.write("  var usrctr = document.msgcenter.usrctr.value;");  
     pDoc.write("  var newval = opener.document.SCORE[\\"newmsg\\"+usrctr];");  
     pDoc.write("  newval.value = opener.checkEntities(document.msgcenter.newmsg.value);");  
   
     pDoc.write("  var msgchk = \\"\\";");  
     pDoc.write("  if (document.msgcenter.subchk.checked) {");  
     pDoc.write("     msgchk = \\"msgsub,\\";");  
     pDoc.write("  }");  
     pDoc.write("  var includemsg = 0;");  
     pDoc.write("  for (var i=1; i<=nmsg; i++) {");  
     pDoc.write("      var opnmsg = opener.document.SCORE[\\"savemsg\\"+i];");  
     pDoc.write("      var frmmsg = document.msgcenter[\\"msg\\"+i];");  
     pDoc.write("      opnmsg.value = opener.checkEntities(frmmsg.value);");  
     pDoc.write("      var showflg = opener.document.SCORE[\\"shownOnce\\"+i];");  
     pDoc.write("      showflg.value = \\"1\\";");  
     pDoc.write("      var chkbox = document.msgcenter[\\"msgn\\"+i];");  
     pDoc.write("      if (chkbox.checked) {");  
     pDoc.write("         msgchk += \\"savemsg\\"+i+\\",\\";");  
     pDoc.write("         includemsg = 1;");  
     pDoc.write("      }");  
     pDoc.write("  }");  
     pDoc.write("  if (document.msgcenter.newmsgchk.checked) {");  
     pDoc.write("     msgchk += \\"newmsg\\"+usrctr;");  
     pDoc.write("     includemsg = 1;");  
     pDoc.write("  }");  
     pDoc.write("  imgformname = opener.document.SCORE[\\"mailicon\\"+usrctr];");  
     pDoc.write("  imgformname.src = \\"$iconpath/\\"+((includemsg) ? \\"mailto.gif\\" : \\"mailbkgrd.gif\\");");  
     pDoc.write("  var includemsg = opener.document.SCORE[\\"includemsg\\"+usrctr];");  
     pDoc.write("  includemsg.value = msgchk;");  
   
     pDoc.write("  self.close()");  
   
     pDoc.write("}");  
   
     pDoc.write("<");  
     pDoc.write("/script>");  
   
     pDoc.write("</head><body bgcolor=white>");  
   
     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("<font color=\\"green\\" size=+1>&nbsp;Compose Message for \"+fullname+\"</font><br><br>");      pDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Compose Message for \"+fullname+\"<\\/span><\\/h3><br /><br />");
   
     pDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");      pDoc.write('<table border="0" width="100%"><tr><td bgcolor="#777777">');
     pDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");      pDoc.write('<table border="0" width="100%"><tr bgcolor="#DDFFFF">');
     pDoc.write("<td><b>Type</b></td><td><b>Include</b></td><td><b>Message</td></tr>");      pDoc.write("<td><b>Type<\\/b><\\/td><td><b>Include<\\/b><\\/td><td><b>Message<\\/td><\\/tr>");
 }  }
     function displaySubject(msg,shwsel) {      function displaySubject(msg,shwsel) {
     pDoc = pWin.document;      pDoc = pWin.document;
     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");      pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     pDoc.write("<td>Subject</td>");      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><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"60\\" 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 bgcolor=\\"#ffffdd\\">");
     pDoc.write("<td align=\\"center\\">"+ctr+"</td>");      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><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 bgcolor=\\"#ffffdd\\">");
     pDoc.write("<td align=\\"center\\">New</td>");      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><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=\\"Save\\" onclick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");
     pDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br><br>");      pDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onclick=\\"self.close()\\"><br /><br />");
     pDoc.write("</form>");      pDoc.write("<\\/form>");
     pDoc.write("</body></html>");      pDoc.write('$end_page_msg_central');
     pDoc.close();      pDoc.close();
 }  }
   
Line 1236  sub sub_page_kw_js { Line 1594  sub sub_page_kw_js {
     hwdWin = window.open('', 'KeywordHighlightCentral', 'resizeable=yes,toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx='+xpos+',screeny='+ypos);      hwdWin = window.open('', 'KeywordHighlightCentral', 'resizeable=yes,toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx='+xpos+',screeny='+ypos);
     hwdWin.focus();      hwdWin.focus();
     var hDoc = hwdWin.document;      var hDoc = hwdWin.document;
     hDoc.open('text/html','replace');      hDoc.$docopen;
     hDoc.write("<html><head>");      hDoc.write('$start_page_highlight_central');
     hDoc.write("<title>Highlight Central</title>");  
   
     hDoc.write("<script language=javascript>");  
     hDoc.write("function updateChoice(flag) {");  
     hDoc.write("  opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr);");  
     hDoc.write("  opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize);");  
     hDoc.write("  opener.document.SCORE.kwstyle.value = opener.radioSelection(document.hlCenter.kwdstyle);");  
     hDoc.write("  opener.document.SCORE.refresh.value = \\"on\\";");  
     hDoc.write("  if (opener.document.SCORE.keywords.value!=\\"\\"){");  
     hDoc.write("     opener.document.SCORE.submit();");  
     hDoc.write("  }");  
     hDoc.write("  self.close()");  
     hDoc.write("}");  
   
     hDoc.write("<");  
     hDoc.write("/script>");  
   
     hDoc.write("</head><body bgcolor=white>");  
   
     hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");      hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");
     hDoc.write("<font color=\\"green\\" size=+1>&nbsp;Keyword Highlight Options</font><br><br>");      hDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Keyword Highlight Options<\\/span><\\/h3><br /><br />");
   
     hDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");      hDoc.write('<table border="0" width="100%"><tr><td bgcolor="#777777">');
     hDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");      hDoc.write('<table border="0" width="100%"><tr bgcolor="#DDFFFF">');
     hDoc.write("<td><b>Text Color</b></td><td><b>Font Size</b></td><td><b>Font Style</td></tr>");      hDoc.write("<td><b>Text Color<\\/b><\\/td><td><b>Font Size<\\/b><\\/td><td><b>Font Style<\\/td><\\/tr>");
   }    }
   
   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 bgcolor=\\"#ffffdd\\">");
     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>");
     hDoc.write("</td></tr></table>&nbsp;");      hDoc.write("<\\/td><\\/tr><\\/table>&nbsp;");
     hDoc.write("<input type=\\"button\\" value=\\"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=\\"Cancel\\" 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("</body></html>");      hDoc.write('$end_page_highlight_central');
     hDoc.close();      hDoc.close();
   }    }
   
 </script>  
 SUBJAVASCRIPT  SUBJAVASCRIPT
 }  }
   
   sub get_increment {
       my $increment = $env{'form.increment'};
       if ($increment != 1 && $increment != .5 && $increment != .25 &&
           $increment != .1) {
           $increment = 1;
       }
       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 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)') 
   '<font color="red">problem weight assigned by computer</font>');                             : '<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 '' ?
   '' : $$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 $result='<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,undef,$symb);      my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
     $result.='<table border="0"><tr><td>'.         [$partid]);
  '<b>Part: </b>'.$display_part.' <b>Points: </b></td><td>'."\n";      my $aggtries = $$record{'resource.'.$partid.'.tries'};
       if ($last_resets{$partid}) {
           $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
       }
       $result.=&Apache::loncommon::start_data_table_row();
     my $ctr = 0;      my $ctr = 0;
     $result.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across      my $thisweight = 0;
     while ($ctr<=$wgt) {      my $increment = &get_increment();
  $result.= '<td><nobr><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.  
     'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.      my $radio.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across
     $ctr.')" value="'.$ctr.'" '.      while ($thisweight<=$wgt) {
     ($score eq $ctr ? 'checked':'').' /> '.$ctr."</nobr></td>\n";   $radio.= '<td><span class="LC_nobreak"><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
  $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');          'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.
       $thisweight.')" value="'.$thisweight.'" '.
       ($score eq $thisweight ? 'checked="checked"':'').' /> '.$thisweight."</label></span></td>\n";
    $radio.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
           $thisweight += $increment;
  $ctr++;   $ctr++;
     }      }
     $result.='</tr></table>';      $radio.='</tr></table>';
   
     $result.='</td><td>&nbsp;<b>or</b>&nbsp;</td>'."\n";      my $line.='<input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
     $result.='<td><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";
       $line.='<td><select name="GD_SEL'.$counter.'_'.$partid.'" '.
     $result.='<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="on">excused</option>';      '<option value="excused" selected="selected">'.&mt('excused').'</option>';
     } else {      } else {
  $result.='<option selected="on"> </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>'.$display_part.'</td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>';
       $result.=&Apache::loncommon::end_data_table_row();
     $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="'.
  $$record{'resource.'.$partid.'.solved'}.'" />'."\n";   $$record{'resource.'.$partid.'.solved'}.'" />'."\n".
     $result.='</td></tr></table>'."\n";          '<input type="hidden" name="totaltries'.$counter.'_'.$partid.'" value="'.
           $$record{'resource.'.$partid.'.tries'}.'" />'."\n".
           '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.
           $aggtries.'" />'."\n";
       my $res_error;
       $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record,\$res_error);
       if ($res_error) {
           return &navmap_errormsg();
       }
     return $result;      return $result;
 }  }
   
   sub handback_box {
       my ($symb,$uname,$udom,$counter,$partid,$record,$res_error_pointer) = @_;
       my ($partlist,$handgrade,$responseType) = &response_type($symb,$res_error_pointer);
       my (@respids);
        my @part_response_id = &flatten_responseType($responseType);
       foreach my $part_response_id (@part_response_id) {
       my ($part,$resp) = @{ $part_response_id };
           if ($part eq $partid) {
               push(@respids,$resp);
           }
       }
       my $result;
       foreach my $respid (@respids) {
    my $prefix = $counter.'_'.$partid.'_'.$respid.'_';
    my $files=&get_submitted_files($udom,$uname,$partid,$respid,$record);
    next if (!@$files);
    my $file_counter = 1;
    foreach my $file (@$files) {
       if ($file =~ /\/portfolio\//) {
              my ($file_path, $file_disp) = ($file =~ m|(.+/)(.+)$|);
              my ($name,$version,$ext) = &file_name_version_ext($file_disp);
              $file_disp = "$name.$ext";
              $file = $file_path.$file_disp;
              $result.=&mt('Return commented version of [_1] to student.',
       '<span class="LC_filename">'.$file_disp.'</span>');
              $result.='<input type="file"   name="'.$prefix.'returndoc'.$file_counter.'" />'."\n";
              $result.='<input type="hidden" name="'.$prefix.'origdoc'.$file_counter.'" value="'.$file.'" /><br />';
              $result.='('.&mt('File will be uploaded when you click on Save &amp; Next below.').')<br />';
              $file_counter++;
       }
    }
       }
       return $result;    
   }
   
 sub show_problem {  sub show_problem {
     my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode) = @_;      my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode,$form) = @_;
     my $rendered;      my $rendered;
       my %form = ((ref($form) eq 'HASH')? %{$form} : ());
       &Apache::lonxml::remember_problem_counter();
     if ($mode eq 'both' or $mode eq 'text') {      if ($mode eq 'both' or $mode eq 'text') {
  $rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,   $rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,
      $ENV{'request.course.id'});         $env{'request.course.id'},
          undef,\%form);
     }      }
     if ($removeform) {      if ($removeform) {
  $rendered=~s|<form(.*?)>||g;   $rendered=~s|<form(.*?)>||g;
  $rendered=~s|</form>||g;   $rendered=~s|</form>||g;
  $rendered=~s|name="submit"|name="would_have_been_submit"|g;   $rendered=~s|(<input[^>]*name\s*=\s*"?)(\w+)("?)|$1would_have_been_$2$3|g;
     }      }
     my $companswer;      my $companswer;
     if ($mode eq 'both' or $mode eq 'answer') {      if ($mode eq 'both' or $mode eq 'answer') {
  $companswer=&Apache::loncommon::get_student_answers($symb,$uname,$udom,   &Apache::lonxml::restore_problem_counter();
     $ENV{'request.course.id'});   $companswer=
       &Apache::loncommon::get_student_answers($symb,$uname,$udom,
       $env{'request.course.id'},
       %form);
     }      }
     if ($removeform) {      if ($removeform) {
  $companswer=~s|<form(.*?)>||g;   $companswer=~s|<form(.*?)>||g;
  $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">';      $rendered=
     $result.='<table border="0" width="100%">';          '<div class="LC_Box">'
     if ($viewon) {         .'<h3 class="LC_hcell">'.&mt('View of the problem').'</h3>'
  $result.='<tr><td bgcolor="#e6ffff"><b> ';         .$rendered
  if ($mode eq 'both' or $mode eq 'text') {         .'</div>';
     $result.='View of the problem - ';      $companswer=
  } else {          '<div class="LC_Box">'
     $result.='Correct answer: ';         .'<h3 class="LC_hcell">'.&mt('Correct answer').'</h3>'
  }         .$companswer
  $result.=$ENV{'form.fullname'}.'</b></td></tr>';         .'</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 {
       my ($r, $symb) = @_;
       my @students = &Apache::loncommon::get_env_multiple('form.stuinfo');
   
       foreach my $student (@students) {
           my ($uname,$udom,$fullname) = split(/:/,$student);
           my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
         $udom,$uname);
           my ($string,$timestamp)= &get_last_submission(\%record);
           foreach my $submission (@$string) {
               my ($partid,$respid) =
    ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
               my $files=&get_submitted_files($udom,$uname,$partid,$respid,
      \%record);
               return 1 if (@$files);
           }
       }
       return 0;
   }
   
   sub download_all_link {
       my ($r,$symb) = @_;
       unless (&files_exist($r, $symb)) {
          $r->print(&mt('There are currently no submitted documents.'));
          return;
       }
   
       my $all_students = 
    join("\n", &Apache::loncommon::get_env_multiple('form.stuinfo'));
   
       my $parts =
    join("\n",&Apache::loncommon::get_env_multiple('form.vPart'));
   
       my $identifier = &Apache::loncommon::get_cgi_id();
       &Apache::lonnet::appenv({'cgi.'.$identifier.'.students' => $all_students,
                                'cgi.'.$identifier.'.symb' => $symb,
                                'cgi.'.$identifier.'.parts' => $parts,});
       $r->print('<a href="/cgi-bin/multidownload.pl?'.$identifier.'">'.
         &mt('Download All Submitted Documents').'</a>');
       return;
   }
   
   sub submit_download_link {
       my ($request,$symb) = @_;
       if (!$symb) { return ''; }
   #FIXME: Figure out which type of problem this is and provide appropriate download
       &download_all_link($request,$symb);
   }
   
   sub build_section_inputs {
       my $section_inputs;
       if ($env{'form.section'} eq '') {
           $section_inputs .= '<input type="hidden" name="section" value="all" />'."\n";
       } else {
           my @sections = &Apache::loncommon::get_env_multiple('form.section');
           foreach my $section (@sections) {
               $section_inputs .= '<input type="hidden" name="section" value="'.$section.'" />'."\n";
           }
       }
       return $section_inputs;
   }
   
 # --------------------------- show submissions of a student, option to grade   # --------------------------- show submissions of a student, option to grade 
 sub submission {  sub submission {
     my ($request,$counter,$total) = @_;      my ($request,$counter,$total,$symb) = @_;
       my ($uname,$udom)     = ($env{'form.student'},$env{'form.userdom'});
     (my $url=$ENV{'form.url'})=~s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;      $udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student?
     my ($uname,$udom)     = ($ENV{'form.student'},$ENV{'form.userdom'});      my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
     $udom = ($udom eq '' ? $ENV{'user.domain'} : $udom); #has form.userdom changed for a student?      $env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq '';
     my $usec = &Apache::lonnet::getsection($udom,$uname,$ENV{'request.course.id'});  
     $ENV{'form.fullname'} = &get_fullname ($uname,$udom) if $ENV{'form.fullname'} eq '';  
   
     my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));      my $probtitle=&Apache::lonnet::gettitle($symb); 
     if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; }      if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; }
   
     if (!&canview($usec)) {      if (!&canview($usec)) {
  $request->print('<font color="red">Unable to view requested student.('.   $request->print('<span class="LC_warning">Unable to view requested student.('.
  $uname.$udom.$usec.$ENV{'request.course.id'}.')</font>');   $uname.':'.$udom.' in section '.$usec.' in course id '.
  $request->print(&show_grading_menu_form($symb,$url));   $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'; }      if (!$env{'form.vProb'}) { $env{'form.vProb'} = 'yes'; }
     if (!$ENV{'form.vAns'}) { $ENV{'form.vAns'} = '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 src="'.$request->dir_config('lonIconsURL').      my $checkIcon = '<img alt="'.&mt('Check Mark').
    '" 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 '' ?    $request->print('<h3>&nbsp;<span class="LC_info">'.&mt('Submission Record').'</span></h3>');
     &Apache::lonnet::gettitle($symb) : $ENV{'form.probTitle'};  
   
  $request->print('<h3>&nbsp;<font color="#339933">Submission Record</font></h3>'."\n".  
  '<font size=+1>&nbsp;<b>Resource: </b>'.$ENV{'form.probTitle'}.'</font>'."\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.
  if ($ENV{'form.vProb'} eq 'yes' or $ENV{'form.vAns'} eq 'yes') {   if ($env{'form.vProb'} eq 'yes' or $env{'form.vAns'} eq 'yes') {
     my $mode;      my $mode;
     if ($ENV{'form.vProb'} eq 'yes' && $ENV{'form.vAns'} eq 'yes') {      if ($env{'form.vProb'} eq 'yes' && $env{'form.vAns'} eq 'yes') {
  $mode='both';   $mode='both';
     } elsif ($ENV{'form.vProb'} eq 'yes') {      } elsif ($env{'form.vProb'} eq 'yes') {
  $mode='text';   $mode='text';
     } elsif ($ENV{'form.vAns'} eq 'yes') {      } elsif ($env{'form.vAns'} eq 'yes') {
  $mode='answer';   $mode='answer';
     }      }
       &Apache::lonxml::clear_problem_counter();
     $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode));      $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode));
  }   }
   
  # kwclr is the only variable that is guaranteed to be non blank    # kwclr is the only variable that is guaranteed to be non blank 
         # if this subroutine has been called once.          # if this subroutine has been called once.
  my %keyhash = ();   my %keyhash = ();
  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'});
   
     my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'};      my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
     $ENV{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';      $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
     $ENV{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';      $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
     $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'));
  $request->print('<form action="/adm/grades" method="post" name="SCORE">'."\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="'.$ENV{'form.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="'.$symb.'" />'."\n".   '<input type="hidden" name="symb"       value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
  '<input type="hidden" name="url"        value="'.$url.'" />'."\n".   '<input type="hidden" name="vProb"      value="'.$env{'form.vProb'}.'" />'."\n".
  '<input type="hidden" name="showgrading" value="'.$ENV{'form.showgrading'}.'" />'."\n".   '<input type="hidden" name="vAns"       value="'.$env{'form.vAns'}.'" />'."\n".
  '<input type="hidden" name="vProb"      value="'.$ENV{'form.vProb'}.'" />'."\n".   '<input type="hidden" name="lastSub"    value="'.$env{'form.lastSub'}.'" />'."\n".
  '<input type="hidden" name="vAns"       value="'.$ENV{'form.vAns'}.'" />'."\n".   &build_section_inputs().
  '<input type="hidden" name="lastSub"    value="'.$ENV{'form.lastSub'}.'" />'."\n".   '<input type="hidden" name="submitonly" value="'.$env{'form.submitonly'}.'" />'."\n".
  '<input type="hidden" name="section"    value="'.$ENV{'form.section'}.'">'."\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') {
     $request->print('<input type="hidden" name="keywords" value="'.$ENV{'form.keywords'}.'" />'."\n".          if (1) {
     '<input type="hidden" name="kwclr"    value="'.$ENV{'form.kwclr'}.'" />'."\n".      $request->print('<input type="hidden" name="keywords" value="'.$env{'form.keywords'}.'" />'."\n".
     '<input type="hidden" name="kwsize"   value="'.$ENV{'form.kwsize'}.'" />'."\n".      '<input type="hidden" name="kwclr"    value="'.$env{'form.kwclr'}.'" />'."\n".
     '<input type="hidden" name="kwstyle"  value="'.$ENV{'form.kwstyle'}.'" />'."\n".      '<input type="hidden" name="kwsize"   value="'.$env{'form.kwsize'}.'" />'."\n".
     '<input type="hidden" name="msgsub"   value="'.$ENV{'form.msgsub'}.'" />'."\n".      '<input type="hidden" name="kwstyle"  value="'.$env{'form.kwstyle'}.'" />'."\n".
       '<input type="hidden" name="msgsub"   value="'.$env{'form.msgsub'}.'" />'."\n".
     '<input type="hidden" name="shownSub" value="0" />'."\n".      '<input type="hidden" name="shownSub" value="0" />'."\n".
     '<input type="hidden" name="savemsgN" value="'.$ENV{'form.savemsgN'}.'" />'."\n");      '<input type="hidden" name="savemsgN" value="'.$env{'form.savemsgN'}.'" />'."\n");
     foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) {      foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) {
  $request->print('<input type="hidden" name="vPart" value="'.$partid.'" />'."\n");   $request->print('<input type="hidden" name="vPart" value="'.$partid.'" />'."\n");
     }      }
  }   }
   
  my ($cts,$prnmsg) = (1,'');   my ($cts,$prnmsg) = (1,'');
  while ($cts <= $ENV{'form.savemsgN'}) {   while ($cts <= $env{'form.savemsgN'}) {
     $prnmsg.='<input type="hidden" name="savemsg'.$cts.'" value="'.      $prnmsg.='<input type="hidden" name="savemsg'.$cts.'" value="'.
  (!exists($keyhash{$symb.'_savemsg'.$cts}) ?    (!exists($keyhash{$symb.'_savemsg'.$cts}) ? 
  &Apache::lonfeedback::clear_out_html($ENV{'form.savemsg'.$cts}) :   &Apache::lonfeedback::clear_out_html($env{'form.savemsg'.$cts}) :
  &Apache::lonfeedback::clear_out_html($keyhash{$symb.'_savemsg'.$cts})).   &Apache::lonfeedback::clear_out_html($keyhash{$symb.'_savemsg'.$cts})).
  '" />'."\n".   '" />'."\n".
  '<input type="hidden" name="shownOnce'.$cts.'" value="0" />'."\n";   '<input type="hidden" name="shownOnce'.$cts.'" value="0" />'."\n";
Line 1518  sub submission { Line 1987  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') {
           if (1) {
 #  #
 # Print out the keyword options line  # Print out the keyword options line
 #  #
     $request->print(<<KEYWORDS);      $request->print(<<KEYWORDS);
 &nbsp;<b>Keyword Options:</b>&nbsp;  &nbsp;<b>Keyword Options:</b>&nbsp;
 <a href="javascript:keywords(document.SCORE)"; TARGET=_self>List</a>&nbsp; &nbsp;  <a href="javascript:keywords(document.SCORE);" target="_self">List</a>&nbsp; &nbsp;
 <a href="#" onMouseDown="javascript:getSel(); return false"  <a href="#" onmousedown="javascript:getSel(); return false"
  CLASS="page">Paste Selection to List</a>&nbsp; &nbsp;   CLASS="page">Paste Selection to List</a>&nbsp; &nbsp;
 <a href="javascript:kwhighlight()"; TARGET=_self>Highlight Attribute</a><br /><br />  <a href="javascript:kwhighlight();" target="_self">Highlight Attribute</a><br /><br />
 KEYWORDS  KEYWORDS
 #  #
 # Load the other essays for similarity check  # Load the other essays for similarity check
 #  #
             my $essayurl=&Apache::lonnet::declutter($url);              my (undef,undef,$essayurl) = &Apache::lonnet::decode_symb($symb);
     my ($adom,$aname,$apath)=($essayurl=~/^(\w+)\/(\w+)\/(.*)$/);      my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);
     $apath=&Apache::lonnet::escape($apath);      $apath=&escape($apath);
     $apath=~s/\W/\_/gs;      $apath=~s/\W/\_/gs;
     %oldessays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);      %old_essays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
         }          }
     }      }
   
     if ($ENV{'form.vProb'} eq 'all' or $ENV{'form.vAns'} eq 'all') {  # This is where output for one specific student would start
  $request->print('<br /><br /><br />') if ($counter > 0);      my $add_class = ($counter%2) ? ' LC_grade_show_user_odd_row' : '';
       $request->print(
           "\n\n"
          .'<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(
                   &mt('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') {
  my $mode;   my $mode;
  if ($ENV{'form.vProb'} eq 'all' && $ENV{'form.vAns'} eq 'all') {   if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {
     $mode='both';      $mode='both';
  } elsif ($ENV{'form.vProb'} eq 'all' ) {   } elsif ($env{'form.vProb'} eq 'all' ) {
     $mode='text';      $mode='text';
  } elsif ($ENV{'form.vAns'} eq 'all') {   } elsif ($env{'form.vAns'} eq 'all') {
     $mode='answer';      $mode='answer';
  }   }
  $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode));   &Apache::lonxml::clear_problem_counter();
    $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 $res_error;
     my ($partlist,$handgrade,$responseType) = &response_type($url,$symb);      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 $result='<div class="LC_Box">'
                 .'<h3 class="LC_hcell">'.&mt('Submissions').'</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') {
       if (1) {
           $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 @col_fullnames;      my $fullname;
     my ($classlist,$fullname);      my $col_fullnames = [];
     if ($ENV{'form.handgrade'} eq 'yes') {  #    if ($env{'form.handgrade'} eq 'yes') {
  ($classlist,undef,$fullname) = &getclasslist('all','0');      if (1) {
  for (keys (%$handgrade)) {   (my $sub_result,$fullname,$col_fullnames)=
     my $ncol = &Apache::lonnet::EXT('resource.'.$_.      &check_collaborators($symb,$uname,$udom,\%record,$handgrade,
     '.maxcollaborators',   $counter);
                                             $symb,$udom,$uname);   $result.=$sub_result;
     next if ($ncol <= 0);  
             s/\_/\./g;  
             next if ($record{'resource.'.$_.'.collaborators'} eq '');  
             my @goodcollaborators = ();  
             my @badcollaborators  = ();  
     foreach (split(/,?\s+/,$record{'resource.'.$_.'.collaborators'})) {   
  $_ =~ s/[\$\^\(\)]//g;  
  next if ($_ eq '');  
  my ($co_name,$co_dom) = split /\@|:/,$_;  
  $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i);  
  next if ($co_name eq $uname && $co_dom eq $udom);  
  # Doing this grep allows 'fuzzy' specification  
  my @Matches = grep /^$co_name:$co_dom$/i,keys %$classlist;  
  if (! scalar(@Matches)) {  
     push @badcollaborators,$_;  
  } else {  
     push @goodcollaborators, @Matches;  
  }  
     }  
             if (scalar(@goodcollaborators) != 0) {  
                 $result.='<b>Collaborators: </b>';  
                 foreach (@goodcollaborators) {  
     my ($lastname,$givenn) = split(/,/,$$fullname{$_});  
     push @col_fullnames, $givenn.' '.$lastname;  
     $result.=$$fullname{$_}.'&nbsp; &nbsp; &nbsp;';  
  }  
                 $result.='<br />'."\n";  
  my ($part)=split(/\./,$_);  
  $result.='<input type="hidden" name="collaborator'.$counter.  
     '" value="'.$part.':'.(join ':',@goodcollaborators).'" />'.  
     "\n";  
     }  
     if (scalar(@badcollaborators) > 0) {  
  $result.='<table border="0"><tr bgcolor="#ffbbbb"><td>';  
  $result.='This student has submitted ';  
  $result.=(scalar(@badcollaborators) == 1) ? 'an invalid collaborator' : 'invalid collaborators';  
  $result .= ': '.join(', ',@badcollaborators);  
  $result .= '</td></tr></table>';  
     }           
     if (scalar(@badcollaborators > $ncol)) {  
  $result .= '<table border="0"><tr bgcolor="#ffbbbb"><td>';  
  $result .= 'This student has submitted too many '.  
     'collaborators.  Maximum is '.$ncol.'.';  
  $result .= '</td></tr></table>';  
     }  
  }  
     }      }
     $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)$/) {      if ($env{'form.lastSub'} =~ /^(lastonly|hdgrade)$/) {
  my ($string,$timestamp)= &get_last_submission(\%record);   my ($string,$timestamp)= &get_last_submission(\%record);
  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 {          } else {
               $lastsubonly =
                   '<div class="LC_grade_submissions_body">'
                  .'<b>'.&mt('Date Submitted:').'</b> '.$$timestamp."\n";
   
     my %seenparts;      my %seenparts;
     for my $part (sort keys(%$handgrade)) {      my @part_response_id = &flatten_responseType($responseType);
  my ($partid,$respid) = split(/_/,$part);      foreach my $part (@part_response_id) {
  my $display_part=&get_display_part($partid,$url,$symb);   next if ($env{'form.lastSub'} eq 'hdgrade' 
  if ($ENV{"form.$uname:$udom:$partid:submitted_by"}) {   && $$handgrade{$$part[0].'_'.$$part[1]} ne 'yes');
   
    my ($partid,$respid) = @{ $part };
    my $display_part=&get_display_part($partid,$symb);
    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.      my $submitby='<b>Part:</b> '.$display_part.
  ' <b>Collaborative submission by:</b> '.   ' <b>Collaborative submission by:</b> '.
  '<a href="javascript:viewSubmitter(\''.   '<a href="javascript:viewSubmitter(\''.
  $ENV{"form.$uname:$udom:$partid:submitted_by"}.   $env{"form.$uname:$udom:$partid:submitted_by"}.
  '\')"; TARGET=_self>'.   '\');" target="_self">'.
  $$fullname{$ENV{"form.$uname:$udom:$partid:submitted_by"}}.'</a><br />';   $$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a><br />';
     $request->print($submitby);      $request->print($submitby);
     next;      next;
  }   }
  my $responsetype = $responseType->{$partid}->{$respid};   my $responsetype = $responseType->{$partid}->{$respid};
  if (!exists($record{"resource.$partid.$respid.submission"})) {   if (!exists($record{"resource.$partid.$respid.submission"})) {
     $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '.                      $lastsubonly.="\n".'<div class="LC_grade_submission_part">'.
  $display_part.' <font color="#999999">( ID '.$respid.                          '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
  ' )</font>&nbsp; &nbsp;'.                          ' <span class="LC_internal_info">'.
  '<font color="red">Nothing submitted - no attempts</font><br /><br />';                          '('.&mt('Response ID: [_1]',$respid).')'.
                           '</span>&nbsp; &nbsp;'.
    '<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br /><br /></div>';
     next;      next;
  }   }
  foreach (@$string) {   foreach my $submission (@$string) {
     my ($partid,$respid) = /^resource\.([^\.]*)\.([^\.]*)\.submission/;      my ($partid,$respid) = ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
     if ($part ne ($partid.'_'.$respid)) { next; }      if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }
     my ($ressub,$subval) = split(/:/,$_,2);      my ($ressub,$hide,$subval) = split(/:/,$submission,3);
     # Similarity check      # Similarity check
     my $similar='';      my $similar='';
     if($ENV{'form.checkPlag'}){      if($env{'form.checkPlag'}){
  my ($oname,$odom,$ocrsid,$oessay,$osim)=   my ($oname,$odom,$ocrsid,$oessay,$osim)=
     &most_similar($uname,$udom,$subval);      &most_similar($uname,$udom,$subval,\%old_essays);
  if ($osim) {   if ($osim) {
     $osim=int($osim*100.0);      $osim=int($osim*100.0);
     $similar="<hr /><h3><font color=\"#FF0000\">Essay".      my %old_course_desc = 
  " is $osim% similar to an essay by ".   &Apache::lonnet::coursedescription($ocrsid,
  &Apache::loncommon::plainname($oname,$odom).     {'one_time' => 1});
  '</font></h3><blockquote><i>'.  
  &keywords_highlight($oessay).                              if ($hide) {
  '</i></blockquote><hr />';                                  $similar='<hr /><span class="LC_warning">'.&mt("Essay was found to be similar to another essay submitted for this assignment.").'<br />'.
                                            &mt('As the current submission is for an anonymous survey, no other details are available.').'</span><hr />';
                               } else {
           $similar="<hr /><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><blockquote><i>'.
       &keywords_highlight($oessay).
       '</i></blockquote><hr />';
                               }
  }   }
     }      }
     my $order=&get_order($partid,$respid,$symb,$uname,$udom);      my $order=&get_order($partid,$respid,$symb,$uname,$udom);
     if ($ENV{'form.lastSub'} eq 'lastonly' ||       if ($env{'form.lastSub'} eq 'lastonly' || 
  ($ENV{'form.lastSub'} eq 'hdgrade' &&    ($env{'form.lastSub'} eq 'hdgrade' && 
  $$handgrade{$part} eq 'yes')) {   $$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) {
  my $display_part=&get_display_part($partid,$url,$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.' <font color="#999999">( ID '.$respid.                              '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
     ' )</font>&nbsp; &nbsp;';                              ' <span class="LC_internal_info">'.
  if ($record{"resource.$partid.$respid.uploadedurl"}) {                              '('.&mt('Response ID: [_1]',$respid).')'.
     &Apache::lonnet::allowuploaded('/adm/grades',                              '</span>&nbsp; &nbsp;';
       $record{"resource.$partid.$respid.uploadedurl"});   my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
     $lastsubonly.='<a href="'.$record{"resource.$partid.$respid.uploadedurl"}.'" target="lonGRDs"><img src="/adm/lonIcons/unknown.gif" border=0"> File uploaded by student</a> <font color="red" size="1">Like all files provided by users, this file may contain virusses</font><br />';   if (@$files) {
                               if ($hide) {
                                   $lastsubonly.='<br />'.&mt('[quant,_1,file] uploaded to this anonymous survey',scalar(@{$files}));
                               } else {
                                   $lastsubonly.='<br /><span class="LC_warning">'.&mt('Like all files provided by users, this file may contain viruses').'</span><br />';
                                   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" /> '.$file.'</a>';
                                   }
                               }
       $lastsubonly.='<br />';
  }   }
  $lastsubonly.='<b>Submitted Answer: </b>'.                          if ($hide) {
     &cleanRecord($subval,$responsetype,$symb,$partid,                              $lastsubonly.='<b>'.&mt('Anonymous Survey').'</b>'; 
  $respid,\%record,$order);                          } else {
       $lastsubonly.='<b>'.&mt('Submitted Answer:').' </b>'.
           &cleanRecord($subval,$responsetype,$symb,$partid,
        $respid,\%record,$order,undef,$uname,$udom);
                           }
  if ($similar) {$lastsubonly.="<br /><br />$similar\n";}   if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
    $lastsubonly.='</div>';
     }      }
  }   }
     }      }
       $lastsubonly.='</div>'."\n"; # End: LC_grade_submissions_body
  }   }
  $lastsubonly.='</td></tr><tr bgcolor="#ffffff"><td>'."\n";  
  $request->print($lastsubonly);   $request->print($lastsubonly);
     } elsif ($ENV{'form.lastSub'} eq 'datesub') {     } elsif ($env{'form.lastSub'} eq 'datesub') {
  my (undef,$responseType,undef,$parts) = &showResourceInfo($url);          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)$/) {      } elsif ($env{'form.lastSub'} =~ /^(last|all)$/) {
  $request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,   $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'));
     }      }
   
     $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))) {  # FIXME: the logic seems off here. Why show the grade button if you cannot grade?
       if (!&canmodify($usec)) {
  my $toGrade.='<input type="button" value="Grade Student" '.   my $toGrade.='<input type="button" value="Grade Student" '.
     'onClick="javascript:checksubmit(this.form,\'Grade Student\',\''      'onclick="javascript:checksubmit(this.form,\'Grade Student\',\''
     .$counter.'\');" TARGET=_self> &nbsp;'."\n" if (&canmodify($usec));      .$counter.'\');" target="_self" /> &nbsp;'."\n" if (&canmodify($usec));
  $toGrade.='</td></tr></table></td></tr></table>'."\n";   $toGrade.='</div>'."\n";
  if (($ENV{'form.command'} eq 'submission') ||   
     ($ENV{'form.command'} eq 'processGroup' && $counter == $total)) {  
     $toGrade.='</form>'.&show_grading_menu_form($symb,$url)   
  }  
  $request->print($toGrade);   $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') {
  my ($lastname,$givenn) = split(/,/,$ENV{'form.fullname'});      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 $msgfor = $givenn.' '.$lastname;   my $msgfor = $givenn.' '.$lastname;
  if (scalar(@col_fullnames) > 0) {   if (scalar(@$col_fullnames) > 0) {
     my $lastone = pop @col_fullnames;      my $lastone = pop(@$col_fullnames);
     $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">'.
     'Compose Message to student'.(scalar(@col_fullnames) >= 1 ? 's' : '').'</a> &nbsp;'.      &mt('Compose message to student').(scalar(@$col_fullnames) >= 1 ? 's' : '').'</a><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" name="mailicon'.$counter.'" />'."\n".
     '<br />&nbsp;(Message will be sent when you click on Save & Next below.)'."\n"       '<br />&nbsp;('.
     if ($ENV{'form.handgrade'} eq 'yes');      &mt('Message will be sent when you click on Save &amp; Next below.').")\n";
    $result.='</div></div>';
  $request->print($result);   $request->print($result);
     }      }
   
     my %seen = ();      my %seen = ();
     my @partlist;      my @partlist;
     my @gradePartRespid;      my @gradePartRespid;
     for (sort keys(%$handgrade)) {      my @part_response_id = &flatten_responseType($responseType);
  my ($partid,$respid) = split(/_/);      $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) {
       my ($partid,$respid) = @{ $part_response_id };
    my $part_resp = join('_',@{ $part_response_id });
  next if ($seen{$partid} > 0);   next if ($seen{$partid} > 0);
  $seen{$partid}++;   $seen{$partid}++;
  next if ($$handgrade{$_} =~ /:no$/ && $ENV{'form.lastSub'} =~ /^(hdgrade)$/);   next if ($$handgrade{$part_resp} ne 'yes' 
  push @partlist,$partid;   && $env{'form.lastSub'} eq 'hdgrade');
  push @gradePartRespid,$partid.'.'.$respid;   push(@partlist,$partid);
    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 1777  KEYWORDS Line 2289  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
   
       $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>'.
     '<option>3</option><option>5</option>'.      '<option>3</option><option>5</option>'.
     '<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="on">$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">'.
  $endform.='</td><tr></table></form>';                    &mt('(Next and Previous (student) do not save the scores.)').
  $endform.=&show_grading_menu_form($symb,$url);                    '</span>'."\n" ;
           $endform.="<input type='hidden' value='".&get_increment().
               "' name='increment' />";
    $endform.='</td></tr></table></form>';
  $request->print($endform);   $request->print($endform);
     }      }
     return '';      return '';
 }  }
   
   sub check_collaborators {
       my ($symb,$uname,$udom,$record,$handgrade,$counter) = @_;
       my ($result,@col_fullnames);
       my ($classlist,undef,$fullname) = &getclasslist('all','0');
       foreach my $part (keys(%$handgrade)) {
    my $ncol = &Apache::lonnet::EXT('resource.'.$part.
    '.maxcollaborators',
    $symb,$udom,$uname);
    next if ($ncol <= 0);
    $part =~ s/\_/\./g;
    next if ($record->{'resource.'.$part.'.collaborators'} eq '');
    my (@good_collaborators, @bad_collaborators);
    foreach my $possible_collaborator
       (split(/,?\s+/,$record->{'resource.'.$part.'.collaborators'})) { 
       $possible_collaborator =~ s/[\$\^\(\)]//g;
       next if ($possible_collaborator eq '');
       my ($co_name,$co_dom) = split(/\@|:/,$possible_collaborator);
       $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i);
       next if ($co_name eq $uname && $co_dom eq $udom);
       # Doing this grep allows 'fuzzy' specification
       my @matches = grep(/^\Q$co_name\E:\Q$co_dom\E$/i, 
          keys(%$classlist));
       if (! scalar(@matches)) {
    push(@bad_collaborators, $possible_collaborator);
       } else {
    push(@good_collaborators, @matches);
       }
    }
    if (scalar(@good_collaborators) != 0) {
       $result.='<br />'.&mt('Collaborators: ');
       foreach my $name (@good_collaborators) {
    my ($lastname,$givenn) = split(/,/,$$fullname{$name});
    push(@col_fullnames, $givenn.' '.$lastname);
    $result.=$fullname->{$name}.'&nbsp; &nbsp; &nbsp;';
       }
       $result.='<br />'."\n";
       my ($part)=split(/\./,$part);
       $result.='<input type="hidden" name="collaborator'.$counter.
    '" value="'.$part.':'.(join ':',@good_collaborators).'" />'.
    "\n";
    }
    if (scalar(@bad_collaborators) > 0) {
       $result.='<div class="LC_warning">';
       $result.=&mt('This student has submitted [quant,_1,invalid collaborator]: [_2]',scalar(@bad_collaborators),join(', ',@bad_collaborators));
       $result .= '</div>';
    }         
    if (scalar(@bad_collaborators > $ncol)) {
       $result .= '<div class="LC_warning">';
       $result .= &mt('This student has submitted too many '.
    'collaborators.  Maximum is [_1].',$ncol);
       $result .= '</div>';
    }
       }
       return ($result,$fullname,\@col_fullnames);
   }
   
 #--- 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)=@_;
     my (@string,$timestamp);      my (@string,$timestamp,%lasthidden);
     if ($$returnhash{'version'}) {      if ($$returnhash{'version'}) {
  my %lasthash=();   my %lasthash=();
  my ($version);   my ($version);
  for ($version=1;$version<=$$returnhash{'version'};$version++) {   for ($version=1;$version<=$$returnhash{'version'};$version++) {
     foreach (sort(split(/\:/,$$returnhash{$version.':keys'}))) {      foreach my $key (sort(split(/\:/,
  $lasthash{$_}=$$returnhash{$version.':'.$_};   $$returnhash{$version.':keys'}))) {
    $timestamp = scalar(localtime($$returnhash{$version.':timestamp'}));   $lasthash{$key}=$$returnhash{$version.':'.$key};
     }   $timestamp = 
  }      &Apache::lonlocal::locallocaltime($$returnhash{$version.':timestamp'});
  foreach ((keys %lasthash)) {      }
     if ($_ =~ /\.submission$/) {   }
  my ($partid,$foo) = split(/submission$/,$_);          my %typeparts;
  my $draft  = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ?          my $showsurv = 
     '<font color="red">Draft Copy</font> ' : '';              &Apache::lonnet::allowed('vas',$env{'request.course.id'});
  push @string, (join(':',$_,$draft.$lasthash{$_}));          foreach my $key (sort(keys(%lasthash))) {
     }              if ($key =~ /\.type$/) {
                   if (($lasthash{$key} eq 'anonsurvey') || 
                       ($lasthash{$key} eq 'anonsurveycred')) {
                       my ($ign,@parts) = split(/\./,$key);
                       pop(@parts);
                       unless ($showsurv) {
                           my $id = join(',',@parts);
                           $typeparts{$ign.'.'.$id} = $lasthash{$key};
                       }
                       delete($lasthash{$key});
                   }
               }
           }
           my @hidden = keys(%typeparts);
    foreach my $key (keys(%lasthash)) {
       next if ($key !~ /\.submission$/);
               my $hide;
               if (@hidden) {
                   foreach my $id (@hidden) {
                       if ($key =~ /^\Q$id\E/) {
                           $hide = 1;
                           last;
                       }
                   }
               }
       my ($partid,$foo) = split(/submission$/,$key);
       my $draft  = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ?
    '<span class="LC_warning">Draft Copy</span> ' : '';
       push(@string, join(':', $key, $hide, $draft.$lasthash{$key}));
  }   }
     }      }
     @string = $string[0] eq '' ? '<font color="red">Nothing submitted - no attempts.</font>' : @string;      if (!@string) {
     return \@string,\$timestamp;   $string[0] =
       '<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span>';
       }
       return (\@string,\$timestamp);
 }  }
   
 #--- High light keywords, with style choosen by user.  #--- High light keywords, with style choosen by user.
 sub keywords_highlight {  sub keywords_highlight {
     my $string    = shift;      my $string    = shift;
     my $size      = $ENV{'form.kwsize'} eq '0' ? '' : 'size='.$ENV{'form.kwsize'};      my $size      = $env{'form.kwsize'} eq '0' ? '' : 'size='.$env{'form.kwsize'};
     my $styleon   = $ENV{'form.kwstyle'} eq ''  ? '' : $ENV{'form.kwstyle'};      my $styleon   = $env{'form.kwstyle'} eq ''  ? '' : $env{'form.kwstyle'};
     (my $styleoff = $styleon) =~ s/\</\<\//;      (my $styleoff = $styleon) =~ s/\</\<\//;
     my @keylist   = split(/[,\s+]/,$ENV{'form.keywords'});      my @keylist   = split(/[,\s+]/,$env{'form.keywords'});
     foreach (@keylist) {      foreach my $keyword (@keylist) {
  $string =~ s/\b\Q$_\E(\b|\.)/<font color\=$ENV{'form.kwclr'} $size\>$styleon$_$styleoff<\/font>/gi;   $string =~ s/\b\Q$keyword\E(\b|\.)/<font color\=$env{'form.kwclr'} $size\>$styleon$keyword$styleoff<\/font>/gi;
     }      }
     return $string;      return $string;
 }  }
   
 #--- Called from submission routine  #--- Called from submission routine
 sub processHandGrade {  sub processHandGrade {
     my ($request) = shift;      my ($request,$symb) = @_;
     my $url    = $ENV{'form.url'};      my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
     my $symb   = $ENV{'form.symb'};      my $button = $env{'form.gradeOpt'};
     my $button = $ENV{'form.gradeOpt'};      my $ngrade = $env{'form.NCT'};
     my $ngrade = $ENV{'form.NCT'};      my $ntstu  = $env{'form.NTSTU'};
     my $ntstu  = $ENV{'form.NTSTU'};      my $cdom   = $env{'course.'.$env{'request.course.id'}.'.domain'};
       my $cnum   = $env{'course.'.$env{'request.course.id'}.'.num'};
   
     if ($button eq 'Save & Next') {      if ($button eq 'Save & Next') {
  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,$url,$symb,$uname,$udom,$ctr);      my ($errorflag,$pts,$wgt) = &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("<font color=\"red\">Not allowed to modify grades for $uname:$udom</font>");   $request->print("<span class=\"LC_warning\">Not allowed to modify grades for $uname:$udom</span>");
  $ctr++;   $ctr++;
  next;   next;
     }      }
     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 ($feedurl,$showsymb) =
    &get_feedurl_and_symb($symb,$uname,$udom);
       my $messagetail;
     if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) {      if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) {
  $subject = $ENV{'form.msgsub'} if ($includemsg =~ /^msgsub/);   $subject = $env{'form.msgsub'} if ($includemsg =~ /msgsub/);
    unless ($subject=~/\w/) { $subject=&mt('Grading Feedback'); }
    $subject.=' ['.$restitle.']';
  my (@msgnum) = split(/,/,$includemsg);   my (@msgnum) = split(/,/,$includemsg);
  foreach (@msgnum) {   foreach (@msgnum) {
     $message.=$ENV{'form.'.$_} if ($_ =~ /savemsg|newmsg/ && $_ ne '');      $message.=$env{'form.'.$_} if ($_ =~ /savemsg|newmsg/ && $_ ne '');
  }   }
  $message =&Apache::lonfeedback::clear_out_html($message);   $message =&Apache::lonfeedback::clear_out_html($message);
  $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt;   if ($env{'form.withgrades'.$ctr}) {
  $message.=" for <a href=\"".      $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt;
     &Apache::lonnet::clutter($url).      $messagetail = " for <a href=\"".
     "?symb=$symb\">$ENV{'form.probTitle'}</a>";                     $feedurl."?symb=$showsymb\">$restitle</a>";
  $msgstatus = &Apache::lonmsg::user_normal_msg ($uname,$udom,   }
        $ENV{'form.msgsub'},$message);   $msgstatus = 
                       &Apache::lonmsg::user_normal_msg($uname,$udom,$subject,
        $message.$messagetail,
                                                        undef,$feedurl,undef,
                                                        undef,undef,$showsymb,
                                                        $restitle);
    $request->print('<br />'.&mt('Sending message to [_1]',$uname.':'.$udom).': '.
    $msgstatus);
     }      }
     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");
  foreach my $collabstr (@collabstrs) {   foreach my $collabstr (@collabstrs) {
     my ($part,@collaborators) = split(/:/,$collabstr);      my ($part,@collaborators) = split(/:/,$collabstr);
     foreach (@collaborators) {      foreach my $collaborator (@collaborators) {
  my ($errorflag,$pts,$wgt) =    my ($errorflag,$pts,$wgt) = 
     &saveHandGrade($request,$url,$symb,$_,$udom,$ctr,      &saveHandGrade($request,$symb,$collaborator,$udom,$ctr,
    $ENV{'form.unamedom'.$ctr},$part);     $env{'form.unamedom'.$ctr},$part);
  if ($errorflag eq 'not_allowed') {   if ($errorflag eq 'not_allowed') {
     $request->print("<font color=\"red\">Not allowed to modify grades for $_:$udom</font>");      $request->print("<span class=\"LC_error\">".&mt('Not allowed to modify grades for [_1]',"$collaborator:$udom")."</span>");
     next;      next;
  } else {   } elsif ($message ne '') {
     if ($message ne '') {      my ($baseurl,$showsymb) = 
  $msgstatus = &Apache::lonmsg::user_normal_msg($_,$udom,$ENV{'form.msgsub'},$message);   &get_feedurl_and_symb($symb,$collaborator,
         $udom);
       if ($env{'form.withgrades'.$ctr}) {
    $messagetail = " for <a href=\"".
                                       $baseurl."?symb=$showsymb\">$restitle</a>";
     }      }
           $msgstatus = 
    &Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
  }   }
     }      }
  }   }
Line 1905  sub processHandGrade { Line 2532  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 = ();
  $ENV{'form.keywords'}           =~ s/,\s{0,}|\s+/ /g;   $env{'form.keywords'}           =~ s/,\s{0,}|\s+/ /g;
  $ENV{'form.keywords'}           =~ s/^\s+|\s+$//;   $env{'form.keywords'}           =~ s/^\s+|\s+$//;
  my (@keywords) = sort(split(/\s+/,$ENV{'form.keywords'}));   my (@keywords) = sort(split(/\s+/,$env{'form.keywords'}));
  $ENV{'form.keywords'} = join(' ',@keywords);   $env{'form.keywords'} = join(' ',@keywords);
  $keyhash{$symb.'_keywords'}     = $ENV{'form.keywords'};   $keyhash{$symb.'_keywords'}     = $env{'form.keywords'};
  $keyhash{$symb.'_subject'}      = $ENV{'form.msgsub'};   $keyhash{$symb.'_subject'}      = $env{'form.msgsub'};
  $keyhash{$loginuser.'_kwclr'}   = $ENV{'form.kwclr'};   $keyhash{$loginuser.'_kwclr'}   = $env{'form.kwclr'};
  $keyhash{$loginuser.'_kwsize'}  = $ENV{'form.kwsize'};   $keyhash{$loginuser.'_kwsize'}  = $env{'form.kwsize'};
  $keyhash{$loginuser.'_kwstyle'} = $ENV{'form.kwstyle'};   $keyhash{$loginuser.'_kwstyle'} = $env{'form.kwstyle'};
   
  # message center - Order of message gets changed. Blank line is eliminated.   # message center - Order of message gets changed. Blank line is eliminated.
  # New messages are saved in ENV for the next student.   # New messages are saved in env for the next student.
  # All messages are saved in nohist_handgrade.db   # All messages are saved in nohist_handgrade.db
  my ($ctr,$idx) = (1,1);   my ($ctr,$idx) = (1,1);
  while ($ctr <= $ENV{'form.savemsgN'}) {   while ($ctr <= $env{'form.savemsgN'}) {
     if ($ENV{'form.savemsg'.$ctr} ne '') {      if ($env{'form.savemsg'.$ctr} ne '') {
  $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.savemsg'.$ctr};   $keyhash{$symb.'_savemsg'.$idx} = $env{'form.savemsg'.$ctr};
  $idx++;   $idx++;
     }      }
     $ctr++;      $ctr++;
  }   }
  $ctr = 0;   $ctr = 0;
  while ($ctr < $ngrade) {   while ($ctr < $ngrade) {
     if ($ENV{'form.newmsg'.$ctr} ne '') {      if ($env{'form.newmsg'.$ctr} ne '') {
  $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.newmsg'.$ctr};   $keyhash{$symb.'_savemsg'.$idx} = $env{'form.newmsg'.$ctr};
  $ENV{'form.savemsg'.$idx} = $ENV{'form.newmsg'.$ctr};   $env{'form.savemsg'.$idx} = $env{'form.newmsg'.$ctr};
  $idx++;   $idx++;
     }      }
     $ctr++;      $ctr++;
  }   }
  $ENV{'form.savemsgN'} = --$idx;   $env{'form.savemsgN'} = --$idx;
  $keyhash{$symb.'_savemsgN'} = $ENV{'form.savemsgN'};   $keyhash{$symb.'_savemsgN'} = $env{'form.savemsgN'};
  my $putresult = &Apache::lonnet::put   my $putresult = &Apache::lonnet::put
     ('nohist_handgrade',\%keyhash,      ('nohist_handgrade',\%keyhash,$cdom,$cnum);
      $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},  
      $ENV{'course.'.$ENV{'request.course.id'}.'.num'});  
     }      }
     # Called by Save & Refresh from Highlight Attribute Window      # Called by Save & Refresh from Highlight Attribute Window
     my (undef,undef,$fullname) = &getclasslist($ENV{'form.section'},'1');      my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
     if ($ENV{'form.refresh'} eq 'on') {      if ($env{'form.refresh'} eq 'on') {
  my ($ctr,$total) = (0,0);   my ($ctr,$total) = (0,0);
  while ($ctr < $ngrade) {   while ($ctr < $ngrade) {
     $total++ if  $ENV{'form.unamedom'.$ctr} ne '';      $total++ if  $env{'form.unamedom'.$ctr} ne '';
     $ctr++;      $ctr++;
  }   }
  $ENV{'form.NTSTU'}=$ngrade;   $env{'form.NTSTU'}=$ngrade;
  $ctr = 0;   $ctr = 0;
  while ($ctr < $total) {   while ($ctr < $total) {
     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  # Go directly to grade student - from submission or link from chart page
   # FIXME: looks like reading off the button label!
     if ($button eq 'Grade Student') {      if ($button eq 'Grade Student') {
  (undef,undef,$ENV{'form.handgrade'},undef,undef) = &showResourceInfo($url);   my $processUser = $env{'form.unamedom'.$env{'form.studentNo'}};
  my $processUser = $ENV{'form.unamedom'.$ENV{'form.studentNo'}};   ($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,0,0,$symb);
  &submission($request,0,0);  
  return '';   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)};
     my $ctr = 2;      my $ctr = 2;
     while ($laststu eq '') {      while ($laststu eq '') {
  $laststu  = $ENV{'form.unamedom'.($ngrade-$ctr)};   $laststu  = $env{'form.unamedom'.($ngrade-$ctr)};
  $ctr++;   $ctr++;
  $laststu = $firststu if ($ctr > $ngrade);   $laststu = $firststu if ($ctr > $ngrade);
     }      }
   
     my (@parsedlist,@nextlist);      my (@parsedlist,@nextlist);
     my ($nextflg) = 0;      my ($nextflg) = 0;
     foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {      foreach my $item (sort 
        {
    if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
        return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
    }
    return $a cmp $b;
        } (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($url);      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);
   
    if ($submitonly eq 'queued') {
       my %queue_status = 
    &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
    $udom,$uname);
       next if (!defined($queue_status{'gradingqueue'}));
    }
   
  if ($submitonly =~ /^(yes|graded|incorrect)$/) {   if ($submitonly =~ /^(yes|graded|incorrect)$/) {
 #    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 %status=&student_gradeStatus($url,$symb,$udom,$uname,$partlist);      my %status=&student_gradeStatus($symb,$udom,$uname,$partlist);
     my $submitted = 0;      my $submitted = 0;
     my $graded = 1;      my $ungraded = 0;
     foreach (keys(%status)) {      my $incorrect = 0;
  $submitted = 1 if ($status{$_} ne 'nothing');      foreach my $item (keys(%status)) {
  $graded = 0 if ($status{$_} =~ /^correct/);   $submitted = 1 if ($status{$item} ne 'nothing');
  my ($foo,$partid,$foo1) = split(/\./,$_);   $ungraded = 1 if ($status{$item} =~ /^ungraded/);
    $incorrect = 1 if ($status{$item} =~ /^incorrect/);
    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 2020  sub processHandGrade { Line 2669  sub processHandGrade {
     next if (!$submitted && ($submitonly eq 'yes' ||      next if (!$submitted && ($submitonly eq 'yes' ||
      $submitonly eq 'incorrect' ||       $submitonly eq 'incorrect' ||
      $submitonly eq 'graded'));       $submitonly eq 'graded'));
     next if (!$graded && ($submitonly eq 'graded' ||      next if (!$ungraded && ($submitonly eq 'graded'));
   $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 2031  sub processHandGrade { Line 2680  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><font color="red">LON-CAPA User Message</font></h3><br />'."\n";   my $the_end = '<h3><span class="LC_info">'.&mt('LON-CAPA User Message').'</span></h3><br />'."\n";
  $the_end.='<b>Message: </b> No more students for this section or class.<br /><br />'."\n";   $the_end.=&mt('<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.=&mt('Click on the button below to return to the grading menu.').'<br /><br />'."\n";
  $the_end.=&show_grading_menu_form ($symb,$url);  
  $request->print($the_end);   $request->print($the_end);
     }      }
     return '';      return '';
Line 2051  sub processHandGrade { Line 2699  sub processHandGrade {
   
 #---- Save the score and award for each student, if changed  #---- Save the score and award for each student, if changed
 sub saveHandGrade {  sub saveHandGrade {
     my ($request,$url,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_;      my ($request,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_;
       my @version_parts;
     my $usec = &Apache::lonnet::getsection($domain,$stuname,      my $usec = &Apache::lonnet::getsection($domain,$stuname,
    $ENV{'request.course.id'});     $env{'request.course.id'});
     if (!&canmodify($usec)) { return('not_allowed'); }      if (!&canmodify($usec)) { return('not_allowed'); }
     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 %newrecord  = ();      my %newrecord  = ();
     my ($pts,$wgt) = ('','');      my ($pts,$wgt) = ('','');
     foreach (split(/:/,$ENV{'form.partlist'.$newflg})) {      my %aggregate = ();
  #collaborator may vary for different parts      my $aggregateflag = 0;
  if ($submitter && $_ ne $part) { next; }      my @parts = split(/:/,$env{'form.partlist'.$newflg});
  my $dropMenu = $ENV{'form.GD_SEL'.$newflg.'_'.$_};      foreach my $new_part (@parts) {
    #collaborator ($submi may vary for different parts
    if ($submitter && $new_part ne $part) { next; }
    my $dropMenu = $env{'form.GD_SEL'.$newflg.'_'.$new_part};
  if ($dropMenu eq 'excused') {   if ($dropMenu eq 'excused') {
     if ($record{'resource.'.$_.'.solved'} ne 'excused') {      if ($record{'resource.'.$new_part.'.solved'} ne 'excused') {
  $newrecord{'resource.'.$_.'.solved'} = 'excused';   $newrecord{'resource.'.$new_part.'.solved'} = 'excused';
  if (exists($record{'resource.'.$_.'.awarded'})) {   if (exists($record{'resource.'.$new_part.'.awarded'})) {
     $newrecord{'resource.'.$_.'.awarded'} = '';      $newrecord{'resource.'.$new_part.'.awarded'} = '';
  }   }
     $newrecord{'resource.'.$_.'.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}";          $newrecord{'resource.'.$new_part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
     }      }
  } elsif ($dropMenu eq 'reset status'   } elsif ($dropMenu eq 'reset status'
  && exists($record{'resource.'.$_.'.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$_\E\./) { $newrecord{$key} = ''; }   if ($key=~/^resource\.\Q$new_part\E\./) { $newrecord{$key} = ''; }
     }      }
     $newrecord{'resource.'.$_.'.regrader'}=      $newrecord{'resource.'.$new_part.'.regrader'}=
  "$ENV{'user.name'}:$ENV{'user.domain'}";   "$env{'user.name'}:$env{'user.domain'}";
               my $totaltries = $record{'resource.'.$part.'.tries'};
   
               my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
          [$new_part]);
               my $aggtries =$totaltries;
               if ($last_resets{$new_part}) {
                   $aggtries = &get_num_tries(\%record,$last_resets{$new_part},
      $new_part);
               }
   
               my $solvedstatus = $record{'resource.'.$new_part.'.solved'};
               if ($aggtries > 0) {
                   &decrement_aggs($symb,$new_part,\%aggregate,$aggtries,$totaltries,$solvedstatus);
                   $aggregateflag = 1;
               }
  } elsif ($dropMenu eq '') {   } elsif ($dropMenu eq '') {
     $pts = ($ENV{'form.GD_BOX'.$newflg.'_'.$_} ne '' ?       $pts = ($env{'form.GD_BOX'.$newflg.'_'.$new_part} ne '' ? 
     $ENV{'form.GD_BOX'.$newflg.'_'.$_} :       $env{'form.GD_BOX'.$newflg.'_'.$new_part} : 
     $ENV{'form.RADVAL'.$newflg.'_'.$_});      $env{'form.RADVAL'.$newflg.'_'.$new_part});
     if ($pts eq '' && $ENV{'form.GD_SEL'.$newflg.'_'.$_} eq '') {      if ($pts eq '' && $env{'form.GD_SEL'.$newflg.'_'.$new_part} eq '') {
  next;   next;
     }      }
     $wgt = $ENV{'form.WGT'.$newflg.'_'.$_} eq '' ? 1 :       $wgt = $env{'form.WGT'.$newflg.'_'.$new_part} eq '' ? 1 : 
  $ENV{'form.WGT'.$newflg.'_'.$_};   $env{'form.WGT'.$newflg.'_'.$new_part};
     my $partial= $pts/$wgt;      my $partial= $pts/$wgt;
     if ($partial eq $record{'resource.'.$_.'.awarded'}) {      if ($partial eq $record{'resource.'.$new_part.'.awarded'}) {
  #do not update score for part if not changed.   #do not update score for part if not changed.
                   &handback_files($request,$symb,$stuname,$domain,$newflg,$new_part,\%newrecord);
  next;   next;
       } else {
           push(@parts_graded,$new_part);
     }      }
     if ($record{'resource.'.$_.'.awarded'} ne $partial) {      if ($record{'resource.'.$new_part.'.awarded'} ne $partial) {
  $newrecord{'resource.'.$_.'.awarded'}  = $partial;   $newrecord{'resource.'.$new_part.'.awarded'}  = $partial;
     }      }
     my $reckey = 'resource.'.$_.'.solved';      my $reckey = 'resource.'.$new_part.'.solved';
     if ($partial == 0) {      if ($partial == 0) {
  if ($record{$reckey} ne 'incorrect_by_override') {   if ($record{$reckey} ne 'incorrect_by_override') {
     $newrecord{$reckey} = 'incorrect_by_override';      $newrecord{$reckey} = 'incorrect_by_override';
Line 2105  sub saveHandGrade { Line 2776  sub saveHandGrade {
  }   }
     }          }    
     if ($submitter &&       if ($submitter && 
  ($record{'resource.'.$_.'.submitted_by'} ne $submitter)) {   ($record{'resource.'.$new_part.'.submitted_by'} ne $submitter)) {
  $newrecord{'resource.'.$_.'.submitted_by'} = $submitter;   $newrecord{'resource.'.$new_part.'.submitted_by'} = $submitter;
     }      }
     $newrecord{'resource.'.$_.'.regrader'}=      $newrecord{'resource.'.$new_part.'.regrader'}=
  "$ENV{'user.name'}:$ENV{'user.domain'}";   "$env{'user.name'}:$env{'user.domain'}";
  }   }
     }   # unless problem has been graded, set flag to version the submitted files
     if (scalar(keys(%newrecord)) > 0) {   unless ($record{'resource.'.$new_part.'.solved'} =~ /^correct_/  || 
           $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override' ||
           $dropMenu eq 'reset status')
      {
       push(@version_parts,$new_part);
    }
       }
       my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
       my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
   
       if (%newrecord) {
           if (@version_parts) {
               my @changed_keys = &version_portfiles(\%record, \@parts_graded, 
                                   $env{'request.course.id'}, $symb, $domain, $stuname, \@version_parts);
       @newrecord{@changed_keys} = @record{@changed_keys};
       foreach my $new_part (@version_parts) {
    &handback_files($request,$symb,$stuname,$domain,$newflg,
    $new_part,\%newrecord);
       }
           }
  &Apache::lonnet::cstore(\%newrecord,$symb,   &Apache::lonnet::cstore(\%newrecord,$symb,
  $ENV{'request.course.id'},$domain,$stuname);   $env{'request.course.id'},$domain,$stuname);
    &check_and_remove_from_queue(\@parts,\%record,\%newrecord,$symb,
        $cdom,$cnum,$domain,$stuname);
       }
       if ($aggregateflag) {
           &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
         $cdom,$cnum);
       }
       return ('',$pts,$wgt);
   }
   
   sub check_and_remove_from_queue {
       my ($parts,$record,$newrecord,$symb,$cdom,$cnum,$domain,$stuname) = @_;
       my @ungraded_parts;
       foreach my $part (@{$parts}) {
    if (    $record->{   'resource.'.$part.'.awarded'} eq ''
        && $record->{   'resource.'.$part.'.solved' } ne 'excused'
        && $newrecord->{'resource.'.$part.'.awarded'} eq ''
        && $newrecord->{'resource.'.$part.'.solved' } ne 'excused'
    ) {
       push(@ungraded_parts, $part);
    }
       }
       if ( !@ungraded_parts ) {
    &Apache::bridgetask::remove_from_queue('gradingqueue',$symb,$cdom,
          $cnum,$domain,$stuname);
       }
   }
   
   sub handback_files {
       my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_;
       my $portfolio_root = '/userfiles/portfolio';
       my $res_error;
       my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
       if ($res_error) {
           $request->print('<br />'.&navmap_errormsg().'<br />');
           return;
       }
       my @part_response_id = &flatten_responseType($responseType);
       foreach my $part_response_id (@part_response_id) {
       my ($part_id,$resp_id) = @{ $part_response_id };
    my $part_resp = join('_',@{ $part_response_id });
               if (($env{'form.'.$newflg.'_'.$part_resp.'_returndoc1'}) && ($new_part == $part_id)) {
                   # if multiple files are uploaded names will be 'returndoc2','returndoc3'
                   my $file_counter = 1;
    my $file_msg;
                   while ($env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter}) {
                       my $fname=$env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'.filename'};
                       my ($directory,$answer_file) = 
                           ($env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$file_counter} =~ /^(.*?)([^\/]*)$/);
                       my ($answer_name,$answer_ver,$answer_ext) =
           &file_name_version_ext($answer_file);
       my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/);
                       my $getpropath = 1;
       my @dir_list = &Apache::lonnet::dirlist($portfolio_root.$portfolio_path,$domain,$stuname,$getpropath);
       my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
                       # fix file name
                       my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/);
                       my $result=&Apache::lonnet::finishuserfileupload($stuname,$domain,
                                              $newflg.'_'.$part_resp.'_returndoc'.$file_counter,
                                              $save_file_name);
                       if ($result !~ m|^/uploaded/|) {
                           $request->print('<br /><span class="LC_error">'.
                               &mt('An error occurred ([_1]) while trying to upload [_2].',
                                   $result,$newflg.'_'.$part_resp.'_returndoc'.$file_counter).
                                           '</span>');
                       } else {
                           # mark the file as read only
                           my @files = ($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"})) {
       $$newrecord{"resource.$new_part.$resp_id.handback"}.=',';
    }
                           $$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 />";
   
                       }
                       $request->print("<br />".$fname." will be the uploaded file name");
                       $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);
               }
           }
       return;
   }
   
   sub get_feedurl_and_symb {
       my ($symb,$uname,$udom) = @_;
       my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
       $url = &Apache::lonnet::clutter($url);
       my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl',
    $symb,$udom,$uname);
       if ($encrypturl =~ /^yes$/i) {
    &Apache::lonenc::encrypted(\$url,1);
    &Apache::lonenc::encrypted(\$symb,1);
       }
       return ($url,$symb);
   }
   
   sub get_submitted_files {
       my ($udom,$uname,$partid,$respid,$record) = @_;
       my @files;
       if ($$record{"resource.$partid.$respid.portfiles"}) {
           my $file_url = '/uploaded/'.$udom.'/'.$uname.'/portfolio';
           foreach my $file (split(',',$$record{"resource.$partid.$respid.portfiles"})) {
          push(@files,$file_url.$file);
           }
       }
       if ($$record{"resource.$partid.$respid.uploadedurl"}) {
           push(@files,$$record{"resource.$partid.$respid.uploadedurl"});
     }      }
     return '',$pts,$wgt;      return (\@files);
   }
   
   # ----------- Provides number of tries since last reset.
   sub get_num_tries {
       my ($record,$last_reset,$part) = @_;
       my $timestamp = '';
       my $num_tries = 0;
       if ($$record{'version'}) {
           for (my $version=$$record{'version'};$version>=1;$version--) {
               if (exists($$record{$version.':resource.'.$part.'.solved'})) {
                   $timestamp = $$record{$version.':timestamp'};
                   if ($timestamp > $last_reset) {
                       $num_tries ++;
                   } else {
                       last;
                   }
               }
           }
       }
       return $num_tries;
   }
   
   # ----------- Determine decrements required in aggregate totals 
   sub decrement_aggs {
       my ($symb,$part,$aggregate,$aggtries,$totaltries,$solvedstatus) = @_;
       my %decrement = (
                           attempts => 0,
                           users => 0,
                           correct => 0
                       );
       $decrement{'attempts'} = $aggtries;
       if ($solvedstatus =~ /^correct/) {
           $decrement{'correct'} = 1;
       }
       if ($aggtries == $totaltries) {
           $decrement{'users'} = 1;
       }
       foreach my $type (keys(%decrement)) {
           $$aggregate{$symb."\0".$part."\0".$type} = -$decrement{$type};
       }
       return;
   }
   
   # ----------- Determine timestamps for last reset of aggregate totals for parts  
   sub get_last_resets {
       my ($symb,$courseid,$partids) =@_;
       my %last_resets;
       my $cdom = $env{'course.'.$courseid.'.domain'};
       my $cname = $env{'course.'.$courseid.'.num'};
       my @keys;
       foreach my $part (@{$partids}) {
    push(@keys,"$symb\0$part\0resettime");
       }
       my %results=&Apache::lonnet::get('nohist_resourcetracker',\@keys,
        $cdom,$cname);
       foreach my $part (@{$partids}) {
    $last_resets{$part}=$results{"$symb\0$part\0resettime"};
       }
       return %last_resets;
   }
   
   # ----------- Handles creating versions for portfolio files as answers
   sub version_portfiles {
       my ($record, $parts_graded, $courseid, $symb, $domain, $stu_name, $v_flag) = @_;
       my $version_parts = join('|',@$v_flag);
       my @returned_keys;
       my $parts = join('|', @$parts_graded);
       my $portfolio_root = '/userfiles/portfolio';
       foreach my $key (keys(%$record)) {
           my $new_portfiles;
           if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {
               my @versioned_portfiles;
               my @portfiles = split(/\s*,\s*/,$$record{$key});
               foreach my $file (@portfiles) {
                   &Apache::lonnet::unmark_as_readonly($domain,$stu_name,[$symb,$env{'request.course.id'}],$file);
                   my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*)$/);
    my ($answer_name,$answer_ver,$answer_ext) =
       &file_name_version_ext($answer_file);
                   my $getpropath = 1;    
                   my @dir_list = &Apache::lonnet::dirlist($portfolio_root.$directory,$domain,$stu_name,$getpropath);
                   my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
                   my $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);
               push(@returned_keys,$key);
           }
       } 
       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);
 }  }
   
 #--------------------------------------------------------------------------------------  #--------------------------------------------------------------------------------------
Line 2127  sub saveHandGrade { Line 3088  sub saveHandGrade {
 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">      $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 2168  sub viewgrades_js { Line 3129  sub viewgrades_js {
  }   }
  for (i=0;i<document.classgrade.total.value;i++) {   for (i=0;i<document.classgrade.total.value;i++) {
     var user = document.classgrade["ctr"+i].value;      var user = document.classgrade["ctr"+i].value;
       user = user.replace(new RegExp(':', 'g'),"_");
     var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];      var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
     var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;      var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
     var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];      var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
Line 2184  sub viewgrades_js { Line 3146  sub viewgrades_js {
     function writeRadText(partid,weight) {      function writeRadText(partid,weight) {
  var selval   = document.classgrade["SELVAL_"+partid];   var selval   = document.classgrade["SELVAL_"+partid];
  var radioButton = document.classgrade["RADVAL_"+partid];   var radioButton = document.classgrade["RADVAL_"+partid];
           var override = document.classgrade["FORCE_"+partid].checked;
  var textbox = document.classgrade["TEXTVAL_"+partid];   var textbox = document.classgrade["TEXTVAL_"+partid];
  if (selval[1].selected || selval[2].selected) {   if (selval[1].selected || selval[2].selected) {
     for (var i=0; i<radioButton.length; i++) {      for (var i=0; i<radioButton.length; i++) {
Line 2194  sub viewgrades_js { Line 3157  sub viewgrades_js {
   
     for (i=0;i<document.classgrade.total.value;i++) {      for (i=0;i<document.classgrade.total.value;i++) {
  var user = document.classgrade["ctr"+i].value;   var user = document.classgrade["ctr"+i].value;
    user = user.replace(new RegExp(':', 'g'),"_");
  var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];   var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
  var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;   var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
  var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];   var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
  if (saveval != "correct") {   if ((saveval != "correct") || override) {
     scorename.value = "";      scorename.value = "";
     if (selval[1].selected) {      if (selval[1].selected) {
  selname[1].selected = true;   selname[1].selected = true;
Line 2211  sub viewgrades_js { Line 3175  sub viewgrades_js {
  } else {   } else {
     for (i=0;i<document.classgrade.total.value;i++) {      for (i=0;i<document.classgrade.total.value;i++) {
  var user = document.classgrade["ctr"+i].value;   var user = document.classgrade["ctr"+i].value;
    user = user.replace(new RegExp(':', 'g'),"_");
  var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];   var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
  var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;   var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
  var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];   var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
  if (saveval != "correct") {   if ((saveval != "correct") || override) {
     scorename.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;      scorename.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
     selname[0].selected = true;      selname[0].selected = true;
  }   }
Line 2229  sub viewgrades_js { Line 3194  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 2251  sub viewgrades_js { Line 3216  sub viewgrades_js {
     if (selval[2].selected) {      if (selval[2].selected) {
  document.classgrade["GD_"+user+'_'+partid+"_tries"].value = "0";   document.classgrade["GD_"+user+'_'+partid+"_tries"].value = "0";
     }      }
  }          }
     }      }
   
     function resetEntry(numpart) {      function resetEntry(numpart) {
Line 2269  sub viewgrades_js { Line 3234  sub viewgrades_js {
   
     for (i=0;i<document.classgrade.total.value;i++) {      for (i=0;i<document.classgrade.total.value;i++) {
  var user = document.classgrade["ctr"+i].value;   var user = document.classgrade["ctr"+i].value;
    user = user.replace(new RegExp(':', 'g'),"_");
  var resetscore = document.classgrade["GD_"+user+"_"+partid+"_awarded"];   var resetscore = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
  resetscore.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;   resetscore.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
  var resettries = document.classgrade["GD_"+user+"_"+partid+"_tries"];   var resettries = document.classgrade["GD_"+user+"_"+partid+"_tries"];
Line 2284  sub viewgrades_js { Line 3250  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) = @_;
     &viewgrades_js($request);      &viewgrades_js($request);
   
     my ($symb,$url) = ($ENV{'form.symb'},$ENV{'form.url'});   
     #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(
                  $ENV{'course.'.$ENV{'request.course.id'}.'.num'},                   $env{'course.'.$env{'request.course.id'}.'.num'},
                  $ENV{'course.'.$ENV{'request.course.id'}.'.domain'});                   $env{'course.'.$env{'request.course.id'}.'.domain'});
     &Apache::lonnet::clear_EXT_cache_status();      &Apache::lonnet::clear_EXT_cache_status();
   
     my $result='<h3><font color="#339933">'.&mt('Manual Grading').'</font></h3>';      my $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>';
     $result.='<font size=+1><b>Current Resource: </b>'.$ENV{'form.probTitle'}.'</font>'."\n";  
   
     #view individual student submission form - called using Javascript viewOneStudent      #view individual student submission form - called using Javascript viewOneStudent
     $result.=&jscriptNform($url,$symb);      $result.=&jscriptNform($symb);
   
     #beginning of class grading form      #beginning of class grading form
       my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n".      $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n".
  '<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".   '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
  '<input type="hidden" name="url"     value="'.$url.'" />'."\n".  
  '<input type="hidden" name="command" value="editgrades" />'."\n".   '<input type="hidden" name="command" value="editgrades" />'."\n".
  '<input type="hidden" name="section" value="'.$ENV{'form.section'}.'" />'."\n".   &build_section_inputs().
  '<input type="hidden" name="saveState" value="'.$ENV{'form.saveState'}.'" />'."\n".   '<input type="hidden" name="Status" value="'.$env{'stu_status'}.'" />'."\n".
  '<input type="hidden" name="Status" value="'.$ENV{'form.Status'}.'" />'."\n".  
  '<input type="hidden" name="probTitle" value="'.$ENV{'form.probTitle'}.'" />'."\n";      my ($common_header,$specific_header);
       if ($env{'form.section'} eq 'all') {
     my $sectionClass;   $common_header = &mt('Assign Common Grade to Class');
     if ($ENV{'form.section'} eq 'all') {          $specific_header = &mt('Assign Grade to Specific Students in Class');
  $sectionClass='Class </h3>';      } elsif ($env{'form.section'} eq 'none') {
     } elsif ($ENV{'form.section'} eq 'no') {          $common_header = &mt('Assign Common Grade to Students in no Section');
  $sectionClass='Students in no Section </h3>';   $specific_header = &mt('Assign Grade to Specific Students in no Section');
     } else {      } else {
  $sectionClass='Students in Section '.$ENV{'form.section'}.'</h3>';          my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
           $common_header = &mt('Assign Common Grade to Students in Section(s) [_1]',$section_display);
    $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1]',$section_display);
     }      }
     $result.='<h3>Assign Common Grade To '.$sectionClass;      $result.= '<h3>'.$common_header.'</h3>'.&Apache::loncommon::start_data_table();
     $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".  
  '<table border=0><tr bgcolor="#ffffdd"><td>';  
     #radio buttons/text box for assigning points for a section or class.      #radio buttons/text box for assigning points for a section or class.
     #handles different parts of a problem      #handles different parts of a problem
     my ($partlist,$handgrade) = &response_type($url,$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 = ();
     for (sort keys(%$handgrade)) {      my @part_response_id = &flatten_responseType($responseType);
  my ($partid,$respid) = split (/_/,$_,2);      foreach my $part_response_id (@part_response_id) {
       my ($partid,$respid) = @{ $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{$_};   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_'.   my $display_part=&get_display_part($partid,$symb);
     $ctsparts.'" value="'.$partid.'" />'."\n";   my $radio.='<table border="0"><tr>';  
  $result.='<input type="hidden" name="weight_'.  
     $partid.'" value="'.$weight{$partid}.'" />'."\n";  
  my $display_part=&get_display_part($partid,$url,$symb);  
  $result.='<tr><td><b>Part:</b> '.$display_part.'&nbsp; &nbsp;<b>Point:</b> </td><td>';  
  $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><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."</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><select name="SELVAL_'.$partid.'"'.
     'onChange="javascript:writeRadText(\''.$partid.'\','.      'onchange="javascript:writeRadText(\''.$partid.'\','.
  $weight{$partid}.')"> '.   $weight{$partid}.')"> '.
     '<option selected="on"> </option>'.      '<option selected="selected"> </option>'.
     '<option>excused</option>'.      '<option value="excused">'.&mt('excused').'</option>'.
     '<option>reset status</option></select></td></tr>'."\n";      '<option value="reset status">'.&mt('reset status').'</option>'.
       '</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="Reset" '.      $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;      $result.= '<h3>'.$specific_header.'</h3>'.
     $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".                &Apache::loncommon::start_data_table().
  '<table border=0><tr bgcolor="#deffff"><td>&nbsp;<b>No.</b>&nbsp;</td>'.        &Apache::loncommon::start_data_table_header_row().
  '<td>'.&nameUserString('header')."</td>\n";        '<th>'.&mt('No.').'</th>'.
     my (@parts) = sort(&getpartlist($url,$symb));        '<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 @partids = ();
     foreach my $part (@parts) {      foreach my $part (@parts) {
  my $display=&Apache::lonnet::metadata($url,$part.'.display');   my $display=&Apache::lonnet::metadata($url,$part.'.display');
  $display =~ s|^Number of Attempts|Tries<br />|; # makes the column narrower          my $narrowtext = &mt('Tries');
    $display =~ s|^Number of Attempts|$narrowtext <br />|; # makes the column narrower
  if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }   if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }
  my ($partid) = &split_part_type($part);   my ($partid) = &split_part_type($part);
  my $display_part=&get_display_part($partid,$url,$symb);          push(@partids,$partid);
   #
   # FIXME: Looks like $display looks at English text
   #
    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]<br /> (weight = [_2])',
       $display_part,$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 = 
    &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($env{'form.section'},'1');
     my $ctr = 0;      my $ctr = 0;
     foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {      foreach (sort 
  my $uname = $_;       {
  $uname=~s/:/_/;   if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
  $result.='<input type="hidden" name="ctr'.$ctr.'" value="'.$uname.'" />'."\n";       return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
    }
    return $a cmp $b;
        } (keys(%$fullname))) {
  $ctr++;   $ctr++;
  $result.=&viewstudentgrade($url,$symb,$ENV{'request.course.id'},   $result.=&viewstudentgrade($symb,$env{'request.course.id'},
    $_,$$fullname{$_},\@parts,\%weight,$ctr);     $_,$$fullname{$_},\@parts,\%weight,$ctr,\%last_resets);
     }      }
     $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 (scalar(%$fullname) eq 0) {
  my $colspan=3+scalar(@parts);   my $colspan=3+scalar(@parts);
  $result='<font color="red">There are no students in section "'.$ENV{'form.section'}.   my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
     '" with enrollment status "'.$ENV{'form.Status'}.'" to modify or grade.</font>';          my $stu_status = join(' or ',&Apache::loncommon::get_env_multiple('form.Status'));
    $result='<span class="LC_warning">'.
       &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade.',
           $section_display, $stu_status).
       '</span>';
     }      }
     $result.=&show_grading_menu_form($symb,$url);  
     return $result;      return $result;
 }  }
   
 #--- call by previous routine to display each student  #--- call by previous routine to display each student
 sub viewstudentgrade {  sub viewstudentgrade {
     my ($url,$symb,$courseid,$student,$fullname,$parts,$weight,$ctr) = @_;      my ($symb,$courseid,$student,$fullname,$parts,$weight,$ctr,$last_resets) = @_;
     my ($uname,$udom) = split(/:/,$student);      my ($uname,$udom) = split(/:/,$student);
     $student=~s/:/_/;  
     my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);      my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
     my $result='<tr bgcolor="#ffffdd"><td align="right">'.$ctr.'&nbsp;</td><td>&nbsp;'.      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> '.
  '<font color="#999999">('.$uname.($ENV{'user.domain'} eq $udom ? '' : ':'.$udom).')</font></td>'."\n";   '<span class="LC_internal_info">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</span></td>'."\n";
       $student=~s/:/_/; # colon doen't work in javascript for names
     foreach my $apart (@$parts) {      foreach my $apart (@$parts) {
  my ($part,$type) = &split_part_type($apart);   my ($part,$type) = &split_part_type($apart);
  my $score=$record{"resource.$part.$type"};   my $score=$record{"resource.$part.$type"};
           $result.='<td align="center">';
           my ($aggtries,$totaltries);
           unless (exists($aggregates{$part})) {
       $totaltries = $record{'resource.'.$part.'.tries'};
   
       $aggtries = $totaltries;
               if ($$last_resets{$part}) {  
                   $aggtries = &get_num_tries(\%record,$$last_resets{$part},
      $part);
               }
               $result.='<input type="hidden" name="'.
                   'GD_'.$student.'_'.$part.'_aggtries" value="'.$aggtries.'" />'."\n";
               $result.='<input type="hidden" name="'.
                   'GD_'.$student.'_'.$part.'_totaltries" value="'.$totaltries.'" />'."\n";
               $aggregates{$part} = 1;
           }
  if ($type eq 'awarded') {   if ($type eq 'awarded') {
     my $pts = $score eq '' ? '' : $score*$$weight{$part};      my $pts = $score eq '' ? '' : &compute_points($score,$$weight{$part});
     $result.='<input type="hidden" name="'.      $result.='<input type="hidden" name="'.
  'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";   'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";
     $result.='<td align="middle"><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);
     $status = 'nothing' if ($status eq '');      $status = 'nothing' if ($status eq '');
     $result.='<input type="hidden" name="'.'GD_'.$student.'_'.      $result.='<input type="hidden" name="'.'GD_'.$student.'_'.
  $part.'_solved_s" value="'.$status.'" />'."\n";   $part.'_solved_s" value="'.$status.'" />'."\n";
     $result.='<td align="middle">&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="on">excused</option>'       $result.= (($status eq 'excused') ? '<option> </option><option selected="selected" value="excused">'.&mt('excused').'</option>' 
  : '<option selected="on"> </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="'.
  'GD_'.$student.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'.   'GD_'.$student.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'.
     "\n";      "\n";
     $result.='<td align="middle"><input type="text" name="'.      $result.='<input type="text" name="'.
  'GD_'.$student.'_'.$part.'_'.$type.'" '.   'GD_'.$student.'_'.$part.'_'.$type.'" '.
  '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 $symb=$ENV{'form.symb'};  
     my $url =$ENV{'form.url'};  
     my $title='<h3><font color="#339933">Current Grade Status</font></h3>';  
     $title.='<font size=+1><b>Current Resource: </b>'.$ENV{'form.probTitle'}.'</font><br />'."\n";  
     $title.='<font size=+1><b>Section: </b>'.$ENV{'form.section'}.'</font>'."\n";  
   
     my $result= '<table border="0"><tr><td bgcolor="#777777">'."\n";  
     $result.= '<table border="0"><tr bgcolor="#deffff">'.  
  '<td rowspan=2 valign="center">&nbsp;<b>No.</b>&nbsp;</td>'.  
  '<td rowspan=2 valign="center">'.&nameUserString('header')."</td>\n";  
   
       my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
       my $title='<h2>'.&mt('Current Grade Status').'</h2>';
       $title.='<h4>'.&mt('<b>Section: </b>[_1]',$section_display).'</h4>'."\n";
   
       my $result= &Apache::loncommon::start_data_table().
    &Apache::loncommon::start_data_table_header_row().
    '<th rowspan="2" valign="middle">'.&mt('No.').'</th>'.
    '<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');
   
     my (@partid);      my (@partid);
     my %weight = ();      my %weight = ();
     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($url,$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);
     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');
     $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;
  }   }
     }      }
     foreach my $partid (@partid) {      foreach my $partid (@partid) {
  my $display_part=&get_display_part($partid,$url,$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 $line;
  my $user = $ENV{'form.ctr'.$i};   my $user = $env{'form.ctr'.$i};
  my $usercolon = $user;   my ($uname,$udom)=split(/:/,$user);
  $usercolon =~s/_/:/;  
  my ($uname,$udom)=split(/_/,$user);  
  my %newrecord;   my %newrecord;
  my $updateflag = 0;   my $updateflag = 0;
  $line .= '<td>'.&nameUserString(undef,$$fullname{$usercolon},$uname,$udom).'</td>';   $line .= '<td>'.&nameUserString(undef,$$fullname{$user},$uname,$udom).'</td>';
  my $usec=$classlist->{"$uname:$udom"}[5];   my $usec=$classlist->{"$uname:$udom"}[5];
  if (!&canmodify($usec)) {   if (!&canmodify($usec)) {
     my $numcols=scalar(@partid)*4+2;      my $numcols=scalar(@partid)*4+2;
     $noupdate.=$line."<td colspan=\"$numcols\"><font color=\"red\">Not allowed to modify student</font></td></tr>";      push(@noupdate,
    $line."<td colspan=\"$numcols\"><span class=\"LC_warning\">".
    &mt('Not allowed to modify student')."</span></td></tr>");
     next;      next;
  }   }
           my %aggregate = ();
           my $aggregateflag = 0;
    $user=~s/:/_/; # colon doen't work in javascript for names
  foreach (@partid) {   foreach (@partid) {
     my $old_aw    = $ENV{'form.GD_'.$user.'_'.$_.'_awarded_s'};      my $old_aw    = $env{'form.GD_'.$user.'_'.$_.'_awarded_s'};
     my $old_part_pcr = $old_aw/($weight{$_} ne '0' ? $weight{$_}:1);      my $old_part_pcr = $old_aw/($weight{$_} ne '0' ? $weight{$_}:1);
     my $old_part  = $old_aw eq '' ? '' : $old_part_pcr;      my $old_part  = $old_aw eq '' ? '' : $old_part_pcr;
     my $old_score = $scoreptr{$ENV{'form.GD_'.$user.'_'.$_.'_solved_s'}};      my $old_score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
       my $awarded   = $env{'form.GD_'.$user.'_'.$_.'_awarded'};
     my $awarded   = $ENV{'form.GD_'.$user.'_'.$_.'_awarded'};  
     my $pcr       = $awarded/($weight{$_} ne '0' ? $weight{$_} : 1);      my $pcr       = $awarded/($weight{$_} ne '0' ? $weight{$_} : 1);
     my $partial   = $awarded eq '' ? '' : $pcr;      my $partial   = $awarded eq '' ? '' : $pcr;
     my $score;      my $score;
     if ($partial eq '') {      if ($partial eq '') {
  $score = $scoreptr{$ENV{'form.GD_'.$user.'_'.$_.'_solved_s'}};   $score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
     } elsif ($partial > 0) {      } elsif ($partial > 0) {
  $score = 'correct_by_override';   $score = 'correct_by_override';
     } elsif ($partial == 0) {      } elsif ($partial == 0) {
  $score = 'incorrect_by_override';   $score = 'incorrect_by_override';
     }      }
     my $dropMenu = $ENV{'form.GD_'.$user.'_'.$_.'_solved'};      my $dropMenu = $env{'form.GD_'.$user.'_'.$_.'_solved'};
     $score = 'excused' if (($dropMenu eq 'excused') && ($score ne 'excused'));      $score = 'excused' if (($dropMenu eq 'excused') && ($score ne 'excused'));
   
       $newrecord{'resource.'.$_.'.regrader'}=
    "$env{'user.name'}:$env{'user.domain'}";
     if ($dropMenu eq 'reset status' &&      if ($dropMenu eq 'reset status' &&
  $old_score ne '') { # ignore if no previous attempts => nothing to reset   $old_score ne '') { # ignore if no previous attempts => nothing to reset
  $newrecord{'resource.'.$_.'.tries'} = 0;   $newrecord{'resource.'.$_.'.tries'} = '';
  $newrecord{'resource.'.$_.'.solved'} = '';   $newrecord{'resource.'.$_.'.solved'} = '';
  $newrecord{'resource.'.$_.'.award'} = '';   $newrecord{'resource.'.$_.'.award'} = '';
  $newrecord{'resource.'.$_.'.awarded'} = 0;   $newrecord{'resource.'.$_.'.awarded'} = '';
  $newrecord{'resource.'.$_.'.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}";  
  $updateflag = 1;   $updateflag = 1;
                   if ($env{'form.GD_'.$user.'_'.$_.'_aggtries'} > 0) {
                       my $aggtries = $env{'form.GD_'.$user.'_'.$_.'_aggtries'};
                       my $totaltries = $env{'form.GD_'.$user.'_'.$_.'_totaltries'};
                       my $solvedstatus = $env{'form.GD_'.$user.'_'.$_.'_solved_s'};
                       &decrement_aggs($symb,$_,\%aggregate,$aggtries,$totaltries,$solvedstatus);
                       $aggregateflag = 1;
                   }
     } elsif (!($old_part eq $partial && $old_score eq $score)) {      } elsif (!($old_part eq $partial && $old_score eq $score)) {
  $updateflag = 1;   $updateflag = 1;
  $newrecord{'resource.'.$_.'.awarded'}  = $partial if $partial ne '';   $newrecord{'resource.'.$_.'.awarded'}  = $partial if $partial ne '';
Line 2598  sub editgrades { Line 3634  sub editgrades {
  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 $old_aw    = $ENV{'form.GD_'.$user.'_'.$part.'_'.$type.'_s'};   my $old_aw    = $env{'form.GD_'.$user.'_'.$part.'_'.$type.'_s'};
  my $awarded   = $ENV{'form.GD_'.$user.'_'.$part.'_'.$type};   my $awarded   = $env{'form.GD_'.$user.'_'.$part.'_'.$type};
  if ($awarded ne '' && $awarded ne $old_aw) {   if ($awarded ne '' && $awarded ne $old_aw) {
     $newrecord{'resource.'.$part.'.'.$type}= $awarded;      $newrecord{'resource.'.$part.'.'.$type}= $awarded;
     $newrecord{'resource.'.$part.'.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}";      $newrecord{'resource.'.$part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
     $updateflag=1;      $updateflag=1;
  }   }
  $line .= '<td align="center">'.$old_aw.'&nbsp;</td>'.   $line .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
     '<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 $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
   
  if ($updateflag) {   if ($updateflag) {
     $count++;      $count++;
     &Apache::lonnet::cstore(\%newrecord,$symb,$ENV{'request.course.id'},      &Apache::lonnet::cstore(\%newrecord,$symb,$env{'request.course.id'},
     $udom,$uname);      $udom,$uname);
     $result.='<tr bgcolor="#ffffde"><td align="right">&nbsp;'.$updateCtr.'&nbsp;</td>'.$line;  
       if (&Apache::bridgetask::in_queue('gradingqueue',$symb,$cdom,
         $cnum,$udom,$uname)) {
    # need to figure out if should be in queue.
    my %record =  
       &Apache::lonnet::restore($symb,$env{'request.course.id'},
        $udom,$uname);
    my $all_graded = 1;
    my $none_graded = 1;
    foreach my $part (@parts) {
       if ( $record{'resource.'.$part.'.awarded'} eq '' ) {
    $all_graded = 0;
       } else {
    $none_graded = 0;
       }
    }
   
    if ($all_graded || $none_graded) {
       &Apache::bridgetask::remove_from_queue('gradingqueue',
      $symb,$cdom,$cnum,
      $udom,$uname);
    }
       }
   
       $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) {
               &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
     $cdom,$cnum);
           }
     }      }
     if ($noupdate) {      if (@noupdate) {
 # my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3;  # my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3;
  my $numcols=scalar(@partid)*4+2;   my $numcols=scalar(@partid)*4+2;
  $result .= '<tr bgcolor="#ffffff"><td align="center" colspan="'.$numcols.'">No Changes Occurred For the Students Below</td></tr><tr bgcolor="#ffffde">'.$noupdate;   $result .= &Apache::loncommon::start_data_table_row('LC_empty_row').
     }      '<td align="center" colspan="'.$numcols.'">'.
     $result .= '</table></td></tr></table>'."\n".      &mt('No Changes Occurred For the Students Below').
  &show_grading_menu_form ($symb,$url);      '</td>'.
     my $msg = '<br /><b>Number of records updated = '.$rec_update.      &Apache::loncommon::end_data_table_row();
  ' for '.$count.' student'.($count <= 1 ? '' : 's').'.</b><br />'.   foreach my $line (@noupdate) {
  '<b>Total number of students = '.$ENV{'form.total'}.'</b><br />';      $result.=
    &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 2638  sub split_part_type { Line 3720  sub split_part_type {
     my ($partstr) = @_;      my ($partstr) = @_;
     my ($temp,@allparts)=split(/_/,$partstr);      my ($temp,@allparts)=split(/_/,$partstr);
     my $type=pop(@allparts);      my $type=pop(@allparts);
     my $part=join('.',@allparts);      my $part=join('_',@allparts);
     return ($part,$type);      return ($part,$type);
 }  }
   
Line 2653  sub split_part_type { Line 3735  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 the student/employee ID');
       my $error2=&mt('You need to specify at least one grading field');
   return(<<ENDPICK);    return(<<ENDPICK);
   function verify(vf) {    function verify(vf) {
     var foundsomething=0;      var foundsomething=0;
     var founduname=0;      var founduname=0;
     var founddomain=0;      var foundID=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) { founduname=1; }        if (i==0 && tw!=0) { foundID=1; }
       if (i==1 && tw!=0) { founddomain=1; }        if (i==1 && tw!=0) { founduname=1; }
       if (i!=0 && i!=1 && tw!=0) { foundsomething=1; }        if (i!=0 && i!=1 && i!=2 && tw!=0) { foundsomething=1; }
     }      }
     if (founduname==0 || founddomain==0) {      if (founduname==0 && foundID==0) {
       alert('You need to specify at both the username and domain');   alert('$error1');
       return;   return;
     }      }
     if (foundsomething==0) {      if (foundsomething==0) {
       alert('You need to specify at least one grading field');   alert('$error2');
       return;   return;
     }      }
     vf.submit();      vf.submit();
   }    }
Line 2691  ENDPICK Line 3775  ENDPICK
 }  }
   
 sub csvupload_javascript_forward_associate {  sub csvupload_javascript_forward_associate {
       my $error1=&mt('You need to specify the username or the student/employee ID');
       my $error2=&mt('You need to specify at least one grading field');
   return(<<ENDPICK);    return(<<ENDPICK);
   function verify(vf) {    function verify(vf) {
     var foundsomething=0;      var foundsomething=0;
     var founduname=0;      var founduname=0;
     var founddomain=0;      var foundID=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) { founduname=1; }        if (tw==1) { foundID=1; }
       if (tw==2) { founddomain=1; }        if (tw==2) { founduname=1; }
       if (tw>2) { foundsomething=1; }        if (tw>3) { foundsomething=1; }
     }      }
     if (founduname==0 || founddomain==0) {      if (founduname==0 && foundID==0) {
       alert('You need to specify at both the username and domain');   alert('$error1');
       return;   return;
     }      }
     if (foundsomething==0) {      if (foundsomething==0) {
       alert('You need to specify at least one grading field');   alert('$error2');
       return;   return;
     }      }
     vf.submit();      vf.submit();
   }    }
Line 2726  ENDPICK Line 3812  ENDPICK
 }  }
   
 sub csvuploadmap_header {  sub csvuploadmap_header {
     my ($request,$symb,$url,$datatoken,$distotal)= @_;      my ($request,$symb,$datatoken,$distotal)= @_;
     my $javascript;      my $javascript;
     if ($ENV{'form.upfile_associate'} eq 'reverse') {      if ($env{'form.upfile_associate'} eq 'reverse') {
  $javascript=&csvupload_javascript_reverse_associate();   $javascript=&csvupload_javascript_reverse_associate();
     } else {      } else {
  $javascript=&csvupload_javascript_forward_associate();   $javascript=&csvupload_javascript_forward_associate();
     }      }
   
     my ($result) = &showResourceInfo($url,$ENV{'form.probTitle'});      my $result='';
       $symb = &Apache::lonenc::check_encrypt($symb);
     $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><font color="#339933">Uploading Class Grades</font></h3>  <h3><span class="LC_info">Uploading Class Grades</span></h3>
 $result  $result
 <hr>  <hr />
 <h3>Identify fields</h3>  <h3>Identify fields</h3>
 Total number of records found in file: $distotal <hr />  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  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 />  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);" />  <input type="button" value="Reverse Association" onclick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" />
 <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" />
 <input type="hidden" name="fileupload" value="$ENV{'form.fileupload'}" />  <input type="hidden" name="fileupload" value="$env{'form.fileupload'}" />
 <input type="hidden" name="upfiletype" value="$ENV{'form.upfiletype'}" />  <input type="hidden" name="upfiletype" value="$env{'form.upfiletype'}" />
 <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="url"        value="$url" />  <input type="hidden" name="command"    value="csvuploadoptions" />
 <input type="hidden" name="saveState"  value="$ENV{'form.saveState'}" />  
 <input type="hidden" name="probTitle"  value="$ENV{'form.probTitle'}" />  
 <input type="hidden" name="command"    value="csvuploadassign" />  
 <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 ($url,$symb) = @_;      my ($symb,$errorref) = @_;
     my (@parts) = &getpartlist($url,$symb);      my (@parts) = &getpartlist($symb,$errorref);
     my @fields=(['username','Student Username'],['domain','Student Domain']);      if (ref($errorref)) {
           if ($$errorref) {
               return;
           }
       }
   
       my @fields=(['ID','Student/Employee ID'],
    ['username','Student Username'],
    ['domain','Student Domain']);
       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');
  my $name=$part;   my $name=$part;
  if  (!$display) { $display = $name; }   if  (!$display) { $display = $name; }
  @datum=($name,$display);   @datum=($name,$display);
    if ($name=~/^stores_(.*)_awarded/) {
       push(@fields,['stores_'.$1.'_points',"Points [Part: $1]"]);
    }
  push(@fields,\@datum);   push(@fields,\@datum);
     }      }
     return (@fields);      return (@fields);
Line 2788  sub csvuploadmap_footer { Line 3881  sub csvuploadmap_footer {
 </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="Assign Grades" /><br />
 </form>  </form>
 ENDPICK  ENDPICK
 }  }
   
 sub upcsvScores_form {  sub checkforfile_js {
     my ($request) = shift;      my $alertmsg = &mt('Please use the browse button to select a file from your local directory.');
     my ($symb,$url)=&get_symb_and_url($request);      my $result = &Apache::lonhtmlcommon::scripttag(<<CSVFORMJS);
     if (!$symb) {return '';}  
     my $result =<<CSVFORMJS;  
 <script type="text/javascript" language="javascript">  
     function checkUpload(formname) {      function checkUpload(formname) {
  if (formname.upfile.value == "") {   if (formname.upfile.value == "") {
     alert("Please use the browse button to select a file from your local directory.");      alert("$alertmsg");
     return false;      return false;
  }   }
  formname.submit();   formname.submit();
     }      }
     </script>  
 CSVFORMJS  CSVFORMJS
     $ENV{'form.probTitle'} = &Apache::lonnet::gettitle($symb);      return $result;
     my ($table) = &showResourceInfo($url,$ENV{'form.probTitle'});  }
     $result.=$table;  
     $result.='<br /><table width=100% border=0><tr><td bgcolor="#777777">'."\n";  sub upcsvScores_form {
     $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";      my ($request,$symb) = @_;
     $result.='&nbsp;<b>Specify a file containing the class scores for current resource'.      if (!$symb) {return '';}
  '.</b></td></tr>'."\n";      my $result=&checkforfile_js();
       $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";
       $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";
       $result.='&nbsp;<b>'.&mt('Specify a file containing the class scores for current resource.').
    '</b></td></tr>'."\n";
     $result.='<tr bgcolor=#ffffe6><td>'."\n";      $result.='<tr bgcolor=#ffffe6><td>'."\n";
       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');
       $symb = &Apache::lonenc::check_encrypt($symb);
     $result.=<<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="url" value="$url" />  
 <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 Scores" />  <br /><input type="button" onclick="javascript:checkUpload(this.form);" value="$upload" />
   
 </form>  </form>
 ENDUPFORM  ENDUPFORM
     $result.='</td></tr></table>'."\n";      $result.=&Apache::loncommon::help_open_topic("Course_Convert_To_CSV",
                              &mt("How do I create a CSV file from a spreadsheet"))
       .'</td></tr></table>'."\n";
     $result.='</td></tr></table><br /><br />'."\n";      $result.='</td></tr></table><br /><br />'."\n";
     $result.=&show_grading_menu_form($symb,$url);  
     return $result;      return $result;
 }  }
   
   
 sub csvuploadmap {  sub csvuploadmap {
     my ($request)= @_;      my ($request,$symb)= @_;
     my ($symb,$url)=&get_symb_and_url($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=$env{'form.datatoken'};
  &Apache::loncommon::load_tmp_file($request);   &Apache::loncommon::load_tmp_file($request);
     }      }
     my @records=&Apache::loncommon::upfile_record_sep();      my @records=&Apache::loncommon::upfile_record_sep();
     &csvuploadmap_header($request,$symb,$url,$datatoken,$#records+1);      &csvuploadmap_header($request,$symb,$datatoken,$#records+1);
     my ($i,$keyfields);      my ($i,$keyfields);
     if (@records) {      if (@records) {
  my @fields=&csvupload_fields($url,$symb);          my $fieldserror;
    my @fields=&csvupload_fields($symb,\$fieldserror);
  if ($ENV{'form.upfile_associate'} eq 'reverse') {          if ($fieldserror) {
               $request->print(&navmap_errormsg());
               return;
           }
    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,
   \@fields);    \@fields);
Line 2864  sub csvuploadmap { Line 3960  sub csvuploadmap {
     unshift(@fields,['none','']);      unshift(@fields,['none','']);
     $i=&Apache::loncommon::csv_samples_select_table($request,\@records,      $i=&Apache::loncommon::csv_samples_select_table($request,\@records,
     \@fields);      \@fields);
     my %sone=&Apache::loncommon::record_sep($records[0]);              foreach my $rec (@records) {
     $keyfields=join(',',sort(keys(%sone)));                  my %temp = &Apache::loncommon::record_sep($rec);
                   if (%temp) {
                       $keyfields=join(',',sort(keys(%temp)));
                       last;
                   }
               }
  }   }
     }      }
     &csvuploadmap_footer($request,$i,$keyfields);      &csvuploadmap_footer($request,$i,$keyfields);
     $request->print(&show_grading_menu_form($symb,$url));  
   
     return '';      return '';
 }  }
   
 sub csvuploadassign {  sub csvuploadoptions {
     my ($request)= @_;      my ($request,$symb)= @_;
     my ($symb,$url)=&get_symb_and_url($request);      $request->print(<<ENDPICK);
     if (!$symb) {return '';}  <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
     &Apache::loncommon::load_tmp_file($request);  <h3><span class="LC_info">Uploading Class Grade Options</span></h3>
     my @gradedata = &Apache::loncommon::upfile_record_sep();  <input type="hidden" name="command"    value="csvuploadassign" />
     my @keyfields = split(/\,/,$ENV{'form.keyfields'});  <!--
     my %fields=();  <p>
     for (my $i=0; $i<=$ENV{'form.nfields'}; $i++) {  <label>
  if ($ENV{'form.upfile_associate'} eq 'reverse') {     <input type="checkbox" name="show_full_results" />
     if ($ENV{'form.f'.$i} ne 'none') {     Show a table of all changes
  $fields{$keyfields[$i]}=$ENV{'form.f'.$i};  </label>
   </p>
   -->
   <p>
   <label>
      <input type="checkbox" name="overwite_scores" checked="checked" />
      Overwrite any existing score
   </label>
   </p>
   ENDPICK
       my %fields=&get_fields();
       if (!defined($fields{'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");
       }
       foreach my $key (sort(keys(%env))) {
    if ($key !~ /^form\.(.*)$/) { next; }
    my $cleankey=$1;
    if ($cleankey eq 'command') { next; }
    $request->print('<input type="hidden" name="'.$cleankey.
    '"  value="'.$env{$key}.'" />'."\n");
       }
       # FIXME do a check for any duplicated user ids...
       # FIXME do a check for any invalid user ids?...
       $request->print('<input type="submit" value="Assign Grades" /><br />
   <hr /></form>'."\n");
       return '';
   }
   
   sub get_fields {
       my %fields;
       my @keyfields = split(/\,/,$env{'form.keyfields'});
       for (my $i=0; $i<=$env{'form.nfields'}; $i++) {
    if ($env{'form.upfile_associate'} eq 'reverse') {
       if ($env{'form.f'.$i} ne 'none') {
    $fields{$keyfields[$i]}=$env{'form.f'.$i};
     }      }
  } else {   } else {
     if ($ENV{'form.f'.$i} ne 'none') {      if ($env{'form.f'.$i} ne 'none') {
  $fields{$ENV{'form.f'.$i}}=$keyfields[$i];   $fields{$env{'form.f'.$i}}=$keyfields[$i];
     }      }
  }   }
     }      }
       return %fields;
   }
   
   sub csvuploadassign {
       my ($request,$symb)= @_;
       if (!$symb) {return '';}
       my $error_msg = '';
       &Apache::loncommon::load_tmp_file($request);
       my @gradedata = &Apache::loncommon::upfile_record_sep();
       my %fields=&get_fields();
     $request->print('<h3>Assigning Grades</h3>');      $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 $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);
    my $domain;
    if ($entries{$fields{'domain'}}) {
       $domain=$entries{$fields{'domain'}};
    } else {
       $domain=$env{'form.default_domain'};
    }
    $domain=~s/\s//g;
  my $username=$entries{$fields{'username'}};   my $username=$entries{$fields{'username'}};
  $username=~s/\s//g;   $username=~s/\s//g;
  my $domain=$entries{$fields{'domain'}};   if (!$username) {
  $domain=~s/\s//g;      my $id=$entries{$fields{'ID'}};
       $id=~s/\s//g;
       my %ids=&Apache::lonnet::idget($domain,$id);
       $username=$ids{$id};
    }
  if (!exists($$classlist{"$username:$domain"})) {   if (!exists($$classlist{"$username:$domain"})) {
     push(@skipped,"$username:$domain");      my $id=$entries{$fields{'ID'}};
       $id=~s/\s//g;
       if ($id) {
    push(@skipped,"$id:$domain");
       } else {
    push(@skipped,"$username:$domain");
       }
     next;      next;
  }   }
  my $usec=$classlist->{"$username:$domain"}[5];   my $usec=$classlist->{"$username:$domain"}[5];
Line 2914  sub csvuploadassign { Line 4076  sub csvuploadassign {
     push(@notallowed,"$username:$domain");      push(@notallowed,"$username:$domain");
     next;      next;
  }   }
    my %points;
  my %grades;   my %grades;
  foreach my $dest (keys(%fields)) {   foreach my $dest (keys(%fields)) {
     if ($dest eq 'username' || $dest eq 'domain') { next; }      if ($dest eq 'ID' || $dest eq 'username' ||
     if ($entries{$fields{$dest}} eq '') { next; }   $dest eq 'domain') { next; }
     my $store_key=$dest;      if ($entries{$fields{$dest}} =~ /^\s*$/) { next; }
     $store_key=~s/^stores/resource/;      if ($dest=~/stores_(.*)_points/) {
     $store_key=~s/_/\./g;   my $part=$1;
     $grades{$store_key}=$entries{$fields{$dest}};   my $wgt =&Apache::lonnet::EXT('resource.'.$part.'.weight',
  }        $symb,$domain,$username);
  $grades{"resource.regrader"}="$ENV{'user.name'}:$ENV{'user.domain'}";                  if ($wgt) {
  &Apache::lonnet::cstore(\%grades,$symb,$ENV{'request.course.id'},                      $entries{$fields{$dest}}=~s/\s//g;
  $domain,$username);                      my $pcr=$entries{$fields{$dest}} / $wgt;
  $request->print('.');                      my $award=($pcr == 0) ? 'incorrect_by_override'
  $request->rflush();                                            : 'correct_by_override';
  $countdone++;                      $grades{"resource.$part.awarded"}=$pcr;
                       $grades{"resource.$part.solved"}=$award;
                       $points{$part}=1;
                   } else {
                       $error_msg = "<br />" .
                           &mt("Some point values were assigned"
                               ." for problems with a weight "
                               ."of zero. These values were "
                               ."ignored.");
                   }
       } else {
    if ($dest=~/stores_(.*)_awarded/) { if ($points{$1}) {next;} }
    if ($dest=~/stores_(.*)_solved/)  { if ($points{$1}) {next;} }
    my $store_key=$dest;
    $store_key=~s/^stores/resource/;
    $store_key=~s/_/\./g;
    $grades{$store_key}=$entries{$fields{$dest}};
       }
    }
    if (! %grades) { 
              push(@skipped,&mt("[_1]: no data to save","$username:$domain")); 
           } else {
      $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
      my $result=&Apache::lonnet::cstore(\%grades,$symb,
      $env{'request.course.id'},
      $domain,$username);
      if ($result eq 'ok') {
   # Successfully stored
         $request->print('.');
   # Remove from grading queue
                 &Apache::bridgetask::remove_from_queue('gradingqueue',$symb,
                                                $env{'course.'.$env{'request.course.id'}.'.domain'},
                                                $env{'course.'.$env{'request.course.id'}.'.num'},
                                                $domain,$username);
                 $countdone++;
              } else {
         $request->print("<p><span class=\"LC_error\">".
                                 &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 />Stored $countdone students\n");      $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt("Saved scores for [quant,_1,student]",$countdone),$countdone==0));
     if (@skipped) {      if (@skipped) {
  $request->print('<p<font size="+1"><b>Skipped Students</b></font></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><font size="+1" color="red"><b>Students Not Allowed to Modify</b></font></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,$url));      return $error_msg;
     return '';  
 }  }
 #------------- end of section for handling csv file upload ---------  #------------- end of section for handling csv file upload ---------
 #  #
Line 2951  sub csvuploadassign { Line 4154  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">      $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 2967  function checkPickOne(formname) { Line 4170  function checkPickOne(formname) {
     formname.submit();      formname.submit();
 }  }
   
 </script>  
 LISTJAVASCRIPT  LISTJAVASCRIPT
     &commonJSfunctions($request);      &commonJSfunctions($request);
     my ($symb,$url) = &get_symb_and_url($request);  
     my $cdom      = $ENV{"course.$ENV{'request.course.id'}.domain"};  
     my $cnum      = $ENV{"course.$ENV{'request.course.id'}.num"};  
     my $getsec    = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'};  
   
     my $result='<h3><font color="#339933">&nbsp;'.      my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
  'Manual Grading by Page or Sequence</font></h3>';      my $cnum      = $env{"course.$env{'request.course.id'}.num"};
       my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
   
       my $result='<h3><span class="LC_info">&nbsp;'.
    &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($request);      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)/);
       my $select = '<select name="selectpage">'."\n";
     my $ctr=0;      my $ctr=0;
     foreach (@$titles) {      foreach (@$titles) {
  my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);   my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
  $result.='<option value="'.$ctr.'" '.   $select.='<option value="'.$ctr.'" '.
     ($$symbx{$_} =~ /$curpage$/ ? 'selected="on"' : '').      ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
     '>'.$showtitle.'</option>'."\n";      '>'.$showtitle.'</option>'."\n";
  $ctr++;   $ctr++;
     }      }
     $result.= '</select>'."<br>\n";      $select.= '</select>';
       $result.='&nbsp;<b>'.&mt('Problems from').':</b> '.$select."<br />\n";
   
     $ctr=0;      $ctr=0;
     foreach (@$titles) {      foreach (@$titles) {
  my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);   my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
Line 3003  LISTJAVASCRIPT Line 4212  LISTJAVASCRIPT
     $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><input type="radio" name="vProb" value="no" checked="on" /> no '."\n".      my $options =
  '<input type="radio" name="vProb" value="yes" /> yes '."<br>\n";   '<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n".
    '<label><input type="radio" name="vProb" value="yes" /> '.&mt('yes').' </label>'."<br />\n";
     $result.='&nbsp;<b>Submission Details: </b>'.      $result.='&nbsp;<b>'.&mt('View Problem Text').': </b>'.$options;
  '<input type="radio" name="lastSub" value="none" /> none'."\n".  
  '<input type="radio" name="lastSub" value="datesub" checked /> by dates and submissions'."\n".      $options =
  '<input type="radio" name="lastSub" value="all" /> all details'."\n";   '<label><input type="radio" name="lastSub" value="none" /> '.&mt('none').' </label>'."\n".
    '<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> '.&mt('by dates and submissions').'</label>'."\n".
     $result.='<input type="hidden" name="section"     value="'.$getsec.'" />'."\n".   '<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').' </label>'."\n";
  '<input type="hidden" name="Status"  value="'.$ENV{'form.Status'}.'" />'."\n".      $result.='&nbsp;<b>'.&mt('Submissions').': </b>'.$options;
       
       $result.=&build_section_inputs();
       my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
       $result.='<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".
  '<input type="hidden" name="command" value="displayPage" />'."\n".   '<input type="hidden" name="command" value="displayPage" />'."\n".
  '<input type="hidden" name="url"     value="'.$url.'" />'."\n".   '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."<br />\n";
  '<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".  
  '<input type="hidden" name="saveState" value="'.$ENV{'form.saveState'}.'" />'."<br />\n";      $result.='&nbsp;<b>'.&mt('Use CODE').': </b> <input type="text" name="CODE" value="" /> <br />'."\n";
   
     $result.='&nbsp;<input type="button" '.      $result.='&nbsp;<input type="button" '.
  'onClick="javascript:checkPickOne(this.form);"value="Next->" /><br />'."\n";               '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');
     my $ptr = 1;      my $ptr = 1;
     foreach my $student (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {      foreach my $student (sort 
    {
        if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
    return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
        }
        return $a cmp $b;
    } (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;<input type="radio" name="student" value="'.$student.'" /> '   $studentTable.='<td>&nbsp;<label><input type="radio" name="student" value="'.$student.'" /> '
     .&nameUserString(undef,$$fullname{$student},$uname,$udom)."\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;' if ($ptr%2 == 0);      if ($ptr%2 == 0) {
     $studentTable.='</td></tr></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,$url);  
     $request->print($studentTable);      $request->print($studentTable);
   
     return '';      return '';
 }  }
   
 sub getSymbMap {  sub getSymbMap {
     my ($request) = @_;      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;
   
     # Gather every sequence that has problems.      # Gather every sequence that has problems.
     my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); }, 1);      my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); },
          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_problem(); }, 0) ) {
     my $title = $minder.'.'.$sequence->compTitle();      my $title = $minder.'.'.
     push @titles, $title; # minder in case two titles are identical   &HTML::Entities::encode($sequence->compTitle(),'"\'&');
     $symbx{$title} = $sequence->symb();      push(@titles, $title); # minder in case two titles are identical
       $symbx{$title} = &HTML::Entities::encode($sequence->symb(),'"\'&');
     $minder++;      $minder++;
  }   }
     }      }
   
     $navmap->untieHashes();  
     return \@titles,\%symbx;      return \@titles,\%symbx;
 }  }
   
 #  #
 #--- 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 $cdom      = $env{"course.$env{'request.course.id'}.domain"};
     my ($symb,$url) = &get_symb_and_url($request);      my $cnum      = $env{"course.$env{'request.course.id'}.num"};
     my $cdom      = $ENV{"course.$ENV{'request.course.id'}.domain"};      my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
     my $cnum      = $ENV{"course.$ENV{'request.course.id'}.num"};      my $pageTitle = $env{'form.page'};
     my $getsec    = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'};  
     my $pageTitle = $ENV{'form.page'};  
     my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');      my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
     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];
   
     #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(
                  $ENV{'course.'.$ENV{'request.course.id'}.'.num'},                   $env{'course.'.$env{'request.course.id'}.'.num'},
                  $ENV{'course.'.$ENV{'request.course.id'}.'.domain'});                   $env{'course.'.$env{'request.course.id'}.'.domain'});
     &Apache::lonnet::clear_EXT_cache_status();      &Apache::lonnet::clear_EXT_cache_status();
   
     if (!&canview($usec)) {      if (!&canview($usec)) {
  $request->print('<font color="red">Unable to view requested student.('.$ENV{'form.student'}.')</font>');   $request->print('<span class="LC_warning">'.&mt('Unable to view requested student. ([_1])',$env{'form.student'}).'</span>');
  $request->print(&show_grading_menu_form($symb,$url));  
  return;   return;
     }      }
     my $result='<h3><font color="#339933">&nbsp;'.$ENV{'form.title'}.'</font></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";
       $env{'form.CODE'} = uc($env{'form.CODE'});
       if (&Apache::lonnet::validCODE(uc($env{'form.CODE'}))) {
    $result.='<h3>&nbsp;'.&mt('CODE: [_1]',$env{'form.CODE'}).'</h3>'."\n";
       } else {
    delete($env{'form.CODE'});
       }
     &sub_page_js($request);      &sub_page_js($request);
     $request->print($result);      $request->print($result);
   
     my $navmap = Apache::lonnavmaps::navmap->new();      my $navmap = Apache::lonnavmaps::navmap->new();
     my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($ENV{'form.page'});      unless (ref($navmap)) {
           $request->print(&navmap_errormsg());
           return;
       }
       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) {
    $request->print('<span class="LC_warning">'.&mt('Unable to view requested sequence. ([_1])',$resUrl).'</span>');
    return; 
       }
     my $iterator = $navmap->getIterator($map->map_start(),      my $iterator = $navmap->getIterator($map->map_start(),
  $map->map_finish());   $map->map_finish());
   
     my $studentTable='<form action="/adm/grades" method="post" name="gradePage">'."\n".      my $studentTable='<form action="/adm/grades" method="post" name="gradePage">'."\n".
  '<input type="hidden" name="command" value="gradeByPage" />'."\n".   '<input type="hidden" name="command" value="gradeByPage" />'."\n".
  '<input type="hidden" name="fullname" value="'.$$fullname{$ENV{'form.student'}}.'" />'."\n".   '<input type="hidden" name="fullname" value="'.$$fullname{$env{'form.student'}}.'" />'."\n".
  '<input type="hidden" name="student" value="'.$ENV{'form.student'}.'" />'."\n".   '<input type="hidden" name="student" value="'.$env{'form.student'}.'" />'."\n".
  '<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="url"     value="'.$url.'" />'."\n".   '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
  '<input type="hidden" name="symb"    value="'.$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'})) {
    $studentTable.=
     my $checkIcon = '<img src="'.$request->dir_config('lonIconsURL').      '<input type="hidden" name="CODE" value="'.$env{'form.CODE'}.'" />'."\n";
  '/check.gif" height="16" border="0" />';      }
       my $checkIcon = '<img alt="'.&mt('Check Mark').
     $studentTable.='&nbsp;<b>Note:</b> Problems graded correct by the computer are marked with a '.$checkIcon.   '" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
  ' symbol.'."\n".  
  '<table border="0"><tr><td bgcolor="#777777">'.      $studentTable.='&nbsp;<span class="LC_info">'.
  '<table border="0"><tr bgcolor="#e6ffff">'.          &mt('Problems graded correct by the computer are marked with a [_1] symbol.',$checkIcon).
  '<td align="center"><b>&nbsp;Prob.&nbsp;</b></td>'.          '</span>'."\n".
  '<td><b>&nbsp;'.($ENV{'form.vProb'} eq 'no' ? 'Title' : 'Problem Text').'/Grade</b></td></tr>';   &Apache::loncommon::start_data_table().
    &Apache::loncommon::start_data_table_header_row().
    '<th align="center">&nbsp;Prob.&nbsp;</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();
     my ($depth,$question,$prob) = (1,1,1);      my ($depth,$question,$prob) = (1,1,1);
     $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"
Line 3147  sub displayPage { Line 4392  sub displayPage {
     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('[_1]&nbsp;parts)',
    scalar(@{$parts}))
    ).
    '</td>';
     $studentTable.='<td valign="top">';      $studentTable.='<td valign="top">';
     if ($ENV{'form.vProb'} eq 'yes' ) {      my %form = ('CODE' => $env{'form.CODE'},);
       if ($env{'form.vProb'} eq 'yes' ) {
  $studentTable.=&show_problem($request,$symbx,$uname,$udom,1,   $studentTable.=&show_problem($request,$symbx,$uname,$udom,1,
      undef,'both');       undef,'both',\%form);
     } else {      } else {
  my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$ENV{'request.course.id'});   my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'},%form);
  $companswer =~ s|<form(.*?)>||g;   $companswer =~ s|<form(.*?)>||g;
  $companswer =~ s|</form>||g;   $companswer =~ s|</form>||g;
 # while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a>  # while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a>
 #    $companswer =~ s/$1/ /ms;  #    $companswer =~ s/$1/ /ms;
 #    $request->print('match='.$1."<br>\n");  #    $request->print('match='.$1."<br />\n");
 # }  # }
 # $companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;  # $companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;
  $studentTable.='&nbsp;<b>'.$title.'</b>&nbsp;<br>&nbsp;<b>Correct answer:</b><br>'.$companswer;   $studentTable.='&nbsp;<b>'.$title.'</b>&nbsp;<br />&nbsp;<b>'.&mt('Correct answer').':</b><br />'.$companswer;
     }      }
   
     my %record = &Apache::lonnet::restore($symbx,$ENV{'request.course.id'},$udom,$uname);      my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
   
     if ($ENV{'form.lastSub'} eq 'datesub') {      if ($env{'form.lastSub'} eq 'datesub') {
  if ($record{'version'} eq '') {   if ($record{'version'} eq '') {
     $studentTable.='<br />&nbsp;<font color="red">No recorded submission for this problem</font><br />';      $studentTable.='<br />&nbsp;<span class="LC_warning">'.&mt('No recorded submission for this problem.').'</span><br />';
  } else {   } else {
     my %responseType = ();      my %responseType = ();
     foreach my $partid (@{$parts}) {      foreach my $partid (@{$parts}) {
Line 3184  sub displayPage { Line 4436  sub displayPage {
     $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' : '');
  $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');
     
     }      }
     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 3205  sub displayPage { Line 4459  sub displayPage {
         $curRes = $iterator->next();          $curRes = $iterator->next();
     }      }
   
     $navmap->untieHashes();      $studentTable.=
           '</table>'."\n".
     $studentTable.='</td></tr></table></td></tr></table>'."\n".          '<input type="button" value="'.&mt('Save').'" '.
  '<input type="button" value="Save" '.          'onclick="javascript:checkSubmitPage(this.form,'.$question.');" />'.
  'onClick="javascript:checkSubmitPage(this.form,'.$question.');" TARGET=_self />'.          '</form>'."\n";
  '</form>'."\n";  
     $studentTable.=&show_grading_menu_form($symb,$url);  
     $request->print($studentTable);      $request->print($studentTable);
   
     return '';      return '';
Line 3219  sub displayPage { Line 4471  sub displayPage {
   
 sub displaySubByDates {  sub displaySubByDates {
     my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;      my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;
     my $studentTable='<table border="0" width="100%"><tr><td bgcolor="#777777">'.      my $isCODE=0;
  '<table border="0" width="100%"><tr bgcolor="#e6ffff">'.      my $isTask = ($symb =~/\.task$/);
  '<td><b>Date/Time</b></td>'.      if (exists($record->{'resource.CODE'})) { $isCODE=1; }
  '<td><b>Submission</b></td>'.      my $studentTable=&Apache::loncommon::start_data_table().
  '<td><b>Status&nbsp;</b></td></tr>';   &Apache::loncommon::start_data_table_header_row().
    '<th>'.&mt('Date/Time').'</th>'.
    ($isCODE?'<th>'.&mt('CODE').'</th>':'').
    '<th>'.&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;<font color="red">Nothing submitted - no attempts</font><br />';   return '<br />&nbsp;<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br />';
     }      }
   
       my $interaction;
       my $no_increment = 1;
     for ($version=1;$version<=$$record{'version'};$version++) {      for ($version=1;$version<=$$record{'version'};$version++) {
  my $timestamp = scalar(localtime($$record{$version.':timestamp'}));   my $timestamp = 
  $studentTable.='<tr bgcolor="#ffffff" valign="top"><td>'.$timestamp.'</td>';      &Apache::lonlocal::locallocaltime($$record{$version.':timestamp'});
    if (exists($$record{$version.':resource.0.version'})) {
       $interaction = $$record{$version.':resource.0.version'};
    }
   
    my $where = ($isTask ? "$version:resource.$interaction"
                : "$version:resource");
    $studentTable.=&Apache::loncommon::start_data_table_row().
       '<td>'.$timestamp.'</td>';
    if ($isCODE) {
       $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</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 = sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys);              my $hidden;
               if (($$record{$version.':resource.'.$partid.'.type'} eq 'anonsurvey') ||
                   ($$record{$version.':resource.'.$partid.'.type'} eq 'anonsurveycred')) {
                   $hidden = 1;
               }
       my @matchKey = ($isTask ? sort(grep /^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys)
               : 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,undef,$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 '') {
     my ($responseId)=($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/);                      
     $displaySub[0].='<b>Part:</b>&nbsp;'.$display_part.'&nbsp;';      my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
     $displaySub[0].='<font color="#999999">(ID&nbsp;'.                 : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
  $responseId.')</font>&nbsp;<b>';                      $displaySub[0].='<span class="LC_nobreak"';
     if ($$record{"$version:resource.$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{"$version:resource.$partid.tries"};                                     .' <b>';
     }                      if ($hidden) {
     my $responseType=$responseType->{$partid}->{$responseId};                          $displaySub[0].= &mt('Anonymous Survey').'</b>';
     if (!exists($orders{$partid})) { $orders{$partid}={}; }                      } else {
     if (!exists($orders{$partid}->{$responseId})) {          if ($$record{"$where.$partid.tries"} eq '') {
  $orders{$partid}->{$responseId}=      $displaySub[0].=&mt('Trial not counted');
     &get_order($partid,$responseId,$symb,$uname,$udom);          } else {
     }      $displaySub[0].=&mt('Trial: [_1]',
     $displaySub[0].='</b>&nbsp; '.      $$record{"$where.$partid.tries"});
  &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:").'<br />';          }
  }          my $responseType=($isTask ? 'Task'
     }                                                : $responseType->{$partid}->{$responseId});
     if (exists $$record{"$version:resource.$partid.award"}) {          if (!exists($orders{$partid})) { $orders{$partid}={}; }
  $displaySub[1].='<b>Part:</b>&nbsp;'.$display_part.' &nbsp;'.          if (!exists($orders{$partid}->{$responseId})) {
     lc($$record{"$version:resource.$partid.award"}).' '.      $orders{$partid}->{$responseId}=
     $mark{$$record{"$version:resource.$partid.solved"}}.          &get_order($partid,$responseId,$symb,$uname,$udom,
                                              $no_increment);
           }
           $displaySub[0].='</b></span>'; # /nobreak
           $displaySub[0].='&nbsp; '.
       &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom).'<br />';
                       }
    }
       }
       if (exists($$record{"$where.$partid.checkedin"})) {
    $displaySub[1].=&mt('Checked in by [_1] into slot [_2]',
       $$record{"$where.$partid.checkedin"},
       $$record{"$where.$partid.checkedin.slot"}).
    '<br />';
       }
       if (exists $$record{"$where.$partid.award"}) {
    $displaySub[1].='<b>'.&mt('Part:').'</b>&nbsp;'.$display_part.' &nbsp;'.
       lc($$record{"$where.$partid.award"}).' '.
       $mark{$$record{"$where.$partid.solved"}}.
     '<br />';      '<br />';
     }      }
     if (exists $$record{"$version:resource.$partid.regrader"}) {      if (exists $$record{"$where.$partid.regrader"}) {
  $displaySub[2].=$$record{"$version:resource.$partid.regrader"}.   $displaySub[2].=$$record{"$where.$partid.regrader"}.
       ' (<b>'.&mt('Part').':</b> '.$display_part.')';
       } elsif ($$record{"$version:resource.$partid.regrader"} =~ /\S/) {
    $displaySub[2].=
       $$record{"$version:resource.$partid.regrader"}.
     ' (<b>'.&mt('Part').':</b> '.$display_part.')';      ' (<b>'.&mt('Part').':</b> '.$display_part.')';
     }      }
  }   }
Line 3280  sub displaySubByDates { Line 4580  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"};
     my $getsec    = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'};      my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
     my $pageTitle = $ENV{'form.page'};      my $pageTitle = $env{'form.page'};
     my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');      my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
     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('<font color="red">Unable to modify requested student.('.$ENV{'form.student'}.'</font>');   $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'},$ENV{'form.url'}));  
  return;   return;
     }      }
     my $result='<h3><font color="#339933">&nbsp;'.$ENV{'form.title'}.'</font></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();
     my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $ENV{'form.page'});      unless (ref($navmap)) {
           $request->print(&navmap_errormsg());
           return;
       }
       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) {
    $request->print('<span class="LC_warning">'.&mt('Unable to grade requested sequence ([_1]).',$resUrl).'</span>');
    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"
Line 3331  sub updateGradeByPage { Line 4640  sub updateGradeByPage {
         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() && !$curRes->randomout) {          if (ref($curRes) && $curRes->is_problem()) {
     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,&nbsp;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 $aggregateflag = 0;
     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};
   
  my $wgt = $ENV{'form.WGT'.$question.'_'.$partid} != 0 ?    my $wgt = $env{'form.WGT'.$question.'_'.$partid} != 0 ? 
     $ENV{'form.WGT'.$question.'_'.$partid} : 1;      $env{'form.WGT'.$question.'_'.$partid} : 1;
  my $partial = $newpts/$wgt;   my $partial = $newpts/$wgt;
  my $score;   my $score;
  if ($partial > 0) {   if ($partial > 0) {
Line 3354  sub updateGradeByPage { Line 4669  sub updateGradeByPage {
  } elsif ($newpts ne '') { #empty is taken as 0   } elsif ($newpts ne '') { #empty is taken as 0
     $score = 'incorrect_by_override';      $score = 'incorrect_by_override';
  }   }
  my $dropMenu = $ENV{'form.GD_SEL'.$question.'_'.$partid};   my $dropMenu = $env{'form.GD_SEL'.$question.'_'.$partid};
  if ($dropMenu eq 'excused') {   if ($dropMenu eq 'excused') {
     $partial = '';      $partial = '';
     $score = 'excused';      $score = 'excused';
  } elsif ($dropMenu eq 'reset status'   } elsif ($dropMenu eq 'reset status'
  && $ENV{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists   && $env{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists
     $newrecord{'resource.'.$partid.'.tries'} = 0;      $newrecord{'resource.'.$partid.'.tries'} = 0;
     $newrecord{'resource.'.$partid.'.solved'} = '';      $newrecord{'resource.'.$partid.'.solved'} = '';
     $newrecord{'resource.'.$partid.'.award'} = '';      $newrecord{'resource.'.$partid.'.award'} = '';
     $newrecord{'resource.'.$partid.'.awarded'} = 0;      $newrecord{'resource.'.$partid.'.awarded'} = 0;
     $newrecord{'resource.'.$partid.'.regrader'} = "$ENV{'user.name'}:$ENV{'user.domain'}";      $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}";
     $changeflag++;      $changeflag++;
     $newpts = '';      $newpts = '';
  }                      
  my $display_part=&get_display_part($partid,undef,                      my $aggtries =  $env{'form.aggtries'.$question.'_'.$partid};
    $curRes->symb());                      my $totaltries = $env{'form.totaltries'.$question.'_'.$partid};
  my $oldstatus = $ENV{'form.solved'.$question.'_'.$partid};                      my $solvedstatus = $env{'form.solved'.$question.'_'.$partid};
  $displayPts[0].='&nbsp;<b>Part:</b> '.$display_part.' = '.                      if ($aggtries > 0) {
                           &decrement_aggs($symbx,$partid,\%aggregate,$aggtries,$totaltries,$solvedstatus);
                           $aggregateflag = 1;
                       }
    }
    my $display_part=&get_display_part($partid,$curRes->symb());
    my $oldstatus = $env{'form.solved'.$question.'_'.$partid};
    $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++;
  next if ($dropMenu eq 'reset status' || ($newpts == $oldpts && $score ne 'excused'));   next if ($dropMenu eq 'reset status' || ($newpts eq $oldpts && $score ne 'excused'));
   
  $newrecord{'resource.'.$partid.'.awarded'}  = $partial if $partial ne '';   $newrecord{'resource.'.$partid.'.awarded'}  = $partial if $partial ne '';
  $newrecord{'resource.'.$partid.'.solved'}   = $score if $score ne '';   $newrecord{'resource.'.$partid.'.solved'}   = $score if $score ne '';
  $newrecord{'resource.'.$partid.'.regrader'} = "$ENV{'user.name'}:$ENV{'user.domain'}"   $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}"
     if (scalar(keys(%newrecord)) > 0);      if (scalar(keys(%newrecord)) > 0);
   
  $changeflag++;   $changeflag++;
     }      }
     if (scalar(keys(%newrecord)) > 0) {      if (scalar(keys(%newrecord)) > 0) {
  &Apache::lonnet::cstore(\%newrecord,$symbx,$ENV{'request.course.id'},   my %record = 
       &Apache::lonnet::restore($symbx,$env{'request.course.id'},
        $udom,$uname);
   
    if (&Apache::lonnet::validCODE($env{'form.CODE'})) {
       $newrecord{'resource.CODE'} = $env{'form.CODE'};
    } elsif (&Apache::lonnet::validCODE($record{'resource.CODE'})) {
       $newrecord{'resource.CODE'} = '';
    }
    &Apache::lonnet::cstore(\%newrecord,$symbx,$env{'request.course.id'},
  $udom,$uname);   $udom,$uname);
    %record = &Apache::lonnet::restore($symbx,
      $env{'request.course.id'},
      $udom,$uname);
    &check_and_remove_from_queue($parts,\%record,undef,$symbx,
        $cdom,$cnum,$udom,$uname);
     }      }
       
               if ($aggregateflag) {
                   &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
                         $env{'course.'.$env{'request.course.id'}.'.domain'},
                         $env{'course.'.$env{'request.course.id'}.'.num'});
               }
   
     $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();
     }      }
   
     $navmap->untieHashes();      $studentTable.=&Apache::loncommon::end_data_table();
       my $grademsg=($changeflag == 0 ? &mt('No score was changed or updated.') :
     $studentTable.='</td></tr></table></td></tr></table>';    &mt('The scores were changed for [quant,_1,problem].',
     $studentTable.=&show_grading_menu_form($ENV{'form.symb'},$ENV{'form.url'});    $changeflag));
     my $grademsg=($changeflag == 0 ? 'No score was changed or updated.' :  
   'The scores were changed for '.  
   $changeflag.' problem'.($changeflag == 1 ? '.' : 's.'));  
     $request->print($grademsg.$studentTable);      $request->print($grademsg.$studentTable);
   
     return '';      return '';
Line 3418  sub updateGradeByPage { Line 4756  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 ---------
   
   =pod
   
   =head1 Bubble sheet grading routines
   
     For this documentation:
   
      'scanline' refers to the full line of characters
      from the file that we are parsing that represents one entire sheet
   
      'bubble line' refers to the data
      representing the line of bubbles that are on the physical bubble sheet
   
   
   The overall process is that a scanned in bubble sheet data is uploaded
   into a course. When a user wants to grade, they select a
   sequence/folder of resources, a file of bubble sheet info, and pick
   one of the predefined configurations for what each scanline looks
   like.
   
   Next each scanline is checked for any errors of either 'missing
   bubbles' (it's an error because it may have been mis-scanned
   because too light bubbling), 'double bubble' (each bubble line should
   have no more that one letter picked), invalid or duplicated CODE,
   invalid student/employee ID
   
   If the CODE option is used that determines the randomization of the
   homework problems, either way the student/employee ID is looked up into a
   username:domain.
   
   During the validation phase the instructor can choose to skip scanlines. 
   
   After the validation phase, there are now 3 bubble sheet files
   
     scantron_original_filename (unmodified original file)
     scantron_corrected_filename (file where the corrected information has replaced the original information)
     scantron_skipped_filename (contains the exact text of scanlines that where skipped)
   
   Also there is a separate hash nohist_scantrondata that contains extra
   correction information that isn't representable in the bubble sheet
   file (see &scantron_getfile() for more information)
   
   After all scanlines are either valid, marked as valid or skipped, then
   foreach line foreach problem in the picked sequence, an ssi request is
   made that simulates a user submitting their selected letter(s) against
   the homework problem.
   
   =over 4
   
   
   
   =item defaultFormData
   
     Returns html hidden inputs used to hold context/default values.
   
    Arguments:
     $symb - $symb of the current resource 
   
   =cut
   
 sub defaultFormData {  sub defaultFormData {
     my ($symb,$url)=@_;      my ($symb)=@_;
     return '      return '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />';
       <input type="hidden" name="symb"    value="'.$symb.'" />'."\n".  
      '<input type="hidden" name="url"     value="'.$url.'" />'."\n".  
      '<input type="hidden" name="saveState" value="'.$ENV{'form.saveState'}.'" />'."\n".  
      '<input type="hidden" name="probTitle" value="'.$ENV{'form.probTitle'}.'" />'."\n";  
 }  }
   
   
   =pod 
   
   =item getSequenceDropDown
   
      Return html dropdown of possible sequences to grade
    
    Arguments:
      $symb - $symb of the current resource
      $map_error - ref to scalar which will container error if
                   $navmap object is unavailable in &getSymbMap().
   
   =cut
   
 sub getSequenceDropDown {  sub getSequenceDropDown {
     my ($request,$symb)=@_;      my ($symb,$map_error)=@_;
     my $result='<select name="selectpage">'."\n";      my $result='<select name="selectpage">'."\n";
     my ($titles,$symbx) = &getSymbMap($request);      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) {
  my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);   my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
  $result.='<option value="'.$$symbx{$_}.'" '.   $result.='<option value="'.$$symbx{$_}.'" '.
     ($$symbx{$_} =~ /$curpage$/ ? 'selected="on"' : '').      ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
     '>'.$showtitle.'</option>'."\n";      '>'.$showtitle.'</option>'."\n";
  $ctr++;   $ctr++;
     }      }
Line 3448  sub getSequenceDropDown { Line 4858  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
   
   # 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};
       }
   }
   
   
   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"};
           $subdivided_bubble_lines{$line} =
               $env{"form.scantron.sub_bubblelines.$line"};
           $responsetype_per_response{$line} =
               $env{"form.scantron.responsetype.$line"};
    $line++;
       }
   }
   
   #  Given the parsed scanline, get the response for 
   #  'answer' number n:
   
   sub get_response_bubbles {
       my ($parsed_line, $response)  = @_;
   
       my $bubble_line = $first_bubble_line{$response-1} +1;
       my $bubble_lines= $bubble_lines_per_response{$response-1};
       
       my $selected = "";
   
       for (my $bline = 0; $bline < $bubble_lines; $bline++) {
    $selected .= $$parsed_line{"scantron.$bubble_line.answer"}.":";
    $bubble_line++;
       }
       return $selected;
   }
   
   =pod 
   
   =item scantron_filenames
   
      Returns a list of the scantron files in the current course 
   
   =cut
   
 sub scantron_filenames {  sub scantron_filenames {
     my $cdom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'};      my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
     my $cname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'};      my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $getpropath = 1;
     my @files=&Apache::lonnet::dirlist('userfiles',$cdom,$cname,      my @files=&Apache::lonnet::dirlist('userfiles',$cdom,$cname,
     &Apache::loncommon::propath($cdom,$cname));                                         $getpropath);
     my @possiblenames;      my @possiblenames;
     foreach my $filename (sort(@files)) {      foreach my $filename (sort(@files)) {
  ($filename)=split(/&/,$filename);   ($filename)=split(/&/,$filename);
Line 3463  sub scantron_filenames { Line 4944  sub scantron_filenames {
     return @possiblenames;      return @possiblenames;
 }  }
   
   =pod 
   
   =item scantron_uploads
   
      Returns  html drop-down list of scantron files in current course.
   
    Arguments:
      $file2grade - filename to set as selected in the dropdown
   
   =cut
   
 sub scantron_uploads {  sub scantron_uploads {
       my ($file2grade) = @_;
     my $result= '<select name="scantron_selectfile">';      my $result= '<select name="scantron_selectfile">';
     $result.="<option></option>";      $result.="<option></option>";
     foreach my $filename (sort(&scantron_filenames())) {      foreach my $filename (sort(&scantron_filenames())) {
  $result.="<option>$filename</option>\n";   $result.="<option".($filename eq $file2grade ? ' selected="selected"':'').">$filename</option>\n";
     }      }
     $result.="</select>";      $result.="</select>";
     return $result;      return $result;
 }  }
   
   =pod 
   
   =item scantron_scantab
   
     Returns html drop down of the scantron formats in the scantronformat.tab
     file.
   
   =cut
   
 sub scantron_scantab {  sub scantron_scantab {
     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');  
     my $result='<select name="scantron_format">'."\n";      my $result='<select name="scantron_format">'."\n";
     $result.='<option></option>'."\n";      $result.='<option></option>'."\n";
     foreach my $line (<$fh>) {      my @lines = &get_scantronformat_file();
  my ($name,$descrip)=split(/:/,$line);      if (@lines > 0) {
  if ($name =~ /^\#/) { next; }          foreach my $line (@lines) {
  $result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";              next if (($line =~ /^\#/) || ($line eq ''));
       my ($name,$descrip)=split(/:/,$line);
       $result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";
           }
     }      }
     $result.='</select>'."\n";      $result.='</select>'."\n";
   
     return $result;      return $result;
 }  }
   
   =pod
   
   =item get_scantronformat_file
   
     Returns an array containing lines from the scantron format file for
     the domain of the course.
   
     If a url for a custom.tab file is listed in domain's configuration.db, 
     lines are from this file.
   
     Otherwise, if a default.tab has been published in RES space by the 
     domainconfig user, lines are from this file.
   
     Otherwise, fall back to getting lines from the legacy file on the
     local server:  /home/httpd/lonTabs/default_scantronformat.tab    
   
   =cut
   
   sub get_scantronformat_file {
       my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
       my %domconfig = &Apache::lonnet::get_dom('configuration',['scantron'],$cdom);
       my $gottab = 0;
       my @lines;
       if (ref($domconfig{'scantron'}) eq 'HASH') {
           if ($domconfig{'scantron'}{'scantronformat'} ne '') {
               my $formatfile = &Apache::lonnet::getfile($Apache::lonnet::perlvar{'lonDocRoot'}.$domconfig{'scantron'}{'scantronformat'});
               if ($formatfile ne '-1') {
                   @lines = split("\n",$formatfile,-1);
                   $gottab = 1;
               }
           }
       }
       if (!$gottab) {
           my $confname = $cdom.'-domainconfig';
           my $default = $Apache::lonnet::perlvar{'lonDocRoot'}.'/res/'.$cdom.'/'.$confname.'/default.tab';
           my $formatfile =  &Apache::lonnet::getfile($default);
           if ($formatfile ne '-1') {
               @lines = split("\n",$formatfile,-1);
               $gottab = 1;
           }
       }
       if (!$gottab) {
           my @domains = &Apache::lonnet::current_machine_domains();
           if (grep(/^\Q$cdom\E$/,@domains)) {
               my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
               @lines = <$fh>;
               close($fh);
           } else {
               my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/default_scantronformat.tab');
               @lines = <$fh>;
               close($fh);
           }
       }
       return @lines;
   }
   
   =pod 
   
   =item scantron_CODElist
   
     Returns html drop down of the saved CODE lists from current course,
     generated from earlier printings.
   
   =cut
   
 sub scantron_CODElist {  sub scantron_CODElist {
     my $cdom = $ENV{'course.'.$ENV{'request.course.id'}.'.domain'};      my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
     my $cnum = $ENV{'course.'.$ENV{'request.course.id'}.'.num'};      my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
     my @names=&Apache::lonnet::getkeys('CODEs',$cdom,$cnum);      my @names=&Apache::lonnet::getkeys('CODEs',$cdom,$cnum);
     my $namechoice='<option></option>';      my $namechoice='<option></option>';
     foreach my $name (sort(@names)) {      foreach my $name (sort {uc($a) cmp uc($b)} @names) {
  if ($name =~ /^error: 2 /) { next; }   if ($name =~ /^error: 2 /) { next; }
    if ($name =~ /^type\0/) { next; }
  $namechoice.='<option value="'.$name.'">'.$name.'</option>';   $namechoice.='<option value="'.$name.'">'.$name.'</option>';
     }      }
     $namechoice='<select name="scantron_CODElist">'.$namechoice.'</select>';      $namechoice='<select name="scantron_CODElist">'.$namechoice.'</select>';
     return $namechoice;      return $namechoice;
 }  }
   
   =pod 
   
   =item scantron_CODEunique
   
     Returns the html for "Each CODE to be used once" radio.
   
   =cut
   
 sub scantron_CODEunique {  sub scantron_CODEunique {
     my $result='<nobr>      my $result='<span class="LC_nobreak">
                  <input type="radio" name="scantron_CODEunique"                   <label><input type="radio" name="scantron_CODEunique"
                         value="Yes" checked="on" /> Yes                          value="yes" checked="checked" />'.&mt('Yes').' </label>
                 </nobr>                  </span>
                 <nobr>                  <span class="LC_nobreak">
                  <input type="radio" name="scantron_CODEunique"                   <label><input type="radio" name="scantron_CODEunique"
                         value="No" /> No                          value="no" />'.&mt('No').' </label>
                 </nobr>';                  </span>';
     return $result;      return $result;
 }  }
   
   =pod 
   
   =item scantron_selectphase
   
     Generates the initial screen to start the bubble sheet process.
     Allows for - starting a grading run.
                - downloading existing scan data (original, corrected
                                                   or skipped info)
   
                - uploading new scan data
   
    Arguments:
     $r          - The Apache request object
     $file2grade - name of the file that contain the scanned data to score
   
   =cut
   
 sub scantron_selectphase {  sub scantron_selectphase {
     my ($r) = @_;      my ($r,$file2grade,$symb) = @_;
     my ($symb,$url)=&get_symb_and_url($r);  
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $sequence_selector=&getSequenceDropDown($r,$symb);      my $map_error;
     my $default_form_data=&defaultFormData($symb,$url);      my $sequence_selector=&getSequenceDropDown($symb,\$map_error);
     my $grading_menu_button=&show_grading_menu_form($symb,$url);      if ($map_error) {
     my $file_selector=&scantron_uploads();          $r->print('<br />'.&navmap_errormsg().'<br />');
           return;
       }
       my $default_form_data=&defaultFormData($symb);
       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;
     #FIXME allow instructor to be able to download the scantron file  
     # and to upload it,  
     $result.= <<SCANTRONFORM;  
     <table width="100%" border="0">  
     <tr>  
       <td bgcolor="#777777">  
        <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">  
        <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>  
                 <input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> Do only previously skipped records <br />  
                 <input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> Remove all exisiting corrections  
     </td>  
           </tr>  
           <tr bgcolor="#ffffe6">  
             <td colspan="2">  
               <input type="submit" value="Validate Scantron Records" />  
             </td>  
           </tr>  
         </table>  
        </form>  
       </td>  
     </tr>  
 SCANTRONFORM  
      
     $r->print($result);  
   
     if (&Apache::lonnet::allowed('usc',$ENV{'request.role.domain'}) ||      $ssi_error = 0;
         &Apache::lonnet::allowed('usc',$ENV{'request.course.id'})) {  
   
         $r->print(<<SCANTRONFORM);      if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
     <tr>          &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
       <td bgcolor="#777777">  
         <table width="100%" border="0">   # Chunk of form to prompt for a scantron file upload.
           <tr bgcolor="#e6ffff">  
             <td>          $r->print('
               &nbsp;<b>Specify a Scantron data file to upload.</b>      <br />
             </td>      '.&Apache::loncommon::start_data_table('LC_scantron_action').'
           </tr>         '.&Apache::loncommon::start_data_table_header_row().'
           <tr bgcolor="#ffffe6">              <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>              <td>
 SCANTRONFORM  ');
     my $default_form_data=&defaultFormData(&get_symb_and_url($r,1));      my $default_form_data=&defaultFormData($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'};
     $r->print(<<UPLOAD);      $r->print(&Apache::lonhtmlcommon::scripttag('
               <script type="text/javascript" language="javascript">  
     function checkUpload(formname) {      function checkUpload(formname) {
  if (formname.upfile.value == "") {   if (formname.upfile.value == "") {
     alert("Please use the browse button to select a file from your local directory.");      alert("'.&mt('Please use the browse button to select a file from your local directory.').'");
     return false;      return false;
  }   }
  formname.submit();   formname.submit();
     }      }'));
               </script>      $r->print('
                 <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
               <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'>                  '.$default_form_data.'
                 $default_form_data                  <input name="courseid" type="hidden" value="'.$cnum.'" />
                 <input name='courseid' type='hidden' value='$cnum' />                  <input name="domainid" type="hidden" value="'.$cdom.'" />
                 <input name='domainid' type='hidden' value='$cdom' />                  <input name="command" value="scantronupload_save" type="hidden" />
                 <input name='command' value='scantronupload_save' type='hidden' />                  '.&mt('File to upload: [_1]','<input type="file" name="upfile" size="50" />').'
                 File to upload:<input type="file" name="upfile" size="50" />  
                 <br />                  <br />
                 <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" />                  <input type="button" onclick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
               </form>                </form>
 UPLOAD  ');
   
         $r->print(<<SCANTRONFORM);          $r->print('
             </td>              </td>
           </tr>         '.&Apache::loncommon::end_data_table_row().'
         </table>         '.&Apache::loncommon::end_data_table().'
       </td>  ');
     </tr>      }
 SCANTRONFORM  
     }      # Chunk of form to prompt for a file to grade and how:
     $r->print(<<SCANTRONFORM);  
     <tr>      $result.= '
       <td bgcolor="#777777">      <br />
         <form action='/adm/grades' name='scantron_download'>      <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">
           <input type="hidden" name="command" value="scantron_download" />      <input type="hidden" name="command" value="scantron_warning" />
           <table width="100%" border="0">      '.$default_form_data.'
             <tr bgcolor="#e6ffff">      '.&Apache::loncommon::start_data_table('LC_scantron_action').'
               <td colspan="2">         '.&Apache::loncommon::start_data_table_header_row().'
                 &nbsp;<b>Download a scoring office file</b>              <th colspan="2">
               </td>                &nbsp;'.&mt('Specify file and which Folder/Sequence to grade').'
             </tr>              </th>
             <tr bgcolor="#ffffe6">         '.&Apache::loncommon::end_data_table_header_row().'
               <td> Filename of scoring office file: </td><td> $file_selector </td>         '.&Apache::loncommon::start_data_table_row().'
             </tr>              <td> '.&mt('Sequence to grade:').' </td><td> '.$sequence_selector.' </td>
             <tr bgcolor="#ffffe6">         '.&Apache::loncommon::end_data_table_row().'
               <td colspan="2">         '.&Apache::loncommon::start_data_table_row().'
                 <input type="submit" value="Show List of Files" />              <td> '.&mt('Filename of bubblesheet data file:').' </td><td> '.$file_selector.' </td>
               </td>         '.&Apache::loncommon::end_data_table_row().'
             </tr>         '.&Apache::loncommon::start_data_table_row().'
           </table>              <td> '.&mt('Format of bubblesheet data file:').' </td><td> '.$format_selector.' </td>
         </form>         '.&Apache::loncommon::end_data_table_row().'
       </td>         '.&Apache::loncommon::start_data_table_row().'
     </tr>              <td> '.&mt('Saved CODEs to validate against:').' </td><td> '.$CODE_selector.' </td>
 SCANTRONFORM         '.&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);
   
   
     $r->print(<<SCANTRONFORM);  
   </table>  
 </form>  
 $grading_menu_button  
 SCANTRONFORM  
   
     return      # Chunk of the form that prompts to view a scoring office file,
       # corrected file, skipped records in a file.
   
       $r->print('
      <br />
      <form action="/adm/grades" name="scantron_download">
        '.$default_form_data.'
        <input type="hidden" name="command" value="scantron_download" />
        '.&Apache::loncommon::start_data_table('LC_scantron_action').'
          '.&Apache::loncommon::start_data_table_header_row().'
                 <th>
                   &nbsp;'.&mt('Download a scoring office file').'
                 </th>
          '.&Apache::loncommon::end_data_table_header_row().'
          '.&Apache::loncommon::start_data_table_row().'
                 <td> '.&mt('Filename of scoring office file: [_1]',$file_selector).' 
                   <br />
                   <input type="submit" value="'.&mt('Download: Show List of Associated Files').'" />
          '.&Apache::loncommon::end_data_table_row().'
        '.&Apache::loncommon::end_data_table().'
      </form>
      <br />
   ');
   
       &Apache::lonpickcode::code_list($r,2);
   
       $r->print('<br /><form method="post" name="checkscantron">'.
                $default_form_data."\n".
                &Apache::loncommon::start_data_table('LC_scantron_action')."\n".
                &Apache::loncommon::start_data_table_header_row()."\n".
                '<th colspan="2">
                 &nbsp;'.&mt('Review bubblesheet data and submissions for a previously graded folder/sequence')."\n".
                '</th>'."\n".
                 &Apache::loncommon::end_data_table_header_row()."\n".
                 &Apache::loncommon::start_data_table_row()."\n".
                 '<td> '.&mt('Graded folder/sequence:').' </td>'."\n".
                 '<td> '.$sequence_selector.' </td>'.
                 &Apache::loncommon::end_data_table_row()."\n".
                 &Apache::loncommon::start_data_table_row()."\n".
                 '<td> '.&mt('Filename of scoring office file:').' </td>'."\n".
                 '<td> '.$file_selector.' </td>'."\n".
                 &Apache::loncommon::end_data_table_row()."\n".
                 &Apache::loncommon::start_data_table_row()."\n".
                 '<td> '.&mt('Format of data file:').' </td>'."\n".
                 '<td> '.$format_selector.' </td>'."\n".
                 &Apache::loncommon::end_data_table_row()."\n".
                 &Apache::loncommon::start_data_table_row()."\n".
                 '<td> '.&mt('Options').' </td>'."\n".
                 '<td> <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> '.&mt('Skip hidden resources').'</label></td>'.
                 &Apache::loncommon::end_data_table_row()."\n".
                 &Apache::loncommon::start_data_table_row()."\n".
                 '<td colspan="2">'."\n".
                 '<input type="hidden" name="command" value="checksubmissions" />'."\n".
                 '<input type="submit" value="'.&mt('Review Bubblesheet Data and Submission Records').'" />'."\n".
                 '</td>'."\n".
                 &Apache::loncommon::end_data_table_row()."\n".
                 &Apache::loncommon::end_data_table()."\n".
                 '</form><br />');
       return;
 }  }
   
   =pod
   
   =item get_scantron_config
   
      Parse and return the scantron configuration line selected as a
      hash of configuration file fields.
   
    Arguments:
       which - the name of the configuration to parse from the file.
   
   
    Returns:
               If the named configuration is not in the file, an empty
               hash is returned.
       a hash with the fields
         name         - internal name for the this configuration setup
         description  - text to display to operator that describes this config
         CODElocation - if 0 or the string 'none'
                             - no CODE exists for this config
                        if -1 || the string 'letter'
                             - a CODE exists for this config and is
                               a string of letters
                        Unsupported value (but planned for future support)
                             if a positive integer
                                  - The CODE exists as the first n items from
                                    the question section of the form
                             if the string 'number'
                                  - The CODE exists for this config and is
                                    a string of numbers
         CODEstart   - (only matter if a CODE exists) column in the line where
                        the CODE starts
         CODElength  - length of the CODE
         IDstart     - column where the student/employee ID starts
         IDlength    - length of the student/employee ID info
         Qstart      - column where the information from the bubbled
                       'questions' start
         Qlength     - number of columns comprising a single bubble line from
                       the sheet. (usually either 1 or 10)
         Qon         - either a single character representing the character used
                       to signal a bubble was chosen in the positional setup, or
                       the string 'letter' if the letter of the chosen bubble is
                       in the final, or 'number' if a number representing the
                       chosen bubble is in the file (1->A 0->J)
         Qoff        - the character used to represent that a bubble was
                       left blank
         PaperID     - if the scanning process generates a unique number for each
                       sheet scanned the column that this ID number starts in
         PaperIDlength - number of columns that comprise the unique ID number
                         for the sheet of paper
         FirstName   - column that the first name starts in
         FirstNameLength - number of columns that the first name spans
    
         LastName    - column that the last name starts in
         LastNameLength - number of columns that the last name spans
   
   =cut
   
 sub get_scantron_config {  sub get_scantron_config {
     my ($which) = @_;      my ($which) = @_;
     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');      my @lines = &get_scantronformat_file();
     my %config;      my %config;
     #FIXME probably should move to XML it has already gotten a bit much now      #FIXME probably should move to XML it has already gotten a bit much now
     foreach my $line (<$fh>) {      foreach my $line (@lines) {
  my ($name,$descrip)=split(/:/,$line);   my ($name,$descrip)=split(/:/,$line);
  if ($name ne $which ) { next; }   if ($name ne $which ) { next; }
  chomp($line);   chomp($line);
Line 3674  sub get_scantron_config { Line 5355  sub get_scantron_config {
  $config{'IDstart'}=$config[5];   $config{'IDstart'}=$config[5];
  $config{'IDlength'}=$config[6];   $config{'IDlength'}=$config[6];
  $config{'Qstart'}=$config[7];   $config{'Qstart'}=$config[7];
  $config{'Qlength'}=$config[8];    $config{'Qlength'}=$config[8];
  $config{'Qoff'}=$config[9];   $config{'Qoff'}=$config[9];
  $config{'Qon'}=$config[10];   $config{'Qon'}=$config[10];
  $config{'PaperID'}=$config[11];   $config{'PaperID'}=$config[11];
Line 3688  sub get_scantron_config { Line 5369  sub get_scantron_config {
     return %config;      return %config;
 }  }
   
   =pod 
   
   =item username_to_idmap
   
       creates a hash keyed by student/employee ID with values of the corresponding
       student username:domain.
   
     Arguments:
   
       $classlist - reference to the class list hash. This is a hash
                    keyed by student name:domain  whose elements are references
                    to arrays containing various chunks of information
                    about the student. (See loncoursedata for more info).
   
     Returns
       %idmap - the constructed hash
   
   =cut
   
 sub username_to_idmap {  sub username_to_idmap {
     my ($classlist)= @_;      my ($classlist)= @_;
     my %idmap;      my %idmap;
Line 3698  sub username_to_idmap { Line 5398  sub username_to_idmap {
     return %idmap;      return %idmap;
 }  }
   
   =pod
   
   =item scantron_fixup_scanline
   
      Process a requested correction to a scanline.
   
     Arguments:
       $scantron_config   - hash from &get_scantron_config()
       $scan_data         - hash of correction information 
                             (see &scantron_getfile())
       $line              - existing scanline
       $whichline         - line number of the passed in scanline
       $field             - type of change to process 
                            (either 
                             'ID'     -> correct the student/employee ID
                             'CODE'   -> correct the CODE
                             'answer' -> fixup the submitted answers)
       
      $args               - hash of additional info,
                             - 'ID' 
                                  'newid' -> studentID to use in replacement
                                             of existing one
                             - 'CODE' 
                                  'CODE_ignore_dup' - set to true if duplicates
                                                      should be ignored.
                          'CODE' - is new code or 'use_unfound'
                                           if the existing unfound code should
                                           be used as is
                             - 'answer'
                                  'response' - new answer or 'none' if blank
                                  'question' - the bubble line to change
                                  'questionnum' - the question identifier,
                                                  may include subquestion. 
   
     Returns:
       $line - the modified scanline
   
     Side effects: 
       $scan_data - may be updated
   
   =cut
   
   
 sub scantron_fixup_scanline {  sub scantron_fixup_scanline {
     my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;      my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
     if ($field eq 'ID') {      if ($field eq 'ID') {
Line 3736  sub scantron_fixup_scanline { Line 5479  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 {
     substr($answer,$args->{'response'},1)=$on;      if ($on eq 'letter') {
    my @alphabet=('A'..'Z');
    $answer=$alphabet[$args->{'response'}];
       } elsif ($on eq 'number') {
    $answer=$args->{'response'}+1;
    if ($answer == 10) { $answer = '0'; }
       } else {
    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 3748  sub scantron_fixup_scanline { Line 5499  sub scantron_fixup_scanline {
     return $line;      return $line;
 }  }
   
   =pod
   
   =item scan_data
   
       Edit or look up  an item in the scan_data hash.
   
     Arguments:
       $scan_data  - The hash (see scantron_getfile)
       $key        - shorthand of the key to edit (actual key is
                     scantronfilename_key).
       $data        - New value of the hash entry.
       $delete      - If true, the entry is removed from the hash.
   
     Returns:
       The new value of the hash table field (undefined if deleted).
   
   =cut
   
   
 sub scan_data {  sub scan_data {
     my ($scan_data,$key,$value,$delete)=@_;      my ($scan_data,$key,$value,$delete)=@_;
     my $filename=$ENV{'form.scantron_selectfile'};      my $filename=$env{'form.scantron_selectfile'};
     if (defined($value)) {      if (defined($value)) {
  $scan_data->{$filename.'_'.$key} = $value;   $scan_data->{$filename.'_'.$key} = $value;
     }      }
Line 3758  sub scan_data { Line 5528  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 
   
   =item scantron_parse_scanline
   
     Decodes a scanline from the selected scantron file
   
    Arguments:
       line             - The text of the scantron file line to process
       whichline        - Line number
       scantron_config  - Hash describing the format of the scantron lines.
       scan_data        - Hash of extra information about the scanline
                          (see scantron_getfile for more information)
       just_header      - True if should not process question answers but only
                          the stuff to the left of the answers.
    Returns:
      Hash containing the result of parsing the scanline
   
      Keys are all proceeded by the string 'scantron.'
   
          CODE    - the CODE in use for this scanline
          useCODE - 1 if the CODE is invalid but it usage has been forced
                    by the operator
          CODE_ignore_dup - 1 if the CODE is a duplicated use when unique
                               CODEs were selected, but the usage has been
                               forced by the operator
          ID  - student/employee ID
          PaperID - if used, the ID number printed on the sheet when the 
                    paper was scanned
          FirstName - first name from the sheet
          LastName  - last name from the sheet
   
        if just_header was not true these key may also exist
   
          missingerror - a list of bubble ranges that are considered to be answers
                         to a single question that don't have any bubbles filled in.
                         Of the form questionnumber:firstbubblenumber:count.
          doubleerror  - a list of bubble ranges that are considered to be answers
                         to a single question that have more than one bubble filled in.
                         Of the form questionnumber::firstbubblenumber:count
      
                   In the above, count is the number of bubble responses in the
                   input line needed to represent the possible answers to the question.
                   e.g. a radioresponse with 15 choices in an answer sheet with 10 choices
                   per line would have count = 2.
   
          maxquest     - the number of the last bubble line that was parsed
   
          (<number> starts at 1)
          <number>.answer - zero or more letters representing the selected
                            letters from the scanline for the bubble line 
                            <number>.
                            if blank there was either no bubble or there where
                            multiple bubbles, (consult the keys missingerror and
                            doubleerror if this is an error condition)
   
   =cut
   
 sub scantron_parse_scanline {  sub scantron_parse_scanline {
     my ($line,$whichline,$scantron_config,$scan_data,$justHeader)=@_;      my ($line,$whichline,$scantron_config,$scan_data,$just_header)=@_;
   
     my %record;      my %record;
     my $questions=substr($line,$$scantron_config{'Qstart'}-1);      my $lastpos = $env{'form.scantron_maxbubble'}*$$scantron_config{'Qlength'};
     my $data=substr($line,0,$$scantron_config{'Qstart'}-1);      my $questions=substr($line,$$scantron_config{'Qstart'}-1,$lastpos);  # Answers
     if ($$scantron_config{'CODElocation'} ne 0) {      my $data=substr($line,0,$$scantron_config{'Qstart'}-1);     # earlier stuff
  if ($$scantron_config{'CODElocation'} < 0) {      if (!($$scantron_config{'CODElocation'} eq 0 ||
     $$scantron_config{'CODElocation'} eq 'none')) {
    if ($$scantron_config{'CODElocation'} < 0 ||
       $$scantron_config{'CODElocation'} eq 'letter' ||
       $$scantron_config{'CODElocation'} eq 'number') {
     $record{'scantron.CODE'}=substr($data,      $record{'scantron.CODE'}=substr($data,
     $$scantron_config{'CODEstart'}-1,      $$scantron_config{'CODEstart'}-1,
     $$scantron_config{'CODElength'});      $$scantron_config{'CODElength'});
Line 3789  sub scantron_parse_scanline { Line 5654  sub scantron_parse_scanline {
     $record{'scantron.LastName'}=      $record{'scantron.LastName'}=
  substr($data,$$scantron_config{'LastName'}-1,   substr($data,$$scantron_config{'LastName'}-1,
        $$scantron_config{'LastNamelength'});         $$scantron_config{'LastNamelength'});
     if ($justHeader) { return \%record; }      if ($just_header) { return \%record; }
   
     my @alphabet=('A'..'Z');      my @alphabet=('A'..'Z');
     my $questnum=0;      my $questnum=0;
     while ($questions) {      my $ansnum  =1; # Multiple 'answer lines'/question.
  $questnum++;  
  my $currentquest=substr($questions,0,$$scantron_config{'Qlength'});      chomp($questions); # Get rid of any trailing \n.
  substr($questions,0,$$scantron_config{'Qlength'})='';      $questions =~ s/\r$//;      # Get rid of trailing \r too (MAC or Win uploads).
  if (length($currentquest) < $$scantron_config{'Qlength'}) { next; }      while (length($questions)) {
  my @array=split($$scantron_config{'Qon'},$currentquest,-1);   my $answers_needed = $bubble_lines_per_response{$questnum};
  if (length($array[0]) eq $$scantron_config{'Qlength'}) {          my $answer_length  = ($$scantron_config{'Qlength'} * $answers_needed)
     $record{"scantron.$questnum.answer"}='';                               || 1;
     if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {          $questnum++;
  push(@{$record{"scantron.missingerror"}},$questnum);          my $quest_id = $questnum;
      }          my $currentquest = substr($questions,0,$answer_length);
  } else {          $questions       = substr($questions,$answer_length);
     $record{"scantron.$questnum.answer"}=$alphabet[length($array[0])];          if (length($currentquest) < $answer_length) { next; }
  }  
   if (scalar(@array) gt 2) {          if ($subdivided_bubble_lines{$questnum-1} =~ /,/) {
      push(@{$record{'scantron.doubleerror'}},$questnum);              my $subquestnum = 1;
      my @ans=@array;              my $subquestions = $currentquest;
      my $i=length($ans[0]);shift(@ans);              my @subanswers_needed = 
     while ($#ans) {                  split(/,/,$subdivided_bubble_lines{$questnum-1});  
   $i+=length($ans[0])+1;              foreach my $subans (@subanswers_needed) {
   $record{"scantron.$questnum.answer"}.=$alphabet[$i];                  my $subans_length =
   shift(@ans);                      ($$scantron_config{'Qlength'} * $subans)  || 1;
      }                  my $currsubquest = substr($subquestions,0,$subans_length);
   }                  $subquestions   = substr($subquestions,$subans_length);
                   $quest_id = "$questnum.$subquestnum";
                   if (($$scantron_config{'Qon'} eq 'letter') ||
                       ($$scantron_config{'Qon'} eq 'number')) {
                       $ansnum = &scantron_validator_lettnum($ansnum, 
                           $questnum,$quest_id,$subans,$currsubquest,$whichline,
                           \@alphabet,\%record,$scantron_config,$scan_data);
                   } else {
                       $ansnum = &scantron_validator_positional($ansnum,
                           $questnum,$quest_id,$subans,$currsubquest,$whichline,                        \@alphabet,\%record,$scantron_config,$scan_data);
                   }
                   $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);
               } else {
                   $ansnum = &scantron_validator_positional($ansnum,$questnum,
                       $quest_id,$answers_needed,$currentquest,$whichline,
                       \@alphabet,\%record,$scantron_config,$scan_data);
               }
           }
     }      }
     $record{'scantron.maxquest'}=$questnum;      $record{'scantron.maxquest'}=$questnum;
     return \%record;      return \%record;
 }  }
   
   sub scantron_validator_lettnum {
       my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,$whichline,
           $alphabet,$record,$scantron_config,$scan_data) = @_;
   
       # 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.
       #
   
       my $matchon;
       if ($$scantron_config{'Qon'} eq 'letter') {
           $matchon = '[A-Z]';
       } elsif ($$scantron_config{'Qon'} eq 'number') {
           $matchon = '\d';
       }
       my $occurrences = 0;
       if (($responsetype_per_response{$questnum-1} eq 'essayresponse') ||
           ($responsetype_per_response{$questnum-1} eq 'formularesponse') ||
           ($responsetype_per_response{$questnum-1} eq 'stringresponse') ||
           ($responsetype_per_response{$questnum-1} eq 'imageresponse') ||
           ($responsetype_per_response{$questnum-1} eq 'reactionresponse') ||
           ($responsetype_per_response{$questnum-1} 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;
   }
   
   sub scantron_validator_positional {
       my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,
           $whichline,$alphabet,$record,$scantron_config,$scan_data) = @_;
   
       # Otherwise there's a positional notation;
       # each bubble line requires Qlength items, and there are filled in
       # bubbles for each case where there 'Qon' characters.
       #
   
       my @array=split($$scantron_config{'Qon'},$currquest,-1);
   
       # If the split only gives us one element.. the full length of the
       # answer string, no bubbles are filled in:
   
       if ($answers_needed eq '') {
           return;
       }
   
       if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {
           for (my $ans=0; $ans<$answers_needed; $ans++ ) {
               $record->{"scantron.$ansnum.answer"}='';
               $ansnum++;
           }
           if (!&scan_data($scan_data,"$whichline.no_bubble.$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.
           #
           if (($responsetype_per_response{$questnum-1} eq 'essayresponse') ||
               ($responsetype_per_response{$questnum-1} eq 'formularesponse') ||
               ($responsetype_per_response{$questnum-1} eq 'stringresponse') ||
               ($responsetype_per_response{$questnum-1} eq 'imageresponse') ||
               ($responsetype_per_response{$questnum-1} eq 'reactionresponse') ||
               ($responsetype_per_response{$questnum-1} 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 ++;
           }
   
           my @ans=@array;
           my $i=0;
           my $increment = 0;
           while ($#ans) {
               $i+=length($ans[0]) + $increment;
               my $line   = int($i/$$scantron_config{'Qlength'} + $ansnum);
               my $bubble = $i%$$scantron_config{'Qlength'};
               $record->{"scantron.$line.answer"}.=$alphabet->[$bubble];
               shift(@ans);
               $increment = 1;
           }
           $ansnum += $answers_needed;
       }
       return $ansnum;
   }
   
   =pod
   
   =item scantron_add_delay
   
      Adds an error message that occurred during the grading phase to a
      queue of messages to be shown after grading pass is complete
   
    Arguments:
      $delayqueue  - arrary ref of hash ref of error messages
      $scanline    - the scanline that caused the error
      $errormesage - the error message
      $errorcode   - a numeric code for the error
   
    Side Effects:
      updates the $delayqueue to have a new hash ref of the error
   
   =cut
   
 sub scantron_add_delay {  sub scantron_add_delay {
     my ($delayqueue,$scanline,$errormessage,$errorcode)=@_;      my ($delayqueue,$scanline,$errormessage,$errorcode)=@_;
     push(@$delayqueue,      push(@$delayqueue,
Line 3830  sub scantron_add_delay { Line 5904  sub scantron_add_delay {
  );   );
 }  }
   
   =pod
   
   =item scantron_find_student
   
      Finds the username for the current scanline
   
     Arguments:
      $scantron_record - hash result from scantron_parse_scanline
      $scan_data       - hash of correction information 
                         (see &scantron_getfile() form more information)
      $idmap           - hash from &username_to_idmap()
      $line            - number of current scanline
    
     Returns:
      Either 'username:domain' or undef if unknown
   
   =cut
   
 sub scantron_find_student {  sub scantron_find_student {
     my ($scantron_record,$scan_data,$idmap,$line)=@_;      my ($scantron_record,$scan_data,$idmap,$line)=@_;
     my $scanID=$$scantron_record{'scantron.ID'};      my $scanID=$$scantron_record{'scantron.ID'};
Line 3844  sub scantron_find_student { Line 5936  sub scantron_find_student {
     return undef;      return undef;
 }  }
   
   =pod
   
   =item scantron_filter
   
      Filter sub for lonnavmaps, filters out hidden resources if ignore
      hidden resources was selected
   
   =cut
   
 sub scantron_filter {  sub scantron_filter {
     my ($curres)=@_;      my ($curres)=@_;
                         # randomout is dysfunctional at best for this purpose  
     if (ref($curres) && $curres->is_problem()) { #&& !$curres->randomout) {      if (ref($curres) && $curres->is_problem()) {
    # if the user has asked to not have either hidden
    # or 'randomout' controlled resources to be graded
    # don't include them
    if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
       && $curres->randomout) {
       return 0;
    }
  return 1;   return 1;
     }      }
     return 0;      return 0;
 }  }
   
   =pod
   
   =item scantron_process_corrections
   
      Gets correction information out of submitted form data and corrects
      the scanline
   
   =cut
   
 sub scantron_process_corrections {  sub scantron_process_corrections {
     my ($r) = @_;      my ($r) = @_;
     my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'});      my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
     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'};
     my $line=&scantron_get_line($scanlines,$scan_data,$which);      my $line=&scantron_get_line($scanlines,$scan_data,$which);
     my ($skip,$err,$errmsg);      my ($skip,$err,$errmsg);
     if ($ENV{'form.scantron_skip_record'}) {      if ($env{'form.scantron_skip_record'}) {
  $skip=1;   $skip=1;
     } elsif ($ENV{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) {      } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) {
  my $newstudent=$ENV{'form.scantron_username'}.':'.   my $newstudent=$env{'form.scantron_username'}.':'.
     $ENV{'form.scantron_domain'};      $env{'form.scantron_domain'};
  my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID];   my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID];
  ($line,$err,$errmsg)=   ($line,$err,$errmsg)=
     &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,      &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
      'ID',{'newid'=>$newid,       'ID',{'newid'=>$newid,
     'username'=>$ENV{'form.scantron_username'},      'username'=>$env{'form.scantron_username'},
     'domain'=>$ENV{'form.scantron_domain'}});      'domain'=>$env{'form.scantron_domain'}});
     } elsif ($ENV{'form.scantron_corrections'} =~ /^(duplicate|incorrect)CODE$/) {      } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)CODE$/) {
  my $resolution=$ENV{'form.scantron_CODE_resolution'};   my $resolution=$env{'form.scantron_CODE_resolution'};
  my $newCODE;   my $newCODE;
  my %args;   my %args;
  if      ($resolution eq 'use_unfound') {   if      ($resolution eq 'use_unfound') {
     $newCODE='use_unfound';      $newCODE='use_unfound';
  } elsif ($resolution eq 'use_found') {   } elsif ($resolution eq 'use_found') {
     $newCODE=$ENV{'form.scantron_CODE_selectedvalue'};      $newCODE=$env{'form.scantron_CODE_selectedvalue'};
  } elsif ($resolution eq 'use_typed') {   } elsif ($resolution eq 'use_typed') {
     $newCODE=$ENV{'form.scantron_CODE_newvalue'};      $newCODE=$env{'form.scantron_CODE_newvalue'};
  } elsif ($resolution =~ /^use_closest_(\d+)/) {   } elsif ($resolution =~ /^use_closest_(\d+)/) {
     $newCODE=$ENV{"form.scantron_CODE_closest_$1"};      $newCODE=$env{"form.scantron_CODE_closest_$1"};
  }   }
  if ($ENV{'form.scantron_corrections'} eq 'duplicateCODE') {   if ($env{'form.scantron_corrections'} eq 'duplicateCODE') {
     $args{'CODE_ignore_dup'}=1;      $args{'CODE_ignore_dup'}=1;
  }   }
  $args{'CODE'}=$newCODE;   $args{'CODE'}=$newCODE;
  ($line,$err,$errmsg)=   ($line,$err,$errmsg)=
     &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,      &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
      'CODE',\%args);       'CODE',\%args);
     } elsif ($ENV{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) {      } elsif ($env{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) {
  foreach my $question (split(',',$ENV{'form.scantron_questions'})) {   foreach my $question (split(',',$env{'form.scantron_questions'})) {
     ($line,$err,$errmsg)=      ($line,$err,$errmsg)=
  &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("Unable to accept last correction, an error occurred :$errmsg:");   $r->print("<span class=\"LC_warning\">Unable to accept last correction, an error occurred :$errmsg:</span>");
     } 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);
     }      }
 }  }
   
   =pod
   
   =item reset_skipping_status
   
      Forgets the current set of remember skipped scanlines (and thus
      reverts back to considering all lines in the
      scantron_skipped_<filename> file)
   
   =cut
   
 sub reset_skipping_status {  sub reset_skipping_status {
     my ($scanlines,$scan_data)=&scantron_getfile();      my ($scanlines,$scan_data)=&scantron_getfile();
     &scan_data($scan_data,'remember_skipping',undef,1);      &scan_data($scan_data,'remember_skipping',undef,1);
     &scantron_putfile(undef,$scan_data);      &scantron_putfile(undef,$scan_data);
 }  }
   
 sub allow_skipping {  =pod
   
   =item start_skipping
   
      Marks a scanline to be skipped. 
   
   =cut
   
   sub start_skipping {
     my ($scan_data,$i)=@_;      my ($scan_data,$i)=@_;
     my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));      my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
     delete($remembered{$i});      if ($env{'form.scantron_options_redo'} =~ /^redo_/) {
    $remembered{$i}=2;
       } else {
    $remembered{$i}=1;
       }
     &scan_data($scan_data,'remember_skipping',join(':',%remembered));      &scan_data($scan_data,'remember_skipping',join(':',%remembered));
 }  }
   
   =pod
   
   =item should_be_skipped
   
      Checks whether a scanline should be skipped.
   
   =cut
   
 sub should_be_skipped {  sub should_be_skipped {
     my ($scan_data,$i)=@_;      my ($scanlines,$scan_data,$i)=@_;
     if ($ENV{'form.scantron_options_redo'} !~ /^redo_/) {      if ($env{'form.scantron_options_redo'} !~ /^redo_/) {
  # not redoing old skips   # not redoing old skips
    if ($scanlines->{'skipped'}[$i]) { return 1; }
  return 0;   return 0;
     }      }
     my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));      my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
     if (exists($remembered{$i})) { return 0; }  
       if (exists($remembered{$i}) && $remembered{$i} != 2 ) {
    return 0;
       }
     return 1;      return 1;
 }  }
   
   =pod
   
   =item remember_current_skipped
   
      Discovers what scanlines are in the scantron_skipped_<filename>
      file and remembers them into scan_data for later use.
   
   =cut
   
 sub remember_current_skipped {  sub remember_current_skipped {
     my ($scanlines,$scan_data)=&scantron_getfile();      my ($scanlines,$scan_data)=&scantron_getfile();
     my %to_remember;      my %to_remember;
Line 3942  sub remember_current_skipped { Line 6103  sub remember_current_skipped {
     $to_remember{$i}=1;      $to_remember{$i}=1;
  }   }
     }      }
     &Apache::lonnet::logthis('remembering '.join(':',%to_remember));  
     &scan_data($scan_data,'remember_skipping',join(':',%to_remember));      &scan_data($scan_data,'remember_skipping',join(':',%to_remember));
     &scantron_putfile(undef,$scan_data);      &scantron_putfile(undef,$scan_data);
 }  }
   
   =pod
   
   =item check_for_error
   
       Checks if there was an error when attempting to remove a specific
       scantron_.. bubble sheet data file. Prints out an error if
       something went wrong.
   
   =cut
   
 sub check_for_error {  sub check_for_error {
     my ($r,$result)=@_;      my ($r,$result)=@_;
     if ($result ne 'ok' && $result ne 'not_found' ) {      if ($result ne 'ok' && $result ne 'not_found' ) {
  $r->print("An error occured ($result) when trying to Remove the existing corrections.");   $r->print(&mt("An error occurred ([_1]) when trying to remove the existing corrections.",$result));
     }      }
 }  }
   
   =pod
   
   =item scantron_warning_screen
   
      Interstitial screen to make sure the operator has selected the
      correct options before we start the validation phase.
   
   =cut
   
 sub scantron_warning_screen {  sub scantron_warning_screen {
     my ($button_text)=@_;      my ($button_text)=@_;
     my $title=&Apache::lonnet::gettitle($ENV{'form.selectpage'});      my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
     return (<<STUFF);      my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
       my $CODElist;
       if ($scantron_config{'CODElocation'} &&
    $scantron_config{'CODEstart'} &&
    $scantron_config{'CODElength'}) {
    $CODElist=$env{'form.scantron_CODElist'};
    if ($env{'form.scantron_CODElist'} eq '') { $CODElist='<span class="LC_warning">None</span>'; }
    $CODElist=
       '<tr><td><b>'.&mt('List of CODES to validate against:').'</b></td><td><tt>'.
       $env{'form.scantron_CODElist'}.'</tt></td></tr>';
       }
       return ('
 <p>  <p>
 <font color="red">Please double check the information  <span class="LC_warning">
                  below before clicking on '$button_text'</font>  '.&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.'
 </table>  </table>
 </font>  
 <br />  <br />
 <p> If this information is correct, please click on '$button_text'.</p>  <p> '.&mt('If this information is correct, please click on \'[_1]\'.',&mt($button_text)).'</p>
 <p> If something is incorrect, please click the 'Grading Menu' button to start over.</p>  <p> '.&mt('If something is incorrect, please click the \'Grading Menu\' button to start over.').'</p>
   
 <br />  <br />
 STUFF  ');
 }  }
   
   =pod
   
   =item scantron_do_warning
   
      Check if the operator has picked something for all required
      fields. Error out if something is missing.
   
   =cut
   
 sub scantron_do_warning {  sub scantron_do_warning {
     my ($r)=@_;      my ($r,$symb)=@_;
     my ($symb,$url)=&get_symb_and_url($r);  
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $default_form_data=&defaultFormData($symb,$url);      my $default_form_data=&defaultFormData($symb);
     $r->print(&scantron_form_start().$default_form_data);      $r->print(&scantron_form_start().$default_form_data);
     my $warning=&scantron_warning_screen('Validate Records');      if ( $env{'form.selectpage'} eq '' ||
     $r->print(<<STUFF);   $env{'form.scantron_selectfile'} eq '' ||
 $warning   $env{'form.scantron_format'} eq '' ) {
 <input type="submit" name="submit" value="Validate Records" />   $r->print("<p>".&mt('You have forgetten to specify some information. Please go Back and try again.')."</p>");
    if ( $env{'form.selectpage'} eq '') {
       $r->print('<p><span class="LC_error">'.&mt('You have not selected a Sequence to grade').'</span></p>');
    } 
    if ( $env{'form.scantron_selectfile'} eq '') {
       $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 '') {
       $r->print('<p><span class="LC_error">'.&mt('You have not selected a the format of the student\'s response data.').'</span></p>');
    } 
       } else {
    my $warning=&scantron_warning_screen('Grading: Validate Records');
    $r->print('
   '.$warning.'
   <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" />
 </form>  ');
 STUFF      }
     $r->print("<br />".&show_grading_menu_form($symb,$url)."</body></html>");      $r->print("</form><br />");
     return '';      return '';
 }  }
   
   =pod
   
   =item scantron_form_start
   
       html hidden input for remembering all selected grading options
   
   =cut
   
 sub scantron_form_start {  sub scantron_form_start {
     my ($max_bubble)=@_;      my ($max_bubble)=@_;
     my $result= <<SCANTRONFORM;      my $result= <<SCANTRONFORM;
 <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="selectpage" value="$ENV{'form.selectpage'}" />    <input type="hidden" name="selectpage" value="$env{'form.selectpage'}" />
   <input type="hidden" name="scantron_format" value="$ENV{'form.scantron_format'}" />    <input type="hidden" name="scantron_format" value="$env{'form.scantron_format'}" />
   <input type="hidden" name="scantron_selectfile" value="$ENV{'form.scantron_selectfile'}" />    <input type="hidden" name="scantron_selectfile" value="$env{'form.scantron_selectfile'}" />
   <input type="hidden" name="scantron_maxbubble" value="$max_bubble" />    <input type="hidden" name="scantron_maxbubble" value="$max_bubble" />
   <input type="hidden" name="scantron_CODElist" value="$ENV{'form.scantron_CODElist'}" />    <input type="hidden" name="scantron_CODElist" value="$env{'form.scantron_CODElist'}" />
   <input type="hidden" name="scantron_CODEunique" value="$ENV{'form.scantron_CODEunique'}" />    <input type="hidden" name="scantron_CODEunique" value="$env{'form.scantron_CODEunique'}" />
   <input type="hidden" name="scantron_options_redo" value="$ENV{'form.scantron_options_redo'}" />    <input type="hidden" name="scantron_options_redo" value="$env{'form.scantron_options_redo'}" />
   <input type="hidden" name="scantron_options_ignore" value="$ENV{'form.scantron_options_ignore'}" />    <input type="hidden" name="scantron_options_ignore" value="$env{'form.scantron_options_ignore'}" />
     <input type="hidden" name="scantron_options_hidden" value="$env{'form.scantron_options_hidden'}" />
 SCANTRONFORM  SCANTRONFORM
   
     my $line = 0;
       while (defined($env{"form.scantron.bubblelines.$line"})) {
          my $chunk =
      '<input type="hidden" name="scantron.bubblelines.'.$line.'" value="'.$env{"form.scantron.bubblelines.$line"}.'" />'."\n";
          $chunk .=
      '<input type="hidden" name="scantron.first_bubble_line.'.$line.'" value="'.$env{"form.scantron.first_bubble_line.$line"}.'" />'."\n";
          $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";
          $result .= $chunk;
          $line++;
      }
     return $result;      return $result;
 }  }
   
   =pod
   
   =item scantron_validate_file
   
       Dispatch routine for doing validation of a bubble sheet data file.
   
       Also processes any necessary information resets that need to
       occur before validation begins (ignore previous corrections,
       restarting the skipped records processing)
   
   =cut
   
 sub scantron_validate_file {  sub scantron_validate_file {
     my ($r) = @_;      my ($r,$symb) = @_;
     my ($symb,$url)=&get_symb_and_url($r);  
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $default_form_data=&defaultFormData($symb,$url);      my $default_form_data=&defaultFormData($symb);
           
     # do the detection of only doing skipped records first befroe we delete      # do the detection of only doing skipped records first befroe we delete
     # them  when doing the corrections reset      # them when doing the corrections reset
     if ($ENV{'form.scantron_options_redo'} ne 'redo_skipped_ready') {      if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') {
  &reset_skipping_status();   &reset_skipping_status();
     }      }
     if ($ENV{'form.scantron_options_redo'} eq 'redo_skipped') {      if ($env{'form.scantron_options_redo'} eq 'redo_skipped') {
  &remember_current_skipped();   &remember_current_skipped();
  &scantron_remove_file('skipped');   $env{'form.scantron_options_redo'}='redo_skipped_ready';
  $ENV{'form.scantron_options_redo'}='redo_skipped_ready';  
     }      }
   
     if ($ENV{'form.scantron_options_ignore'} eq 'ignore_corrections') {      if ($env{'form.scantron_options_ignore'} eq 'ignore_corrections') {
  &check_for_error($r,&scantron_remove_file('corrected'));   &check_for_error($r,&scantron_remove_file('corrected'));
  &check_for_error($r,&scantron_remove_file('skipped'));   &check_for_error($r,&scantron_remove_file('skipped'));
  &check_for_error($r,&scantron_remove_scan_data());   &check_for_error($r,&scantron_remove_scan_data());
  $ENV{'form.scantron_options_ignore'}='done';   $env{'form.scantron_options_ignore'}='done';
     }      }
   
     if ($ENV{'form.scantron_corrections'}) {      if ($env{'form.scantron_corrections'}) {
  &scantron_process_corrections($r);   &scantron_process_corrections($r);
     }      }
     $r->print("<p>Gathering neccessary info.</p>");$r->rflush();      $r->print('<p>'.&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($r);      my $nav_error;
       my $max_bubble=&scantron_get_maxbubble(\$nav_error);
       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;
     $r->print($result);      $r->print($result);
           
     my @validate_phases=( 'ID',      my @validate_phases=( 'sequence',
     'ID',
   'CODE',    'CODE',
   'doublebubble',    'doublebubble',
   'missingbubbles');    'missingbubbles');
     if (!$ENV{'form.validatepass'}) {      if (!$env{'form.validatepass'}) {
  $ENV{'form.validatepass'} = 0;   $env{'form.validatepass'} = 0;
     }      }
     my $currentphase=$ENV{'form.validatepass'};      my $currentphase=$env{'form.validatepass'};
   
   
     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];
  {   {
Line 4063  sub scantron_validate_file { Line 6316  sub scantron_validate_file {
     }      }
     if (!$stop) {      if (!$stop) {
  my $warning=&scantron_warning_screen('Start Grading');   my $warning=&scantron_warning_screen('Start Grading');
  $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("Alternatively, the 'Review bubblesheet data' utility (see grading menu) can be used for all students after grading is complete.").'<br /><br />'.
                     '<input type="submit" name="submit" value="'.&mt('Start Grading').'" />'.
                     '<input type="hidden" name="command" value="scantron_process" />'."\n");
     } else {      } else {
  $r->print('<input type="hidden" name="command" value="scantron_validate" />');   $r->print('<input type="hidden" name="command" value="scantron_validate" />');
  $r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />");   $r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />");
     }      }
     if ($stop) {      if ($stop) {
  $r->print('<input type="submit" name="submit" value="Continue ->" />');   if ($validate_phases[$currentphase] eq 'sequence') {
  $r->print(' using corrected info <br />');      $r->print('<input type="submit" name="submit" value="'.&mt('Ignore').' &rarr; " />');
  $r->print("<input type='submit' value='Skip' name='scantron_skip_record' />");      $r->print(' '.&mt('this error').' <br />');
  $r->print(" this scanline saving it for later.");  
       $r->print(" <p>".&mt("Or click the 'Grading Menu' button to start over.")."</p>");
    } else {
               if ($validate_phases[$currentphase] eq 'doublebubble' || $validate_phases[$currentphase] eq 'missingbubbles') {
           $r->print('<input type="button" name="submitbutton" value="'.&mt('Continue').' &rarr;" onclick="javascript:verify_bubble_radio(this.form)" />');
               } else {
                   $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,$url).      $r->print(" </form><br />");
       "</body></html>");  
     return '';      return '';
 }  }
   
   
   =pod
   
   =item scantron_remove_file
   
      Removes the requested bubble sheet data file, makes sure that
      scantron_original_<filename> is never removed
   
   
   =cut
   
 sub scantron_remove_file {  sub scantron_remove_file {
     my ($which)=@_;      my ($which)=@_;
     my $cname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'};      my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
     my $cdom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'};      my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
     my $file='scantron_';      my $file='scantron_';
     if ($which eq 'corrected' || $which eq 'skipped') {      if ($which eq 'corrected' || $which eq 'skipped') {
  $file.=$which.'_';   $file.=$which.'_';
     } else {      } else {
  return 'refused';   return 'refused';
     }      }
     $file.=$ENV{'form.scantron_selectfile'};      $file.=$env{'form.scantron_selectfile'};
     return &Apache::lonnet::removeuserfile($cname,$cdom,$file);      return &Apache::lonnet::removeuserfile($cname,$cdom,$file);
 }  }
   
   
   =pod
   
   =item scantron_remove_scan_data
   
      Removes all scan_data correction for the requested bubble sheet
      data file.  (In the case that both the are doing skipped records we need
      to remember the old skipped lines for the time being so that element
      persists for a while.)
   
   =cut
   
 sub scantron_remove_scan_data {  sub scantron_remove_scan_data {
     my $cname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'};      my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
     my $cdom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'};      my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
     my @keys=&Apache::lonnet::getkeys('nohist_scantrondata',$cdom,$cname);      my @keys=&Apache::lonnet::getkeys('nohist_scantrondata',$cdom,$cname);
     my @todelete;      my @todelete;
     my $filename=$ENV{'form.scantron_selectfile'};      my $filename=$env{'form.scantron_selectfile'};
     foreach my $key (@keys) {      foreach my $key (@keys) {
  if ($key=~/^\Q$filename\E_/) {   if ($key=~/^\Q$filename\E_/) {
     if ($ENV{'form.scantron_options_redo'} eq 'redo_skipped_ready' &&      if ($env{'form.scantron_options_redo'} eq 'redo_skipped_ready' &&
  $key=~/remember_skipping/) {   $key=~/remember_skipping/) {
  next;   next;
     }      }
Line 4116  sub scantron_remove_scan_data { Line 6407  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;
 }  }
   
   
   =pod
   
   =item scantron_getfile
   
       Fetches the requested bubble sheet data file (all 3 versions), and
       the scan_data hash
     
     Arguments:
       None
   
     Returns:
       2 hash references
   
        - first one has 
            orig      -
            corrected -
            skipped   -  each of which points to an array ref of the specified
                         file broken up into individual lines
            count     - number of scanlines
    
        - second is the scan_data hash possible keys are
          ($number refers to scanline numbered $number and thus the key affects
           only that scanline
           $bubline refers to the specific bubble line element and the aspects
           refers to that specific bubble line element)
   
          $number.user - username:domain to use
          $number.CODE_ignore_dup 
                       - ignore the duplicate CODE error 
          $number.useCODE
                       - use the CODE in the scanline as is
          $number.no_bubble.$bubline
                       - it is valid that there is no bubbled in bubble
                         at $number $bubline
          remember_skipping
                       - a frozen hash containing keys of $number and values
                         of either 
                           1 - we are on a 'do skipped records pass' and plan
                               on processing this line
                           2 - we are on a 'do skipped records pass' and this
                               scanline has been marked to skip yet again
   
   =cut
   
 sub scantron_getfile {  sub scantron_getfile {
     #FIXME really would prefer a scantron directory      #FIXME really would prefer a scantron directory
     my $cname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'};      my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
     my $cdom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'};      my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
     my $lines;      my $lines;
     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.      $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
        'scantron_orig_'.$ENV{'form.scantron_selectfile'});         'scantron_orig_'.$env{'form.scantron_selectfile'});
     my %scanlines;      my %scanlines;
     $scanlines{'orig'}=[(split("\n",$lines,-1))];      $scanlines{'orig'}=[(split("\n",$lines,-1))];
     my $temp=$scanlines{'orig'};      my $temp=$scanlines{'orig'};
     $scanlines{'count'}=$#$temp;      $scanlines{'count'}=$#$temp;
   
     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.      $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
        'scantron_corrected_'.$ENV{'form.scantron_selectfile'});         'scantron_corrected_'.$env{'form.scantron_selectfile'});
     if ($lines eq '-1') {      if ($lines eq '-1') {
  $scanlines{'corrected'}=[];   $scanlines{'corrected'}=[];
     } else {      } else {
  $scanlines{'corrected'}=[(split("\n",$lines,-1))];   $scanlines{'corrected'}=[(split("\n",$lines,-1))];
     }      }
     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.      $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
        'scantron_skipped_'.$ENV{'form.scantron_selectfile'});         'scantron_skipped_'.$env{'form.scantron_selectfile'});
     if ($lines eq '-1') {      if ($lines eq '-1') {
  $scanlines{'skipped'}=[];   $scanlines{'skipped'}=[];
     } else {      } else {
Line 4153  sub scantron_getfile { Line 6492  sub scantron_getfile {
     return (\%scanlines,\%scan_data);      return (\%scanlines,\%scan_data);
 }  }
   
   =pod
   
   =item lonnet_putfile
   
      Wrapper routine to call &Apache::lonnet::finishuserfileupload
   
    Arguments:
      $contents - data to store
      $filename - filename to store $contents into
   
    Returns:
      result value from &Apache::lonnet::finishuserfileupload
   
   =cut
   
 sub lonnet_putfile {  sub lonnet_putfile {
     my ($contents,$filename)=@_;      my ($contents,$filename)=@_;
     my $docuname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'};      my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};
     my $docudom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'};      my $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'};
     my $docuhome=$ENV{'course.'.$ENV{'request.course.id'}.'.home'};      $env{'form.sillywaytopassafilearound'}=$contents;
     $ENV{'form.sillywaytopassafilearound'}=$contents;      &Apache::lonnet::finishuserfileupload($docuname,$docudom,'sillywaytopassafilearound',$filename);
     &Apache::lonnet::finishuserfileupload($docuname,$docudom,$docuhome,'sillywaytopassafilearound',$filename);  
   
 }  }
   
   =pod
   
   =item scantron_putfile
   
       Stores the current version of the bubble sheet data files, and the
       scan_data hash. (Does not modify the original version only the
       corrected and skipped versions.
   
    Arguments:
       $scanlines - hash ref that looks like the first return value from
                    &scantron_getfile()
       $scan_data - hash ref that looks like the second return value from
                    &scantron_getfile()
   
   =cut
   
 sub scantron_putfile {  sub scantron_putfile {
     my ($scanlines,$scan_data) = @_;      my ($scanlines,$scan_data) = @_;
     #FIXME really would prefer a scantron directory      #FIXME really would prefer a scantron directory
     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'};
     if ($scanlines) {      if ($scanlines) {
  my $prefix='scantron_';   my $prefix='scantron_';
 # no need to update orig, shouldn't change  # no need to update orig, shouldn't change
 #   &lonnet_putfile(join("\n",@{$scanlines->{'orig'}}),$prefix.'orig_'.  #   &lonnet_putfile(join("\n",@{$scanlines->{'orig'}}),$prefix.'orig_'.
 #    $ENV{'form.scantron_selectfile'});  #    $env{'form.scantron_selectfile'});
  &lonnet_putfile(join("\n",@{$scanlines->{'corrected'}}),   &lonnet_putfile(join("\n",@{$scanlines->{'corrected'}}),
  $prefix.'corrected_'.   $prefix.'corrected_'.
  $ENV{'form.scantron_selectfile'});   $env{'form.scantron_selectfile'});
  &lonnet_putfile(join("\n",@{$scanlines->{'skipped'}}),   &lonnet_putfile(join("\n",@{$scanlines->{'skipped'}}),
  $prefix.'skipped_'.   $prefix.'skipped_'.
  $ENV{'form.scantron_selectfile'});   $env{'form.scantron_selectfile'});
     }      }
     &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname);      &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname);
 }  }
   
   =pod
   
   =item scantron_get_line
   
      Returns the correct version of the scanline
   
    Arguments:
       $scanlines - hash ref that looks like the first return value from
                    &scantron_getfile()
       $scan_data - hash ref that looks like the second return value from
                    &scantron_getfile()
       $i         - number of the requested line (starts at 0)
   
    Returns:
      A scanline, (either the original or the corrected one if it
      exists), or undef if the requested scanline should be
      skipped. (Either because it's an skipped scanline, or it's an
      unskipped scanline and we are not doing a 'do skipped scanlines'
      pass.
   
   =cut
   
 sub scantron_get_line {  sub scantron_get_line {
     my ($scanlines,$scan_data,$i)=@_;      my ($scanlines,$scan_data,$i)=@_;
     if (&should_be_skipped($scan_data,$i)) { return undef; }      if (&should_be_skipped($scanlines,$scan_data,$i)) { return undef; }
     if ($scanlines->{'skipped'}[$i]) { return undef; }      #if ($scanlines->{'skipped'}[$i]) { return undef; }
     if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$i];}      if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$i];}
     return $scanlines->{'orig'}[$i];       return $scanlines->{'orig'}[$i]; 
 }  }
   
   =pod
   
   =item scantron_todo_count
   
       Counts the number of scanlines that need processing.
   
    Arguments:
       $scanlines - hash ref that looks like the first return value from
                    &scantron_getfile()
       $scan_data - hash ref that looks like the second return value from
                    &scantron_getfile()
   
    Returns:
       $count - number of scanlines to process
   
   =cut
   
 sub get_todo_count {  sub get_todo_count {
     my ($scanlines,$scan_data)=@_;      my ($scanlines,$scan_data)=@_;
     my $count=0;      my $count=0;
Line 4202  sub get_todo_count { Line 6610  sub get_todo_count {
     return $count;      return $count;
 }  }
   
   =pod
   
   =item scantron_put_line
   
       Updates the 'corrected' or 'skipped' versions of the bubble sheet
       data file.
   
    Arguments:
       $scanlines - hash ref that looks like the first return value from
                    &scantron_getfile()
       $scan_data - hash ref that looks like the second return value from
                    &scantron_getfile()
       $i         - line number to update
       $newline   - contents of the updated scanline
       $skip      - if true make the line for skipping and update the
                    'skipped' file
   
   =cut
   
 sub scantron_put_line {  sub scantron_put_line {
     my ($scanlines,$scan_data,$i,$newline,$skip)=@_;      my ($scanlines,$scan_data,$i,$newline,$skip)=@_;
     if ($skip) {      if ($skip) {
  $scanlines->{'skipped'}[$i]=$newline;   $scanlines->{'skipped'}[$i]=$newline;
  &allow_skipping($scan_data,$i);   &start_skipping($scan_data,$i);
  return;   return;
     }      }
     $scanlines->{'corrected'}[$i]=$newline;      $scanlines->{'corrected'}[$i]=$newline;
 }  }
   
   =pod
   
   =item scantron_clear_skip
   
      Remove a line from the 'skipped' file
   
    Arguments:
       $scanlines - hash ref that looks like the first return value from
                    &scantron_getfile()
       $scan_data - hash ref that looks like the second return value from
                    &scantron_getfile()
       $i         - line number to update
   
   =cut
   
   sub scantron_clear_skip {
       my ($scanlines,$scan_data,$i)=@_;
       if (exists($scanlines->{'skipped'}[$i])) {
    undef($scanlines->{'skipped'}[$i]);
    return 1;
       }
       return 0;
   }
   
   =pod
   
   =item scantron_filter_not_exam
   
      Filter routine used by &Apache::lonnavmaps::retrieveResources(), to
      filter out resources that are not marked as 'exam' mode
   
   =cut
   
   sub scantron_filter_not_exam {
       my ($curres)=@_;
       
       if (ref($curres) && $curres->is_problem() && !$curres->is_exam()) {
    # if the user has asked to not have either hidden
    # or 'randomout' controlled resources to be graded
    # don't include them
    if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
       && $curres->randomout) {
       return 0;
    }
    return 1;
       }
       return 0;
   }
   
   =pod
   
   =item scantron_validate_sequence
   
       Validates the selected sequence, checking for resource that are
       not set to exam mode.
   
   =cut
   
   sub scantron_validate_sequence {
       my ($r,$currentphase) = @_;
   
       my $navmap=Apache::lonnavmaps::navmap->new();
       unless (ref($navmap)) {
           $r->print(&navmap_errormsg());
           return (1,$currentphase);
       }
       my (undef,undef,$sequence)=
    &Apache::lonnet::decode_symb($env{'form.selectpage'});
   
       my $map=$navmap->getResourceByUrl($sequence);
   
       $r->print('<input type="hidden" name="validate_sequence_exam"
                                       value="ignore" />');
       if ($env{'form.validate_sequence_exam'} ne 'ignore') {
    my @resources=
       $navmap->retrieveResources($map,\&scantron_filter_not_exam,1,0);
    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>");
       return (1,$currentphase);
    }
       }
   
       return (0,$currentphase+1);
   }
   
   
   
 sub scantron_validate_ID {  sub scantron_validate_ID {
     my ($r,$currentphase) = @_;      my ($r,$currentphase) = @_;
           
Line 4220  sub scantron_validate_ID { Line 6734  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=&get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();      my ($scanlines,$scan_data)=&scantron_getfile();
   
       my $nav_error;
       &scantron_get_maxbubble(\$nav_error); # 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++) {
  my $line=&scantron_get_line($scanlines,$scan_data,$i);   my $line=&scantron_get_line($scanlines,$scan_data,$i);
Line 4274  sub scantron_validate_ID { Line 6795  sub scantron_validate_ID {
     return (0,$currentphase+1);      return (0,$currentphase+1);
 }  }
   
   
 sub scantron_get_correction {  sub scantron_get_correction {
     my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;      my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;
   #FIXME in the case of a duplicated ID the previous line, probably need
 #FIXME in the case of a duplicated ID the previous line, probaly 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 ( defined($$scan_record{'scantron.PaperID'}) ) {   $r->print("<p>".&mt("<b>An error was detected ($error)</b>".
  $r->print(" for PaperID <tt>".      " for PaperID <tt>[_1]</tt>",
   $$scan_record{'scantron.PaperID'}."</tt> \n");      $$scan_record{'scantron.PaperID'})."</p> \n");
     } else {      } else {
  $r->print(" in scanline $i <pre>".   $r->print("<p>".&mt("<b>An error was detected ($error)</b>".
   $line."</pre> \n");      " in scanline [_1] <pre>[_2]</pre>",
     }      $i,$line)."</p> \n");
       }
       my $message="<p>".&mt("The ID on the form is  <tt>[_1]</tt><br />".
     "The name on the paper is [_2],[_3]",
     $$scan_record{'scantron.ID'},
     $$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>".&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>".&mt("The encoded ID has also been used by a previous paper [_1]",$arg)."</p>\n");
  }   }
  $r->print("<p>The ID on the form is  <tt>".   $r->print($message);
   $$scan_record{'scantron.ID'}."</tt><br />\n");   $r->print("<p>".&mt("How should I handle this?")." <br /> \n");
  $r->print("The name on the paper is ".  
   $$scan_record{'scantron.LastName'}.",".  
   $$scan_record{'scantron.FirstName'}."</p>");  
  $r->print("<p>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
Line 4310  sub scantron_get_correction { Line 6839  sub scantron_get_correction {
        '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@".
  &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>".&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>".&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  <tt>'[_1]'</tt>",
   $$scan_record{'scantron.CODE'}."</tt><br />\n");      $$scan_record{'scantron.CODE'})."<br />\n");
  $r->print("<p>The ID on the form is  <tt>".   $r->print($message);
   $$scan_record{'scantron.ID'}."</tt><br />\n");   $r->print("<p>".&mt("How should I handle this?")." <br /> \n");
  $r->print("The name on the paper is ".  
   $$scan_record{'scantron.LastName'}.",".  
   $$scan_record{'scantron.FirstName'}."</p>");  
  $r->print("<p>How should I handle this? <br /> \n");  
  $r->print("\n<br /> ");   $r->print("\n<br /> ");
  my $i=0;   my $i=0;
  if ($error eq 'incorrectCODE') {   if ($error eq 'incorrectCODE' 
       && $$scan_record{'scantron.CODE'}=~/\S/ ) {
     my ($max,$closest)=&scantron_get_closely_matching_CODEs($arg,$$scan_record{'scantron.CODE'});      my ($max,$closest)=&scantron_get_closely_matching_CODEs($arg,$$scan_record{'scantron.CODE'});
     foreach my $testcode (@{$closest}) {      if ($closest > 0) {
  my $checked='';   foreach my $testcode (@{$closest}) {
  if (!$i) { $checked=' checked="on" '; }      my $checked='';
  $r->print("<input type='radio' name='scantron_CODE_resolution' value='use_closest_$i' $checked /> Use the similar CODE <b><tt>".$testcode."</tt></b> instead.<input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />");      if (!$i) { $checked=' checked="checked"'; }
  $r->print("\n<br />");      $r->print("
  $i++;     <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 />");
       $i++;
    }
     }      }
  }   }
  my $checked; if (!$i) { $checked=' checked="on" '; }   if ($$scan_record{'scantron.CODE'}=~/\S/ ) {
  $r->print("<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.");      my $checked; if (!$i) { $checked=' checked="checked"'; }
  $r->print("\n<br />");      $r->print("
       <label>
           <input type='radio' name='scantron_CODE_resolution' value='use_unfound'$checked />
          ".&mt("Use the CODE [_1] that is was on the paper, ignoring the error.",
        "<b><tt>".$$scan_record{'scantron.CODE'}."</tt></b>")."
       </label>");
       $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 4352  function change_radio(field) { Line 6892  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=".&Apache::lonnet::escape("scantronupload").     "form=".&escape("scantronupload").
    "&scantron_format=".&Apache::lonnet::escape($ENV{'form.scantron_format'}).     "&scantron_format=".&escape($env{'form.scantron_format'}).
    "&scantron_CODElist=".&Apache::lonnet::escape($ENV{'form.scantron_CODElist'}).     "&scantron_CODElist=".&escape($env{'form.scantron_CODElist'}).
    "&curCODE=".&Apache::lonnet::escape($$scan_record{'scantron.CODE'}).     "&curCODE=".&escape($$scan_record{'scantron.CODE'}).
    "&scantron_selectfile=".&Apache::lonnet::escape($ENV{'form.scantron_selectfile'});     "&scantron_selectfile=".&escape($env{'form.scantron_selectfile'});
  $r->print("<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. 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')\" />");   if ($env{'form.scantron_CODElist'} =~ /\S/) { 
  $r->print("\n<br />");      $r->print("
  $r->print("<input type='radio' name='scantron_CODE_resolution' value='use_typed' /> Use <input type='text' size='8' name='scantron_CODE_newvalue' onfocus=\"javascript:change_radio('use_typed')\" onkeypress=\"javascript:change_radio('use_typed')\" /> as the CODE.");      <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("
       <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>".&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);
   
  $r->print('<input type="hidden" name="scantron_questions" value="'.   $r->print('<input type="hidden" name="scantron_questions" value="'.
   join(',',@{$arg}).'" />');    $line_list.'" />');
  $r->print("<p>Please indicate which bubble should be used for grading</p>");   $r->print($message);
    $r->print("<p>".&mt("Please indicate which bubble should be used for grading")."</p>");
  foreach my $question (@{$arg}) {   foreach my $question (@{$arg}) {
     my $selected=$$scan_record{"scantron.$question.answer"};      my @linenums = &prompt_for_corrections($r,$question,$scan_config,
     &scantron_bubble_selector($r,$scan_config,$question,split('',$selected));                                                     $scan_record, $error);
               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>".&mt("There have been <b>no</b> bubbles scanned for some question(s)")."</p>\n");
  $r->print("<p>Please indicate which bubble should be used for grading</p>");   $r->print($message);
  $r->print("Some questions have no scanned bubbles\n");   $r->print("<p>".&mt("Please indicate which bubble should be used for grading.")."</p>");
    $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);
   
  $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=$$scan_record{"scantron.$question.answer"};      my @linenums = &prompt_for_corrections($r,$question,$scan_config,
     &scantron_bubble_selector($r,$scan_config,$question);                                                     $scan_record, $error);
               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.");
       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
   
   =item  questions_to_line_list
   
   Converts a list of questions into a string of comma separated
   line numbers in the answer sheet used by the questions.  This is
   used to fill in the scantron_questions form field.
   
     Arguments:
        questions    - Reference to an array of questions.
   
   =cut
   
   
   sub questions_to_line_list {
       my ($questions) = @_;
       my @lines;
   
       foreach my $item (@{$questions}) {
           my $question = $item;
           my ($first,$count,$last);
           if ($item =~ /^(\d+)\.(\d+)$/) {
               $question = $1;
               my $subquestion = $2;
               $first = $first_bubble_line{$question-1} + 1;
               my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
               my $subcount = 1;
               while ($subcount<$subquestion) {
                   $first += $subans[$subcount-1];
                   $subcount ++;
               }
               $count = $subans[$subquestion-1];
           } else {
       $first   = $first_bubble_line{$question-1} + 1;
       $count   = $bubble_lines_per_response{$question-1};
           }
           $last = $first+$count-1;
           push(@lines, ($first..$last));
       }
       return join(',', @lines);
 }  }
   
   =pod 
   
   =item prompt_for_corrections
   
   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).
   
    Arguments:
      $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
   
    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.  
   
   =cut
   
   sub prompt_for_corrections {
       my ($r, $question, $scan_config, $scan_record, $error) = @_;
       my ($current_line,$lines);
       my @linenums;
       my $questionnum = $question;
       if ($question =~ /^(\d+)\.(\d+)$/) {
           $question = $1;
           $current_line = $first_bubble_line{$question-1} + 1 ;
           my $subquestion = $2;
           my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
           my $subcount = 1;
           while ($subcount<$subquestion) {
               $current_line += $subans[$subcount-1];
               $subcount ++;
           }
           $lines = $subans[$subquestion-1];
       } else {
           $current_line = $first_bubble_line{$question-1} + 1 ;
           $lines        = $bubble_lines_per_response{$question-1};
       }
       if ($lines > 1) {
           $r->print(&mt('The group of bubble lines below responds to a single question.').'<br />');
           if (($responsetype_per_response{$question-1} eq 'essayresponse') ||
               ($responsetype_per_response{$question-1} eq 'formularesponse') ||
               ($responsetype_per_response{$question-1} eq 'stringresponse') ||
               ($responsetype_per_response{$question-1} eq 'imageresponse') ||
               ($responsetype_per_response{$question-1} eq 'reactionresponse') ||
               ($responsetype_per_response{$question-1} eq 'organicresponse')) {
               $r->print(&mt("Although this particular question type requires handgrading, the instructions for this question in the exam directed students to leave [quant,_1,line] blank on their 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;
   }
   
   =pod
   
   =item scantron_bubble_selector
     
      Generates the html radiobuttons to correct a single bubble line
      possibly showing the existing the selected bubbles if known
   
    Arguments:
       $r           - Apache request object
       $scan_config - hash from &get_scantron_config()
       $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.
   
   =cut
   
 sub scantron_bubble_selector {  sub scantron_bubble_selector {
     my ($r,$scan_config,$quest,@selected)=@_;      my ($r,$scan_config,$line,$questionnum,$error,@selected)=@_;
     my $max=$$scan_config{'Qlength'};      my $max=$$scan_config{'Qlength'};
   
       my $scmode=$$scan_config{'Qon'};
       if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }     
   
     my @alphabet=('A'..'Z');      my @alphabet=('A'..'Z');
     $r->print("<table border='1'><tr><td rowspan='2'>$quest</td>");      $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++) {      for (my $i=0;$i<$max+1;$i++) {
  $r->print('<td align="center">');   $r->print("\n".'<td align="center">');
  if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) }   if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) }
  else { $r->print('&nbsp;'); }   else { $r->print('&nbsp;'); }
  $r->print('</td>');   $r->print('</td>');
     }      }
     $r->print('<td></td></tr><tr>');      $r->print(&Apache::loncommon::end_data_table_row().
                 &Apache::loncommon::start_data_table_row());
     for (my $i=0;$i<$max;$i++) {      for (my $i=0;$i<$max;$i++) {
  $r->print('<td><input type="radio" name="scantron_correct_Q_'.$quest.   $r->print("\n".
   '" value="'.$i.'" />'.$alphabet[$i]."</td>");    '<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('<td><input type="radio" name="scantron_correct_Q_'.$quest.      $r->print("\n".'<td><label><input type="radio" name="scantron_correct_Q_'.
       '" value="none" /> No bubble </td>');        $line.'" value="none"'.$nobub_checked.'/>'.&mt('No bubble').
     $r->print('</tr></table>');                '</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
   
   =item num_matches
   
      Counts the number of characters that are the same between the two arguments.
   
    Arguments:
      $orig - CODE from the scanline
      $code - CODE to match against
   
    Returns:
      $count - integer count of the number of same characters between the
               two arguments
   
   =cut
   
 sub num_matches {  sub num_matches {
     my ($orig,$code) = @_;      my ($orig,$code) = @_;
     my @code=split(//,$code);      my @code=split(//,$code);
Line 4422  sub num_matches { Line 7195  sub num_matches {
     return $same;      return $same;
 }  }
   
   =pod
   
   =item scantron_get_closely_matching_CODEs
   
      Cycles through all CODEs and finds the set that has the greatest
      number of same characters as the provided CODE
   
    Arguments:
      $allcodes - hash ref returned by &get_codes()
      $CODE     - CODE from the current scanline
   
    Returns:
      2 element list
       - first elements is number of how closely matching the best fit is 
         (5 means best set has 5 matching characters)
       - second element is an arrary ref containing the set of valid CODEs
         that best fit the passed in CODE
   
   =cut
   
 sub scantron_get_closely_matching_CODEs {  sub scantron_get_closely_matching_CODEs {
     my ($allcodes,$CODE)=@_;      my ($allcodes,$CODE)=@_;
     my @CODEs;      my @CODEs;
Line 4432  sub scantron_get_closely_matching_CODEs Line 7225  sub scantron_get_closely_matching_CODEs
     return ($#CODEs,$CODEs[-1]);      return ($#CODEs,$CODEs[-1]);
 }  }
   
   =pod
   
   =item get_codes
   
      Builds a hash which has keys of all of the valid CODEs from the selected
      set of remembered CODEs.
   
    Arguments:
     $old_name - name of the set of remembered CODEs
     $cdom     - domain of the course
     $cnum     - internal course name
   
    Returns:
     %allcodes - keys are the valid CODEs, values are all 1
   
   =cut
   
 sub get_codes {  sub get_codes {
     my $old_name=$ENV{'form.scantron_CODElist'};      my ($old_name, $cdom, $cnum) = @_;
     my $cdom =$ENV{'course.'.$ENV{'request.course.id'}.'.domain'};      if (!$old_name) {
     my $cnum =$ENV{'course.'.$ENV{'request.course.id'}.'.num'};   $old_name=$env{'form.scantron_CODElist'};
     my %result=&Apache::lonnet::get('CODEs',[$old_name],$cdom,$cnum);      }
     my %allcodes=map {(&Apache::lonprintout::num_to_letters($_),1)} split(',',$result{$old_name});      if (!$cdom) {
    $cdom =$env{'course.'.$env{'request.course.id'}.'.domain'};
       }
       if (!$cnum) {
    $cnum =$env{'course.'.$env{'request.course.id'}.'.num'};
       }
       my %result=&Apache::lonnet::get('CODEs',[$old_name,"type\0$old_name"],
       $cdom,$cnum);
       my %allcodes;
       if ($result{"type\0$old_name"} eq 'number') {
    %allcodes=map {($_,1)} split(',',$result{$old_name});
       } else {
    %allcodes=map {(&Apache::lonprintout::num_to_letters($_),1)} split(',',$result{$old_name});
       }
     return %allcodes;      return %allcodes;
 }  }
   
   =pod
   
   =item scantron_validate_CODE
   
      Validates all scanlines in the selected file to not have any
      invalid or underspecified CODEs and that none of the codes are
      duplicated if this was requested.
   
   =cut
   
 sub scantron_validate_CODE {  sub scantron_validate_CODE {
     my ($r,$currentphase) = @_;      my ($r,$currentphase) = @_;
     my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'});      my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
     if ($scantron_config{'CODElocation'} &&      if ($scantron_config{'CODElocation'} &&
  $scantron_config{'CODEstart'} &&   $scantron_config{'CODEstart'} &&
  $scantron_config{'CODElength'}) {   $scantron_config{'CODElength'}) {
  if (!defined($ENV{'form.scantron_CODElist'})) {   if (!defined($env{'form.scantron_CODElist'})) {
     &FIXME_blow_up()      &FIXME_blow_up()
  }   }
     } else {      } else {
Line 4458  sub scantron_validate_CODE { Line 7291  sub scantron_validate_CODE {
   
     my %allcodes=&get_codes();      my %allcodes=&get_codes();
   
       my $nav_error;
       &scantron_get_maxbubble(\$nav_error); # 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++) {
  my $line=&scantron_get_line($scanlines,$scan_data,$i);   my $line=&scantron_get_line($scanlines,$scan_data,$i);
Line 4466  sub scantron_validate_CODE { Line 7306  sub scantron_validate_CODE {
  $scan_data);   $scan_data);
  my $CODE=$$scan_record{'scantron.CODE'};   my $CODE=$$scan_record{'scantron.CODE'};
  my $error=0;   my $error=0;
  if (!exists($allcodes{$CODE}) && !$$scan_record{'scantron.useCODE'}) {   if (!&Apache::lonnet::validCODE($CODE)) {
       &scantron_get_correction($r,$i,$scan_record,
        \%scantron_config,
        $line,'incorrectCODE',\%allcodes);
       return(1,$currentphase);
    }
    if (%allcodes && !exists($allcodes{$CODE}) 
       && !$$scan_record{'scantron.useCODE'}) {
     &scantron_get_correction($r,$i,$scan_record,      &scantron_get_correction($r,$i,$scan_record,
      \%scantron_config,       \%scantron_config,
      $line,'incorrectCODE',\%allcodes);       $line,'incorrectCODE',\%allcodes);
     return(1,$currentphase);      return(1,$currentphase);
  }   }
  if (exists($usedCODEs{$CODE})    if (exists($usedCODEs{$CODE}) 
     && $ENV{'form.scantron_CODEunique'} eq 'yes'      && $env{'form.scantron_CODEunique'} eq 'yes'
     && !$$scan_record{'scantron.CODE_ignore_dup'}) {      && !$$scan_record{'scantron.CODE_ignore_dup'}) {
     &scantron_get_correction($r,$i,$scan_record,      &scantron_get_correction($r,$i,$scan_record,
      \%scantron_config,       \%scantron_config,
      $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);
 }  }
   
   =pod
   
   =item scantron_validate_doublebubble
   
      Validates all scanlines in the selected file to not have any
      bubble lines with multiple bubbles marked.
   
   =cut
   
 sub scantron_validate_doublebubble {  sub scantron_validate_doublebubble {
     my ($r,$currentphase) = @_;      my ($r,$currentphase) = @_;
     #get student info      #get student info
Line 4492  sub scantron_validate_doublebubble { Line 7348  sub scantron_validate_doublebubble {
     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=&get_scantron_config($env{'form.scantron_format'});
     my ($scanlines,$scan_data)=&scantron_getfile();      my ($scanlines,$scan_data)=&scantron_getfile();
       my $nav_error;
       &scantron_get_maxbubble(\$nav_error); # 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; }
Line 4508  sub scantron_validate_doublebubble { Line 7371  sub scantron_validate_doublebubble {
     return (0,$currentphase+1);      return (0,$currentphase+1);
 }  }
   
   
 sub scantron_get_maxbubble {  sub scantron_get_maxbubble {
     my ($r)=@_;      my ($nav_error) = @_;
     if (defined($ENV{'form.scantron_maxbubble'}) &&      if (defined($env{'form.scantron_maxbubble'}) &&
  $ENV{'form.scantron_maxbubble'}) {   $env{'form.scantron_maxbubble'}) {
  return $ENV{'form.scantron_maxbubble'};   &restore_bubble_lines();
    return $env{'form.scantron_maxbubble'};
     }      }
   
       my (undef, undef, $sequence) =
    &Apache::lonnet::decode_symb($env{'form.selectpage'});
   
     my $navmap=Apache::lonnavmaps::navmap->new();      my $navmap=Apache::lonnavmaps::navmap->new();
     my (undef,undef,$sequence)=      unless (ref($navmap)) {
  &Apache::lonnet::decode_symb($ENV{'form.selectpage'});          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);
     &Apache::lonnet::delenv('form.counter');  
       &Apache::lonxml::clear_problem_counter();
   
       my $uname       = $env{'user.name'};
       my $udom        = $env{'user.domain'};
       my $cid         = $env{'request.course.id'};
       my $total_lines = 0;
       %bubble_lines_per_response = ();
       %first_bubble_line         = ();
       %subdivided_bubble_lines   = ();
       %responsetype_per_response = ();
   
       my $response_number = 0;
       my $bubble_line     = 0;
     foreach my $resource (@resources) {      foreach my $resource (@resources) {
  my $result=&Apache::lonnet::ssi($resource->src());          my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,$udom);
           if ((ref($analysis) eq 'HASH') && (ref($parts) eq 'ARRAY')) {
       foreach my $part_id (@{$parts}) {
                   my $lines;
   
           # TODO - make this a persistent hash not an array.
   
                   # optionresponse, matchresponse and rankresponse type items 
                   # render as separate sub-questions in exam mode.
                   if (($analysis->{$part_id.'.type'} eq 'optionresponse') ||
                       ($analysis->{$part_id.'.type'} eq 'matchresponse') ||
                       ($analysis->{$part_id.'.type'} eq 'rankresponse')) {
                       my ($numbub,$numshown);
                       if ($analysis->{$part_id.'.type'} eq 'optionresponse') {
                           if (ref($analysis->{$part_id.'.options'}) eq 'ARRAY') {
                               $numbub = scalar(@{$analysis->{$part_id.'.options'}});
                           }
                       } elsif ($analysis->{$part_id.'.type'} eq 'matchresponse') {
                           if (ref($analysis->{$part_id.'.items'}) eq 'ARRAY') {
                               $numbub = scalar(@{$analysis->{$part_id.'.items'}});
                           }
                       } elsif ($analysis->{$part_id.'.type'} eq 'rankresponse') {
                           if (ref($analysis->{$part_id.'.foils'}) eq 'ARRAY') {
                               $numbub = scalar(@{$analysis->{$part_id.'.foils'}});
                           }
                       }
                       if (ref($analysis->{$part_id.'.shown'}) eq 'ARRAY') {
                           $numshown = scalar(@{$analysis->{$part_id.'.shown'}});
                       }
                       my $bubbles_per_line = 10;
                       my $inner_bubble_lines = int($numbub/$bubbles_per_line);
                       if (($numbub % $bubbles_per_line) != 0) {
                           $inner_bubble_lines++;
                       }
                       for (my $i=0; $i<$numshown; $i++) {
                           $subdivided_bubble_lines{$response_number} .= 
                               $inner_bubble_lines.',';
                       }
                       $subdivided_bubble_lines{$response_number} =~ s/,$//;
                       $lines = $numshown * $inner_bubble_lines;
                   } else {
                       $lines = $analysis->{"$part_id.bubble_lines"};
                   } 
   
                   $first_bubble_line{$response_number} = $bubble_line;
           $bubble_lines_per_response{$response_number} = $lines;
                   $responsetype_per_response{$response_number} = 
                       $analysis->{$part_id.'.type'};
           $response_number++;
   
           $bubble_line +=  $lines;
           $total_lines +=  $lines;
       }
           }
     }      }
     &Apache::lonnet::delenv('scantron\.');      &Apache::lonnet::delenv('scantron.');
     my $envfile=$ENV{'user.environment'};  
     $envfile=~/\/([^\/]+)\.id$/;      &save_bubble_lines();
     $envfile=$1;      $env{'form.scantron_maxbubble'} =
     &Apache::lonnet::transfer_profile_to_env($r->dir_config('lonIDsDir'),   $total_lines;
      $envfile);      return $env{'form.scantron_maxbubble'};
     $ENV{'form.scantron_maxbubble'}=$ENV{'form.counter'}-1;  
     return $ENV{'form.scantron_maxbubble'};  
 }  }
   
 sub scantron_validate_missingbubbles {  sub scantron_validate_missingbubbles {
Line 4540  sub scantron_validate_missingbubbles { Line 7477  sub scantron_validate_missingbubbles {
     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=&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 $nav_error;
       my $max_bubble=&scantron_get_maxbubble(\$nav_error);
       if ($nav_error) {
           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);
Line 4551  sub scantron_validate_missingbubbles { Line 7492  sub scantron_validate_missingbubbles {
  $scan_data);   $scan_data);
  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;
                  if (!defined($first_bubble_line{$question -1})) { next; }
                  my $first = $first_bubble_line{$question-1};
                  my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
                  my $subcount = 1;
                  while ($subcount<$subquestion) {
                      $first += $subans[$subcount-1];
                      $subcount ++;
                  }
                  my $count = $subans[$subquestion-1];
                  $lastbubble = $first + $count;
               } else {
                   if (!defined($first_bubble_line{$missing - 1})) { next; }
                   $lastbubble = $first_bubble_line{$missing - 1} + $bubble_lines_per_response{$missing - 1};
               }
               if ($lastbubble > $max_bubble) { next; }
     push(@to_correct,$missing);      push(@to_correct,$missing);
  }   }
  if (@to_correct) {   if (@to_correct) {
Line 4565  sub scantron_validate_missingbubbles { Line 7527  sub scantron_validate_missingbubbles {
     return (0,$currentphase+1);      return (0,$currentphase+1);
 }  }
   
   
 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 ($symb,$url)=&get_symb_and_url($r);  
     if (!$symb) {return '';}  
     my $default_form_data=&defaultFormData($symb,$url);  
   
     my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'});      my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
       if (!$symb) {
    return '';
       }
       my $default_form_data=&defaultFormData($symb);
   
       my %scantron_config=&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 %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 @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);      my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
 #    $r->print("geto ".scalar(@resources)."<br />");      my (%grader_partids_by_symb,%grader_randomlists_by_symb);
       &graders_resources_pass(\@resources,\%grader_partids_by_symb,
                               \%grader_randomlists_by_symb);
       my $resource_error;
       foreach my $resource (@resources) {
           my $ressymb;
           if (ref($resource)) {
               $ressymb = $resource->symb();
           } else {
               $resource_error = 1;
               last;
           }
           my ($analysis,$parts) =
               &scantron_partids_tograde($resource,$env{'request.course.id'},
                                         $env{'user.name'},$env{'user.domain'},1);
           $grader_partids_by_symb{$ressymb} = $parts;
           if (ref($analysis) eq 'HASH') {
               if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') {
                   $grader_randomlists_by_symb{$ressymb} = 
                       $analysis->{'parts_withrandomlist'};
               }
           }
       }
       if ($resource_error) {
           $r->print(&navmap_errormsg());
           return '';
       }
   
       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 4588  SCANTRONFORM Line 7585  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,'Bubblesheet Status',
      'Scantron Progress',$count,       'Bubblesheet Progress',$count,
     'inline',undef,'scantronupload');      'inline',undef,'scantronupload');
     &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,      &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
   'Processing first student');    'Processing first student');
       $r->print('<br />');
     my $start=&Time::HiRes::time();      my $start=&Time::HiRes::time();
     my $i=-1;      my $i=-1;
     my ($uname,$udom,$started);      my $started;
   
       my $nav_error;
       &scantron_get_maxbubble(\$nav_error); # 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 = &letter_to_digits();
       my $numletts = scalar(keys(%lettdig));
   
     while ($i<$scanlines->{'count'}) {      while ($i<$scanlines->{'count'}) {
   ($uname,$udom)=('','');    ($uname,$udom)=('','');
   $i++;    $i++;
Line 4623  SCANTRONFORM Line 7643  SCANTRONFORM
      next;       next;
   }    }
   ($uname,$udom)=split(/:/,$uname);    ($uname,$udom)=split(/:/,$uname);
   &Apache::lonnet::delenv('form.counter');  
   &Apache::lonnet::appenv(%$scan_record);          my (%partids_by_symb,$res_error);
           foreach my $resource (@resources) {
  my $i=0;              my $ressymb;
  foreach my $resource (@resources) {              if (ref($resource)) {
     $i++;                  $ressymb = $resource->symb();
     my %form=('submitted'     =>'scantron',              } else {
       'grade_target'  =>'grade',                  $res_error = 1;
       'grade_username'=>$uname,                  last;
       'grade_domain'  =>$udom,              }
       'grade_courseid'=>$ENV{'request.course.id'},              if ((exists($grader_randomlists_by_symb{$ressymb})) ||
       'grade_symb'    =>$resource->symb());                  (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
     if (exists($scan_record->{'scantron.CODE'}) &&                  my ($analysis,$parts) =
  $scan_record->{'scantron.CODE'}) {                      &scantron_partids_tograde($resource,$env{'request.course.id'},$uname,$udom);
  $form{'CODE'}=$scan_record->{'scantron.CODE'};                  $partids_by_symb{$ressymb} = $parts;
     }              } else {
     my $result=&Apache::lonnet::ssi($resource->src(),%form);                  $partids_by_symb{$ressymb} = $grader_partids_by_symb{$ressymb};
     if (&Apache::loncommon::connection_aborted($r)) { last; }              }
           }
   
           if ($res_error) {
               &scantron_add_delay(\@delayqueue,$line,
                                   'An error occurred while grading student '.$uname,2);
               next;
           }
   
    &Apache::lonxml::clear_problem_counter();
     &Apache::lonnet::appenv($scan_record);
   
    if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
       &scantron_putfile($scanlines,$scan_data);
  }   }
   
           my $scancode;
           if ((exists($scan_record->{'scantron.CODE'})) &&
               (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
               $scancode = $scan_record->{'scantron.CODE'};
           } else {
               $scancode = '';
           }
   
           if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
                                      \@resources,\%partids_by_symb) eq 'ssi_error') {
               $ssi_error = 0; # So end of handler error message does not trigger.
               $r->print("</form>");
               &ssi_print_error($r);
               &Apache::lonnet::remove_lock($lock);
               return '';      # Why return ''?  Beats me.
           }
   
  $completedstudents{$uname}={'line'=>$line};   $completedstudents{$uname}={'line'=>$line};
  if (&Apache::loncommon::connection_aborted($r)) { last; }          if ($env{'form.verifyrecord'}) {
               my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
               my $studentdata = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
               chomp($studentdata);
               $studentdata =~ s/\r$//;
               my $studentrecord = '';
               my $counter = -1;
               foreach my $resource (@resources) {
                   my $ressymb = $resource->symb();
                   ($counter,my $recording) =
                       &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
                                                $counter,$studentdata,$partids_by_symb{$ressymb},
                                                \%scantron_config,\%lettdig,$numletts);
                   $studentrecord .= $recording;
               }
               if ($studentrecord ne $studentdata) {
                   &Apache::lonxml::clear_problem_counter();
                   if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
                                              \@resources,\%partids_by_symb) eq 'ssi_error') {
                       $ssi_error = 0; # So end of handler error message does not trigger.
                       $r->print("</form>");
                       &ssi_print_error($r);
                       &Apache::lonnet::remove_lock($lock);
                       delete($completedstudents{$uname});
                       return '';
                   }
                   $counter = -1;
                   $studentrecord = '';
                   foreach my $resource (@resources) {
                       my $ressymb = $resource->symb();
                       ($counter,my $recording) =
                           &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
                                                    $counter,$studentdata,$partids_by_symb{$ressymb},
                                                    \%scantron_config,\%lettdig,$numletts);
                       $studentrecord .= $recording;
                   }
                   if ($studentrecord ne $studentdata) {
                       $r->print('<p><span class="LC_error">');
                       if ($scancode eq '') {
                           $r->print(&mt('Mismatch grading bubble sheet for user: [_1] with ID: [_2].',
                                     $uname.':'.$udom,$scan_record->{'scantron.ID'}));
                       } else {
                           $r->print(&mt('Mismatch grading bubble sheet for user: [_1] with ID: [_2] and CODE: [_3].',
                                     $uname.':'.$udom,$scan_record->{'scantron.ID'},$scancode));
                       }
                       $r->print('</span><br />'.&Apache::loncommon::start_data_table()."\n".
                                 &Apache::loncommon::start_data_table_header_row()."\n".
                                 '<th>'.&mt('Source').'</th><th>'.&mt('Bubbled responses').'</th>'.
                                 &Apache::loncommon::end_data_table_header_row()."\n".
                                 &Apache::loncommon::start_data_table_row().
                                 '<td>'.&mt('Bubble Sheet').'</td>'.
                                 '<td><span class="LC_nobreak">'.$studentdata.'</span></td>'.
                                 &Apache::loncommon::end_data_table_row().
                                 &Apache::loncommon::start_data_table_row().
                                 '<td>Stored submissions</td>'.
                                 '<td><span class="LC_nobreak">'.$studentrecord.'</span></td>'."\n".
                                 &Apache::loncommon::end_data_table_row().
                                 &Apache::loncommon::end_data_table().'</p>');
                   } else {
                       $r->print('<br /><span class="LC_warning">'.
                                &mt('A second grading pass was needed for user: [_1] with ID: [_2], because a mismatch was seen on the first pass.',$uname.':'.$udom,$scan_record->{'scantron.ID'}).'<br />'.
                                &mt("As a consequence, this user's submission history records two tries.").
                                    '</span><br />');
                   }
               }
           }
           if (&Apache::loncommon::connection_aborted($r)) { last; }
     } continue {      } continue {
  &Apache::lonnet::delenv('form.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>");
   
     $navmap->untieHashes();  
     $r->print("</form>");      $r->print("</form>");
     $r->print(&show_grading_menu_form($symb,$url));  
     return '';      return '';
 }  }
   
   sub graders_resources_pass {
       my ($resources,$grader_partids_by_symb,$grader_randomlists_by_symb) = @_;
       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);
               $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;
   }
   
   sub grade_student_bubbles {
       my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts) = @_;
       if (ref($resources) eq 'ARRAY') {
           my $count = 0;
           foreach my $resource (@{$resources}) {
               my $ressymb = $resource->symb();
               my %form = ('submitted'      => 'scantron',
                           'grade_target'   => 'grade',
                           'grade_username' => $uname,
                           'grade_domain'   => $udom,
                           'grade_courseid' => $env{'request.course.id'},
                           'grade_symb'     => $ressymb,
                           'CODE'           => $scancode
                          );
               if (ref($parts) eq 'HASH') {
                   if (ref($parts->{$ressymb}) eq 'ARRAY') {
                       foreach my $part (@{$parts->{$ressymb}}) {
                           $form{'scantron_questnum_start.'.$part} =
                               1+$env{'form.scantron.first_bubble_line.'.$count};
                           $count++;
                       }
                   }
               }
               my $result=&ssi_with_retries($resource->src(),$ssi_retries,%form);
               return 'ssi_error' if ($ssi_error);
               last if (&Apache::loncommon::connection_aborted($r));
           }
       }
       return;
   }
   
 sub scantron_upload_scantron_data {  sub scantron_upload_scantron_data {
     my ($r)=@_;      my ($r,$symb)=@_;
     $r->print(&Apache::loncommon::coursebrowser_javascript($ENV{'request.role.domain'}));      my $dom = $env{'request.role.domain'};
       my $domdesc = &Apache::lonnet::domain($dom,'description');
       $r->print(&Apache::loncommon::coursebrowser_javascript($dom));
     my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',      my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
   'domainid',    'domainid',
   'coursename');    'coursename',$dom);
     my $domsel=&Apache::loncommon::select_dom_form($ENV{'request.role.domain'},      my $syllabuslink = '<a href="javascript:ToSyllabus();">'.&mt('Syllabus').'</a>'.
    'domainid');                         ('&nbsp'x2).&mt('(shows course personnel)'); 
     my $default_form_data=&defaultFormData(&get_symb_and_url($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">      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.");
       $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;
       }
   
   '));
       $r->print('
   <h3>'.&mt('Send scanned 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().
     &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 '';
 }  }
   
   
 sub scantron_upload_scantron_data_save {  sub scantron_upload_scantron_data_save {
     my($r)=@_;      my($r,$symb)=@_;
     my ($symb,$url)=&get_symb_and_url($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,$url));  
  } else {  
     $r->print($doanotherupload);      $r->print($doanotherupload);
  }   }
  return '';   return '';
     }      }
     $r->print("Doing upload to ".$ENV{'form.courseid'}." <br />");      my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
     my $home=&Apache::lonnet::homeserver($ENV{'form.courseid'},      my $uploadedfile;
  $ENV{'form.domainid'});      $r->print('<h3>'.&mt("Uploading file to [_1]",$coursedata{'description'}).'</h3>');
     my $fname=$ENV{'form.upfile.filename'};      if (length($env{'form.upfile'}) < 2) {
     #FIXME          $r->print(&mt('[_1]Error:[_2] The file you attempted to upload, [_3] contained no information. Please check that you entered the correct filename.','<span class="LC_error">','</span>','<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'));
     #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'; }  
     $fname='scantron_orig_'.$fname;  
     if (length($ENV{'form.upfile'}) < 2) {  
  $r->print("<font color='red'>Error:</font> 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.");  
     } else {      } else {
  my $result=&Apache::lonnet::finishuserfileupload($ENV{'form.courseid'},$ENV{'form.domainid'},$home,'upfile',$fname);          my $result = 
  if ($result =~ m|^/uploaded/|) {              &Apache::lonnet::userfileupload('upfile','','scantron','','','',
     $r->print("<font color='green'>Success:</font> Successfully uploaded ".(length($ENV{'form.upfile'})-1)." bytes of data into location <tt>".$result."</tt>");                                              $env{'form.courseid'},$env{'form.domainid'});
    if ($result =~ m{^/uploaded/}) {
       $r->print(&mt('[_1]Success:[_2] Successfully uploaded [_3] bytes of data into location: [_4]',
                             '<span class="LC_success">','</span>',(length($env{'form.upfile'})-1),
     '<span class="LC_filename">'.$result.'</span>'));
               ($uploadedfile) = ($result =~ m{/([^/]+)$});
               $r->print(&validate_uploaded_scantron_file($env{'form.domainid'},
                                                          $env{'form.courseid'},$uploadedfile));
  } else {   } else {
     $r->print("<font color='red'>Error:</font> An error (".$result.") occured when attempting to upload the file, <tt>".&HTML::Entities::encode($ENV{'form.upfile.filename'},'<>&"')."</tt>");      $r->print(&mt('[_1]Error:[_2] An error ([_3]) occurred when attempting to upload the file, [_4]',
                             '<span class="LC_error">','</span>',$result,
     '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'));
  }   }
     }      }
     if ($symb) {      if ($symb) {
  $r->print(&show_grading_menu_form($symb,$url));   $r->print(&scantron_selectphase($r,$uploadedfile,$symb));
     } else {      } else {
  $r->print($doanotherupload);   $r->print($doanotherupload);
     }      }
     return '';      return '';
 }  }
   
   sub validate_uploaded_scantron_file {
       my ($cdom,$cname,$fname) = @_;
       my $scanlines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.$fname);
       my @lines;
       if ($scanlines ne '-1') {
           @lines=split("\n",$scanlines,-1);
       }
       my $output;
       if (@lines) {
           my (%counts,$max_match_format);
           my ($max_match_count,$max_match_pct) = (0,0);
           my $classlist = &Apache::loncoursedata::get_classlist($cdom,$cname);
           my %idmap = &username_to_idmap($classlist);
           foreach my $key (keys(%idmap)) {
               my $lckey = lc($key);
               $idmap{$lckey} = $idmap{$key};
           }
           my %unique_formats;
           my @formatlines = &get_scantronformat_file();
           foreach my $line (@formatlines) {
               chomp($line);
               my @config = split(/:/,$line);
               my $idstart = $config[5];
               my $idlength = $config[6];
               if (($idstart ne '') && ($idlength > 0)) {
                   if (ref($unique_formats{$idstart.':'.$idlength}) eq 'ARRAY') {
                       push(@{$unique_formats{$idstart.':'.$idlength}},$config[0].':'.$config[1]); 
                   } else {
                       $unique_formats{$idstart.':'.$idlength} = [$config[0].':'.$config[1]];
                   }
               }
           }
           foreach my $key (keys(%unique_formats)) {
               my ($idstart,$idlength) = split(':',$key);
               %{$counts{$key}} = (
                                  'found'   => 0,
                                  'total'   => 0,
                                 );
               foreach my $line (@lines) {
                   next if ($line =~ /^#/);
                   next if ($line =~ /^[\s\cz]*$/);
                   my $id = substr($line,$idstart-1,$idlength);
                   $id = lc($id);
                   if (exists($idmap{$id})) {
                       $counts{$key}{'found'} ++;
                   }
                   $counts{$key}{'total'} ++;
               }
               if ($counts{$key}{'total'}) {
                   my $percent_match = (100*$counts{$key}{'found'})/($counts{$key}{'total'});
                   if (($max_match_format eq '') || ($percent_match > $max_match_pct)) {
                       $max_match_pct = $percent_match;
                       $max_match_format = $key;
                       $max_match_count = $counts{$key}{'total'};
                   }
               }
           }
           if (ref($unique_formats{$max_match_format}) eq 'ARRAY') {
               my $format_descs;
               my $numwithformat = @{$unique_formats{$max_match_format}};
               for (my $i=0; $i<$numwithformat; $i++) {
                   my ($name,$desc) = split(':',$unique_formats{$max_match_format}[$i]);
                   if ($i<$numwithformat-2) {
                       $format_descs .= '"<i>'.$desc.'</i>", ';
                   } elsif ($i==$numwithformat-2) {
                       $format_descs .= '"<i>'.$desc.'</i>" '.&mt('and').' ';
                   } elsif ($i==$numwithformat-1) {
                       $format_descs .= '"<i>'.$desc.'</i>"';
                   }
               }
               my $showpct = sprintf("%.0f",$max_match_pct).'%';
               $output .= '<br />'.&mt('Comparison of student IDs in the uploaded file with the course roster found matches for [_1] of the [_2] entries in the file (for the format defined for [_3]).','<b>'.$showpct.'</b>','<b>'.$max_match_count.'</b>',$format_descs).
                          '<br />'.&mt('A low percentage of matches results from one of the following:').'<ul>'.
                          '<li>'.&mt('The file was uploaded to the wrong course').'</li>'.
                          '<li>'.&mt('The data are not in the format expected for the domain: [_1]',
                                     '<i>'.$cdom.'</i>').'</li>'.
                          '<li>'.&mt('Students did not bubble their IDs, or mis-bubbled them').'</li>'.
                          '<li>'.&mt('The course roster is not up to date').'</li>'.
                          '</ul>';
           }
       } else {
           $output = '<span class="LC_warning">'.&mt('Uploaded file contained no data').'</span>';
       }
       return $output;
   }
   
 sub valid_file {  sub valid_file {
     my ($requested_file)=@_;      my ($requested_file)=@_;
     foreach my $filename (sort(&scantron_filenames())) {      foreach my $filename (sort(&scantron_filenames())) {
  &Apache::lonnet::logthis("$requested_file  $filename");  
  if ($requested_file eq $filename) { return 1; }   if ($requested_file eq $filename) { return 1; }
     }      }
     return 0;      return 0;
 }  }
   
 sub scantron_download_scantron_data {  sub scantron_download_scantron_data {
     my ($r)=@_;      my ($r,$symb)=@_;
     my $default_form_data=&defaultFormData(&get_symb_and_url($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 file name was invalid.').'
         </p>          </p>
 ERROR  ');
  $r->print(&show_grading_menu_form(&get_symb_and_url($r,1)));  
  return;   return;
     }      }
     my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file;      my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file;
Line 4779  ERROR Line 8055  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 scantron 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_and_url($r,1)));  
     return '';      return '';
 }  }
   
   sub checkscantron_results {
       my ($r,$symb) = @_;
       if (!$symb) {return '';}
       my $cid = $env{'request.course.id'};
       my %lettdig = &letter_to_digits();
       my $numletts = scalar(keys(%lettdig));
       my $cnum = $env{'course.'.$cid.'.num'};
       my $cdom = $env{'course.'.$cid.'.domain'};
       my (undef, undef, $sequence) = &Apache::lonnet::decode_symb($env{'form.selectpage'});
       my %record;
       my %scantron_config =
           &Apache::grades::get_scantron_config($env{'form.scantron_format'});
       my ($scanlines,$scan_data)=&Apache::grades::scantron_getfile();
       my $classlist=&Apache::loncoursedata::get_classlist();
       my %idmap=&Apache::grades::username_to_idmap($classlist);
       my $navmap=Apache::lonnavmaps::navmap->new();
       unless (ref($navmap)) {
           $r->print(&navmap_errormsg());
           return '';
       }
       my $map=$navmap->getResourceByUrl($sequence);
       my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
       my (%grader_partids_by_symb,%grader_randomlists_by_symb);
       &graders_resources_pass(\@resources,\%grader_partids_by_symb,                             \%grader_randomlists_by_symb);
   
       my ($uname,$udom);
       my (%scandata,%lastname,%bylast);
       $r->print('
   <form method="post" enctype="multipart/form-data" action="/adm/grades" name="checkscantron">'."\n");
   
       my @delayqueue;
       my %completedstudents;
   
       my $count=&Apache::grades::get_todo_count($scanlines,$scan_data);
       my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Bubblesheet/Submissions Comparison Status',
                                       'Progress of Bubblesheet Data/Submission Records Comparison',$count,
                                       'inline',undef,'checkscantron');
       my ($username,$domain,$started);
       my $nav_error;
       &scantron_get_maxbubble(\$nav_error); # 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=&Apache::grades::scantron_find_student($scan_record,$scan_data,
                                                                 \%idmap,$i)) {
               &Apache::grades::scantron_add_delay(\@delayqueue,$line,
                                   'Unable to find a student that matches',1);
               next;
           }
           if (exists $completedstudents{$uname}) {
               &Apache::grades::scantron_add_delay(\@delayqueue,$line,
                                   'Student '.$uname.' has multiple sheets',2);
               next;
           }
           my $pid = $scan_record->{'scantron.ID'};
           $lastname{$pid} = $scan_record->{'scantron.LastName'};
           push(@{$bylast{$lastname{$pid}}},$pid);
           my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
           $scandata{$pid} = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
           chomp($scandata{$pid});
           $scandata{$pid} =~ s/\r$//;
           ($username,$domain)=split(/:/,$uname);
           my $counter = -1;
           foreach my $resource (@resources) {
               my $parts;
               my $ressymb = $resource->symb();
               if ((exists($grader_randomlists_by_symb{$ressymb})) ||
                   (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
                   (my $analysis,$parts) =
                       &scantron_partids_tograde($resource,$env{'request.course.id'},$username,$domain);
               } else {
                   $parts = $grader_partids_by_symb{$ressymb};
               }
               ($counter,my $recording) =
                   &verify_scantron_grading($resource,$domain,$username,$cid,$counter,
                                            $scandata{$pid},$parts,
                                            \%scantron_config,\%lettdig,$numletts);
               $record{$pid} .= $recording;
           }
       }
       &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
       $r->print('<br />');
       my ($okstudents,$badstudents,$numstudents,$passed,$failed);
       $passed = 0;
       $failed = 0;
       $numstudents = 0;
       foreach my $last (sort(keys(%bylast))) {
           if (ref($bylast{$last}) eq 'ARRAY') {
               foreach my $pid (sort(@{$bylast{$last}})) {
                   my $showscandata = $scandata{$pid};
                   my $showrecord = $record{$pid};
                   $showscandata =~ s/\s/&nbsp;/g;
                   $showrecord =~ s/\s/&nbsp;/g;
                   if ($scandata{$pid} eq $record{$pid}) {
                       my $css_class = ($passed % 2)?'LC_odd_row':'LC_even_row';
                       $okstudents .= '<tr class="'.$css_class.'">'.
   '<td>'.&mt('Bubblesheet').'</td><td>'.$showscandata.'</td><td rowspan="2">'.$last.'</td><td rowspan="2">'.$pid.'</td>'."\n".
   '</tr>'."\n".
   '<tr class="'.$css_class.'">'."\n".
   '<td>Submissions</td><td>'.$showrecord.'</td></tr>'."\n";
                       $passed ++;
                   } else {
                       my $css_class = ($failed % 2)?'LC_odd_row':'LC_even_row';
                       $badstudents .= '<tr class="'.$css_class.'"><td>'.&mt('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>Submissions</td><td><span class="LC_nobreak">'.$record{$pid}.'</span></td>'."\n".
   '</tr>'."\n";
                       $failed ++;
                   }
                   $numstudents ++;
               }
           }
       }
       $r->print('<p>'.&mt('Comparison of bubblesheet data (including corrections) with corresponding submission records (most recent submission) for <b>[quant,_1,student]</b>  ([_2] scantron lines/student).',$numstudents,$env{'form.scantron_maxbubble'}).'</p>');
       $r->print('<p>'.&mt('Exact matches for <b>[quant,_1,student]</b>.',$passed).'<br />'.&mt('Discrepancies detected for <b>[quant,_1,student]</b>.',$failed).'</p>');
       if ($passed) {
           $r->print(&mt('Students with exact correspondence between bubblesheet data and submissions are as follows:').'<br /><br />');
           $r->print(&Apache::loncommon::start_data_table()."\n".
                    &Apache::loncommon::start_data_table_header_row()."\n".
                    '<th>'.&mt('Source').'</th><th>'.&mt('Bubble records').'</th><th>'.&mt('Name').'</th><th>'.&mt('ID').'</th>'.
                    &Apache::loncommon::end_data_table_header_row()."\n".
                    $okstudents."\n".
                    &Apache::loncommon::end_data_table().'<br />');
       }
       if ($failed) {
           $r->print(&mt('Students with differences between bubblesheet data and submissions are as follows:').'<br /><br />');
           $r->print(&Apache::loncommon::start_data_table()."\n".
                    &Apache::loncommon::start_data_table_header_row()."\n".
                    '<th>'.&mt('Source').'</th><th>'.&mt('Bubble records').'</th><th>'.&mt('Name').'</th><th>'.&mt('ID').'</th>'.
                    &Apache::loncommon::end_data_table_header_row()."\n".
                    $badstudents."\n".
                    &Apache::loncommon::end_data_table()).'<br />'.
                    &mt('Differences can occur if submissions were modified using manual grading after a bubblesheet grading pass.').'<br />'.&mt('If unexpected discrepancies were detected, it is recommended that you inspect the original bubblesheets.');  
       }
       $r->print('</form><br />');
       return;
   }
   
   sub verify_scantron_grading {
       my ($resource,$domain,$username,$cid,$counter,$scandata,$partids,
           $scantron_config,$lettdig,$numletts) = @_;
       my ($record,%expected,%startpos);
       return ($counter,$record) if (!ref($resource));
       return ($counter,$record) if (!$resource->is_problem());
       my $symb = $resource->symb();
       return ($counter,$record) if (ref($partids) ne 'ARRAY');
       foreach my $part_id (@{$partids}) {
           $counter ++;
           $expected{$part_id} = 0;
           if ($env{"form.scantron.sub_bubblelines.$counter"}) {
               my @sub_lines = split(/,/,$env{"form.scantron.sub_bubblelines.$counter"});
               foreach my $item (@sub_lines) {
                   $expected{$part_id} += $item;
               }
           } else {
               $expected{$part_id} = $env{"form.scantron.bubblelines.$counter"};
           }
           $startpos{$part_id} = $env{"form.scantron.first_bubble_line.$counter"};
       }
       if ($symb) {
           my %recorded;
           my (%returnhash) = &Apache::lonnet::restore($symb,$cid,$domain,$username);
           if ($returnhash{'version'}) {
               my %lasthash=();
               my $version;
               for ($version=1;$version<=$returnhash{'version'};$version++) {
                   foreach my $key (sort(split(/\:/,$returnhash{$version.':keys'}))) {
                       $lasthash{$key}=$returnhash{$version.':'.$key};
                   }
               }
               foreach my $key (keys(%lasthash)) {
                   if ($key =~ /\.scantron$/) {
                       my $value = &unescape($lasthash{$key});
                       my ($part_id) = ($key =~ /^resource\.(.+)\.scantron$/);
                       if ($value eq '') {
                           for (my $i=0; $i<$expected{$part_id}; $i++) {
                               for (my $j=0; $j<$scantron_config->{'length'}; $j++) {
                                   $recorded{$part_id} .= $scantron_config->{'Qoff'};
                               }
                           }
                       } else {
                           my @tocheck;
                           my @items = split(//,$value);
                           if (($scantron_config->{'Qon'} eq 'letter') ||
                               ($scantron_config->{'Qon'} eq 'number')) {
                               if (@items < $expected{$part_id}) {
                                   my $fragment = substr($scandata,$startpos{$part_id},$expected{$part_id});
                                   my @singles = split(//,$fragment);
                                   foreach my $pos (@singles) {
                                       if ($pos eq ' ') {
                                           push(@tocheck,$pos);
                                       } else {
                                           my $next = shift(@items);
                                           push(@tocheck,$next);
                                       }
                                   }
                               } else {
                                   @tocheck = @items;
                               }
                               foreach my $letter (@tocheck) {
                                   if ($scantron_config->{'Qon'} eq 'letter') {
                                       if ($letter !~ /^[A-J]$/) {
                                           $letter = $scantron_config->{'Qoff'};
                                       }
                                       $recorded{$part_id} .= $letter;
                                   } elsif ($scantron_config->{'Qon'} eq 'number') {
                                       my $digit;
                                       if ($letter !~ /^[A-J]$/) {
                                           $digit = $scantron_config->{'Qoff'};
                                       } else {
                                           $digit = $lettdig->{$letter};
                                       }
                                       $recorded{$part_id} .= $digit;
                                   }
                               }
                           } else {
                               @tocheck = @items;
                               for (my $i=0; $i<$expected{$part_id}; $i++) {
                                   my $curr_sub = shift(@tocheck);
                                   my $digit;
                                   if ($curr_sub =~ /^[A-J]$/) {
                                       $digit = $lettdig->{$curr_sub}-1;
                                   }
                                   if ($curr_sub eq 'J') {
                                       $digit += scalar($numletts);
                                   }
                                   for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) {
                                       if ($j == $digit) {
                                           $recorded{$part_id} .= $scantron_config->{'Qon'};
                                       } else {
                                           $recorded{$part_id} .= $scantron_config->{'Qoff'};
                                       }
                                   }
                               }
                           }
                       }
                   }
               }
           }
           foreach my $part_id (@{$partids}) {
               if ($recorded{$part_id} eq '') {
                   for (my $i=0; $i<$expected{$part_id}; $i++) {
                       for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) {
                           $recorded{$part_id} .= $scantron_config->{'Qoff'};
                       }
                   }
               }
               $record .= $recorded{$part_id};
           }
       }
       return ($counter,$record);
   }
   
   sub letter_to_digits { 
       my %lettdig = (
                       A => 1,
                       B => 2,
                       C => 3,
                       D => 4,
                       E => 5,
                       F => 6,
                       G => 7,
                       H => 8,
                       I => 9,
                       J => 0,
                     );
       return %lettdig;
   }
   
   
 #-------- end of section for handling grading scantron forms -------  #-------- end of section for handling grading scantron forms -------
 #  #
 #-------------------------------------------------------------------  #-------------------------------------------------------------------
   
   
 #-------------------------- 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,$url)=@_;  sub href_symb_cmd {
     my $result.='<br /><form action="/adm/grades" method="post">'."\n".      my ($symb,$cmd)=@_;
  '<input type="hidden" name="symb" value="'.$symb.'" />'."\n".      return '/adm/grades?symb='.&HTML::Entities::encode(&Apache::lonenc::check_encrypt($symb),'<>&"').'&command='.$cmd;
  '<input type="hidden" name="url" value="'.$url.'" />'."\n".  }
  '<input type="hidden" name="saveState"  value="'.$ENV{'form.saveState'}.'" />'."\n".  
  '<input type="hidden" name="command" value="gradingmenu" />'."\n".  sub grading_menu {
  '<input type="submit" name="submit" value="Grading Menu" />'."\n".      my ($request,$symb) = @_;
  '</form>'."\n";      if (!$symb) {return '';}
   
       my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb),
                     'command'=>'individual');
       
       my $url1a = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
   
       $fields{'command'}='ungraded';
       my $url1b=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
   
       $fields{'command'}='table';
       my $url1c=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
   
       $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';
       my $url2 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       
       $fields{'command'} = 'processclicker';
       my $url3 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       
       $fields{'command'} = 'scantron_selectphase';
       my $url4 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
   
       $fields{'command'} = 'initialverifyreceipt';
       my $url5 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       
       my @menu = ({ categorytitle=>'Hand Grading',
               items =>[
                           { linktext => 'Select individual students to grade',
                       url => $url1a,
                       permission => 'F',
                       icon => 'edit-find-replace.png',
                       linktitle => 'Grade current resource for a selection of students.'
                           }, 
                           {       linktext => 'Grade ungraded submissions.',
                                   url => $url1b,
                                   permission => 'F',
                                   icon => 'edit-find-replace.png',
                                   linktitle => 'Grade all submissions that have not been graded yet.'
                           },
   
                           {       linktext => 'Grading table',
                                   url => $url1c,
                                   permission => 'F',
                                   icon => 'edit-find-replace.png',
                                   linktitle => 'Grade current resource for all students.'
                           },
                           {       linktext => 'Grade page/folder for one student',
                                   url => $url1d,
                                   permission => 'F',
                                   icon => 'edit-find-replace.png',
                                   linktitle => 'Grade all resources in current page/sequence/folder for one student.'
                           },
                           {       linktext => 'Download submissions',
                                   url => $url1e,
                                   permission => 'F',
                                   icon => 'edit-find-replace.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 => 'stat.png',
                       linktitle => 'Grade scantron exams, upload/download scantron data files, and review previously graded scantron exams.'
                      },
                               {   linktext => 'Verify Receipt Number',
                                   url => $url5,
                                   permission => 'F',
                                   icon => 'edit-find-replace.png',
                                   linktitle => 'Verify a system-generated receipt number for correct problem solution.'
                               }
   
                       ]
               });
   
       # Create the menu
       my $Str;
       $Str .= '<form method="post" action="" name="gradingMenu">';
       $Str .= '<input type="hidden" name="command" value="" />'.
       '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
   
       $Str .= &Apache::lonhtmlcommon::generate_menu(@menu);
       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.='
   <h2>
     '.&mt('Grade page/folder for one student').'
   </h2>'.
               &selectfield(0).
               '<input type="hidden" name="command" value="pickStudentPage" />
               <div>
                 <input type="submit" value="'.&mt('Next').' &rarr;" />
               </div>
           </div>
     </form>';
     return $result;      return $result;
 }  }
   
 # -- Retrieve choices for grading form  sub submit_options_table {
 sub savedState {      my ($request,$symb) = @_;
     my %savedState = ();  
     if ($ENV{'form.saveState'}) {  
  foreach (split(/:/,$ENV{'form.saveState'})) {  
     my ($key,$value) = split(/=/,$_,2);  
     $savedState{$key} = $value;  
  }  
     }  
     return \%savedState;  
 }  
   
 #--- Displays the main menu page -------  
 sub gradingmenu {  
     my ($request) = @_;  
     my ($symb,$url)=&get_symb_and_url($request);  
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $probTitle = &Apache::lonnet::gettitle($symb);      &commonJSfunctions($request);
       my $result;
   
     $request->print(<<GRADINGMENUJS);      $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
 <script type="text/javascript" language="javascript">          '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
     function checkChoice(formname,val,cmdx) {  
  if (val <= 2) {      $result.='
     var cmd = radioSelection(formname.radioChoice);  <h2>
     var cmdsave = cmd;    '.&mt('Grading table').'
  } else {  </h2>'.
     cmd = cmdx;              &selectfield(0).
     cmdsave = 'submission';              '<input type="hidden" name="command" value="viewgrades" />
  }              <div>
  formname.command.value = cmd;                <input type="submit" value="'.&mt('Next').' &rarr;" />
  formname.saveState.value = "saveCmd="+cmdsave+":saveSec="+pullDownSelection(formname.section)+              </div>
     ":saveSub="+pullDownSelection(formname.submitonly)+":saveStatus="+pullDownSelection(formname.Status);          </div>
  if (val < 5) formname.submit();    </form>';
  if (val == 5) {      return $result;
     if (!checkReceiptNo(formname,'notOK')) { return false;}  }
     formname.submit();  
  }  sub submit_options_download {
       my ($request,$symb) = @_;
       if (!$symb) {return '';}
   
       &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 Which to Download Submissions').'
   </h2>'.&selectfield(1).'
                   <input type="hidden" name="command" value="downloadfileslink" /> 
                 <input type="submit" value="'.&mt('Next').' &rarr;" />
               </div>
             </div>
   
   
     </form>';
       return $result;
   }
   
   #--- Displays the submissions first page -------
   sub submit_options {
       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.='
   <h2>
     '.&mt('Select individual students to grade').'
   </h2>'.&selectfield(1).'
                   <input type="hidden" name="command" value="submission" /> 
         <input type="submit" value="'.&mt('Next').' &rarr;" />
               </div>
             </div>
   
   
     </form>';
       return $result;
   }
   
   sub selectfield {
      my ($full)=@_;
      my $result='<div class="LC_columnSection">
     
       <fieldset>
         <legend>
          '.&mt('Sections').'
         </legend>
         '.&Apache::lonstatistics::SectionSelect('section','multiple',5).'
       </fieldset>
     
       <fieldset>
         <legend>
           '.&mt('Groups').'
         </legend>
         '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'
       </fieldset>
     
       <fieldset>
         <legend>
           '.&mt('Access Status').'
         </legend>
         '.&Apache::lonhtmlcommon::StatusOptions(undef,undef,5,undef,'mult').'
       </fieldset>';
       if ($full) {
          $result.='
       <fieldset>
         <legend>
           '.&mt('Submission Status').'
         </legend>'.
          &Apache::loncommon::select_form('all','submitonly',
             (&Apache::lonlocal::texthash(
                'yes'       => 'with submissions',
                'queued'    => 'in grading queue',
                'graded'    => 'with ungraded submissions',
                'incorrect' => 'with incorrect submissions',
                'all'       => 'with any status'),
                'select_form_order' => ['yes','queued','graded','incorrect','all'])).
      '</fieldset>';
     }      }
       $result.='</div><br />';
       return $result;
   }
   
     function checkReceiptNo(formname,nospace) {  sub reset_perm {
  var receiptNo = formname.receipt.value;      undef(%perm);
  var checkOpt = false;  }
  if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}  
  if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}  sub init_perm {
  if (checkOpt) {      &reset_perm();
     alert("Please enter a receipt number given by a student in the receipt box.");      foreach my $test_perm ('vgr','mgr','opa') {
     formname.receipt.value = "";  
     formname.receipt.focus();   my $scope = $env{'request.course.id'};
     return false;   if (!($perm{$test_perm}=&Apache::lonnet::allowed($test_perm,$scope))) {
   
       $scope .= '/'.$env{'request.course.sec'};
       if ( $perm{$test_perm}=
    &Apache::lonnet::allowed($test_perm,$scope)) {
    $perm{$test_perm.'_section'}=$env{'request.course.sec'};
       } else {
    delete($perm{$test_perm});
       }
  }   }
  return true;  
     }      }
 </script>  }
 GRADINGMENUJS  
     &commonJSfunctions($request);  
     my $result='<h3>&nbsp;<font color="#339933">Manual Grading/View Submission</font></h3>';  
     my ($table,undef,$hdgrade) = &showResourceInfo($url,$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".  sub gather_clicker_ids {
  '<input type="hidden" name="symb"        value="'.$symb.'" />'."\n".      my %clicker_ids;
  '<input type="hidden" name="url"         value="'.$url.'" />'."\n".  
  '<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".      my $classlist = &Apache::loncoursedata::get_classlist();
  '<input type="hidden" name="probTitle"   value="'.$probTitle.'" />'."\n".  
  '<input type="hidden" name="command"     value="" />'."\n".      # Set up a couple variables.
  '<input type="hidden" name="saveState"   value="" />'."\n".      my $username_idx = &Apache::loncoursedata::CL_SNAME();
  '<input type="hidden" name="gradingMenu" value="1" />'."\n".      my $domain_idx   = &Apache::loncoursedata::CL_SDOM();
  '<input type="hidden" name="showgrading" value="yes" />'."\n";      my $status_idx   = &Apache::loncoursedata::CL_STATUS();
   
     $result.='<table width="100%" border=0><tr><td bgcolor=#777777>'."\n".      foreach my $student (keys(%$classlist)) {
  '<table width=100% border=0><tr bgcolor="#e6ffff"><td colspan="2">'."\n".          if ($classlist->{$student}->[$status_idx] ne 'Active') { next; }
  '&nbsp;<b>Select a Grading/Viewing Option</b></td></tr>'."\n".          my $username = $classlist->{$student}->[$username_idx];
  '<tr bgcolor="#ffffe6" valign="top"><td>'."\n";          my $domain   = $classlist->{$student}->[$domain_idx];
           my $clickers =
     $result.='<table width="100%" border=0>';      (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1];
     $result.='<tr bgcolor="#ffffe6" valign="top"><td>'."\n".          foreach my $id (split(/\,/,$clickers)) {
  '&nbsp;'.&mt('Select Section').': <select name="section">'."\n";              $id=~s/^[\#0]+//;
     if (ref($sections)) {              $id=~s/[\-\:]//g;
  foreach (sort (@$sections)) {              if (exists($clicker_ids{$id})) {
     $result.='<option value="'.$_.'" '.   $clicker_ids{$id}.=','.$username.':'.$domain;
  ($saveSec eq $_ ? 'selected="on"':'').'>'.$_.'</option>'."\n";              } else {
  }   $clicker_ids{$id}=$username.':'.$domain;
     }              }
     $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="on"' : ''). '>all</select> &nbsp; ';          }
       }
     $result.=&mt('Student Status').':</b>'.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,undef);      return %clicker_ids;
   }
     if (ref($sections) && (grep /no/,@$sections)) {  
  $result.='&nbsp;(Section "no" implies the students were not assigned a section.)<br />';  sub gather_adv_clicker_ids {
     }      my %clicker_ids;
     $result.='</td></tr>';      my $cnum=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
     $result.='<tr bgcolor="#ffffe6"valign="top"><td>'.      my %coursepersonnel=&Apache::lonnet::get_course_adv_roles($cdom.'/'.$cnum);
  '<input type="radio" name="radioChoice" value="submission" '.      foreach my $element (sort(keys(%coursepersonnel))) {
  ($saveCmd eq 'submission' ? 'checked' : '').'> '.'<b>'.&mt('Current Resource').':</b> '.&mt('For one or more students').          foreach my $person (split(/\,/,$coursepersonnel{$element})) {
  ' <select name="submitonly">'.              my ($puname,$pudom)=split(/\:/,$person);
  '<option value="yes" '.              my $clickers =
  ($saveSub eq 'yes' ? 'selected="on"' : '').'>with submissions</option>'.   (&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1];
  '<option value="graded" '.              foreach my $id (split(/\,/,$clickers)) {
  ($saveSub eq 'graded' ? 'selected="on"' : '').'>with ungraded submissions</option>'.   $id=~s/^[\#0]+//;
  '<option value="incorrect" '.                  $id=~s/[\-\:]//g;
  ($saveSub eq 'incorrect' ? 'selected="on"' : '').'>with incorrect submissions</option>'.   if (exists($clicker_ids{$id})) {
  '<option value="all" '.      $clicker_ids{$id}.=','.$puname.':'.$pudom;
  ($saveSub eq 'all' ? 'selected="on"' : '').'>with any status</option></select></td></tr>'."\n";   } else {
       $clicker_ids{$id}=$puname.':'.$pudom;
     $result.='<tr bgcolor="#ffffe6"valign="top"><td>'.   }
  '<input type="radio" name="radioChoice" value="viewgrades" '.              }
  ($saveCmd eq 'viewgrades' ? 'checked' : '').'> '.          }
  '<b>Current Resource:</b> For all students in selected section or course</td></tr>'."\n";      }
       return %clicker_ids;
     $result.='<tr bgcolor="#ffffe6" valign="top"><td>'.  }
  '<input type="radio" name="radioChoice" value="pickStudentPage" '.  
  ($saveCmd eq 'pickStudentPage' ? 'checked' : '').'> '.  sub clicker_grading_parameters {
  'The <b>complete</b> set/page/sequence: For one student</td></tr>'."\n";      return ('gradingmechanism' => 'scalar',
               'upfiletype' => 'scalar',
     $result.='<tr bgcolor="#ffffe6"><td><br />'.              'specificid' => 'scalar',
  '<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'.              'pcorrect' => 'scalar',
  '</td></tr></table>'."\n";              'pincorrect' => 'scalar');
   }
     $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"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";  
     }   
   
   sub process_clicker {
       my ($r,$symb)=@_;
       if (!$symb) {return '';}
       my $result=&checkforfile_js();
       $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";
       $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";
       $result.='&nbsp;<b>'.&mt('Specify a file containing the clicker information for this resource.').
           '</b></td></tr>'."\n";
       $result.='<tr bgcolor="#ffffe6"><td>'."\n";
   # Attempt to restore parameters from last session, set defaults if not present
       my %Saveable_Parameters=&clicker_grading_parameters();
       &Apache::loncommon::restore_course_settings('grades_clicker',
                                                    \%Saveable_Parameters);
       if (!$env{'form.pcorrect'}) { $env{'form.pcorrect'}=100; }
       if (!$env{'form.pincorrect'}) { $env{'form.pincorrect'}=100; }
       if (!$env{'form.gradingmechanism'}) { $env{'form.gradingmechanism'}='attendance'; }
       if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; }
   
       my %checked;
       foreach my $gradingmechanism ('attendance','personnel','specific','given') {
          if ($env{'form.gradingmechanism'} eq $gradingmechanism) {
             $checked{$gradingmechanism}=' checked="checked"';
          }
       }
   
       my $upload=&mt("Upload File");
       my $type=&mt("Type");
       my $attendance=&mt("Award points just for participation");
       my $personnel=&mt("Correctness determined from response by course personnel");
       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 $pincorrect=&mt("Percentage points for incorrect solution");
       my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',
      ('iclicker' => 'i>clicker',
                                                       'interwrite' => 'interwrite PRS'));
       $symb = &Apache::lonenc::check_encrypt($symb);
       $result.= &Apache::lonhtmlcommon::scripttag(<<ENDUPFORM);
   function sanitycheck() {
   // Accept only integer percentages
      document.forms.gradesupload.pcorrect.value=Math.round(document.forms.gradesupload.pcorrect.value);
      document.forms.gradesupload.pincorrect.value=Math.round(document.forms.gradesupload.pincorrect.value);
   // Find out grading choice
      for (i=0; i<document.forms.gradesupload.gradingmechanism.length; i++) {
         if (document.forms.gradesupload.gradingmechanism[i].checked) {
            gradingchoice=document.forms.gradesupload.gradingmechanism[i].value;
         }
      }
   // By default, new choice equals user selection
      newgradingchoice=gradingchoice;
   // Not good to give more points for false answers than correct ones
      if (Math.round(document.forms.gradesupload.pcorrect.value)<Math.round(document.forms.gradesupload.pincorrect.value)) {
         document.forms.gradesupload.pcorrect.value=document.forms.gradesupload.pincorrect.value;
      }
   // If new choice is attendance only, and old choice was correctness-based, restore defaults
      if ((gradingchoice=='attendance') && (document.forms.gradesupload.waschecked.value!='attendance')) {
         document.forms.gradesupload.pcorrect.value=100;
         document.forms.gradesupload.pincorrect.value=100;
      }
   // If the values are different, cannot be attendance only
      if ((Math.round(document.forms.gradesupload.pcorrect.value)!=Math.round(document.forms.gradesupload.pincorrect.value)) &&
          (gradingchoice=='attendance')) {
          newgradingchoice='personnel';
      }
   // Change grading choice to new one
      for (i=0; i<document.forms.gradesupload.gradingmechanism.length; i++) {
         if (document.forms.gradesupload.gradingmechanism[i].value==newgradingchoice) {
            document.forms.gradesupload.gradingmechanism[i].checked=true;
         } else {
            document.forms.gradesupload.gradingmechanism[i].checked=false;
         }
      }
   // Remember the old state
      document.forms.gradesupload.waschecked.value=newgradingchoice;
   }
   ENDUPFORM
       $result.= <<ENDUPFORM;
   <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
   <input type="hidden" name="symb" value="$symb" />
   <input type="hidden" name="command" value="processclickerfile" />
   <input type="file" name="upfile" size="50" />
   <br /><label>$type: $selectform</label>
   <br /><label><input type="radio" name="gradingmechanism" value="attendance"$checked{'attendance'} onclick="sanitycheck()" />$attendance </label>
   <br /><label><input type="radio" name="gradingmechanism" value="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" />
   <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'}" />
   <br /><label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onchange="sanitycheck()" /></label>
   <br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onchange="sanitycheck()" /></label>
   <br /><input type="button" onclick="javascript:checkUpload(this.form);" value="$upload" />
   </form>'
   ENDUPFORM
       $result.='</td></tr></table>'."\n".
                '</td></tr></table><br /><br />'."\n";
       return $result;
   }
   
   sub process_clicker_file {
       my ($r,$symb)=@_;
       if (!$symb) {return '';}
   
       my %Saveable_Parameters=&clicker_grading_parameters();
       &Apache::loncommon::store_course_settings('grades_clicker',
                                                 \%Saveable_Parameters);
       my $result='';
       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>';
    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 %correct_ids;
       if ($env{'form.gradingmechanism'} eq 'personnel') {
    %correct_ids=&gather_adv_clicker_ids();
       }
       if ($env{'form.gradingmechanism'} eq 'specific') {
    foreach my $correct_id (split(/[\s\,]/,$env{'form.specificid'})) {;
      $correct_id=~tr/a-z/A-Z/;
      $correct_id=~s/\s//gs;
      $correct_id=~s/^[\#0]+//;
              $correct_id=~s/[\-\:]//g;
              if ($correct_id) {
         $correct_ids{$correct_id}='specified';
              }
           }
       }
       if ($env{'form.gradingmechanism'} eq 'attendance') {
    $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 {
    my $number=0;
    $result.='<p><b>'.&mt('Correctness determined by the following IDs').'</b>';
    foreach my $id (sort(keys(%correct_ids))) {
       $result.='<br /><tt>'.$id.'</tt> - ';
       if ($correct_ids{$id} eq 'specified') {
    $result.=&mt('specified');
       } else {
    my ($uname,$udom)=split(/\:/,$correct_ids{$id});
    $result.=&Apache::loncommon::plainname($uname,$udom);
       }
       $number++;
    }
           $result.="</p>\n";
    if ($number==0) {
       $result.='<span class="LC_error">'.&mt('No IDs found to determine correct answer').'</span>';
       return $result;
    }
       }
       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.',
        '<span class="LC_error">',
        '</span>',
        '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>');
           return $result;
       }
   
   # Were able to get all the info needed, now analyze the file
   
       $result.=&Apache::loncommon::studentbrowser_javascript();
       $symb = &Apache::lonenc::check_encrypt($symb);
       my $heading=&mt('Scanning clicker file');
       $result.=(<<ENDHEADER);
   <br /><table width="100%" border="0"><tr><td bgcolor="#777777">
   <table width="100%" border="0"><tr bgcolor="#e6ffff"><td>
   <b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>
   <form method="post" action="/adm/grades" name="clickeranalysis">
   <input type="hidden" name="symb" value="$symb" />
   <input type="hidden" name="command" value="assignclickergrades" />
   <input type="hidden" name="gradingmechanism" value="$env{'form.gradingmechanism'}" />
   <input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" />
   <input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" />
   ENDHEADER
       if ($env{'form.gradingmechanism'} eq 'given') {
          $result.='<input type="hidden" name="correct:given" value="'.$env{'form.givenanswer'}.'" />';
       } 
       my %responses;
       my @questiontitles;
       my $errormsg='';
       my $number=0;
       if ($env{'form.upfiletype'} eq 'iclicker') {
    ($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses);
       }
       if ($env{'form.upfiletype'} eq 'interwrite') {
           ($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses);
       }
       $result.='<br />'.&mt('Found [_1] question(s)',$number).'<br />'.
                '<input type="hidden" name="number" value="'.$number.'" />'.
                &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',
                    $env{'form.pcorrect'},$env{'form.pincorrect'}).
                '<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
   # FIXME: Possibly need delimiter other than ":"
       for (my $i=0;$i<$number;$i++) {
           $result.='<input type="hidden" name="question:'.$i.'" value="'.
                    &HTML::Entities::encode($questiontitles[$i],'"&<>').'" />';
       }
       my $correct_count=0;
       my $student_count=0;
       my $unknown_count=0;
   # Match answers with usernames
   # FIXME: Possibly need delimiter other than ":"
       foreach my $id (keys(%responses)) {
          if ($correct_ids{$id}) {
             $result.="\n".'<input type="hidden" name="correct:'.$correct_count.':'.$correct_ids{$id}.'" value="'.$responses{$id}.'" />';
             $correct_count++;
          } elsif ($clicker_ids{$id}) {
             if ($clicker_ids{$id}=~/\,/) {
   # More than one user with the same clicker!
                $result.="\n<hr />".&mt('Clicker registered more than once').": <tt>".$id."</tt><br />";
                $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
                              "<select name='multi".$id."'>";
                foreach my $reguser (sort(split(/\,/,$clicker_ids{$id}))) {
                    $result.="<option value='".$reguser."'>".&Apache::loncommon::plainname(split(/\:/,$reguser)).' ('.$reguser.')</option>';
                }
                $result.='</select>';
                $unknown_count++;
             } else {
   # Good: found one and only one user with the right clicker
                $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />';
                $student_count++;
             }
          } else {
             $result.="\n<hr />".&mt('Unregistered Clicker')." <tt>".$id."</tt><br />";
             $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
                      "\n".&mt("Username").": <input type='text' name='uname".$id."' />&nbsp;".
                      "\n".&mt("Domain").": ".
                      &Apache::loncommon::select_dom_form($env{'course.'.$env{'request.course.id'}.'.domain'},'udom'.$id).'&nbsp;'.
                      &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id);
             $unknown_count++;
          }
       }
       $result.='<hr />'.
                &mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);
       if (($env{'form.gradingmechanism'} ne 'attendance') && ($env{'form.gradingmechanism'} ne 'given')) {
          if ($correct_count==0) {
             $errormsg.="Found no correct answers answers for grading!";
          } elsif ($correct_count>1) {
             $result.='<br /><span class="LC_warning">'.&mt("Found [_1] entries for grading!",$correct_count).'</span>';
          }
       }
       if ($number<1) {
          $errormsg.="Found no questions.";
       }
       if ($errormsg) {
          $result.='<br /><span class="LC_error">'.&mt($errormsg).'</span>';
       } else {
          $result.='<br /><input type="submit" name="finalize" value="'.&mt('Finalize Grading').'" />';
       }
     $result.='</form></td></tr></table>'."\n".      $result.='</form></td></tr></table>'."\n".
  '</td></tr></table>'."\n".               '</td></tr></table><br /><br />'."\n";
  '</td></tr></table>'."\n";  
     return $result;      return $result;
 }  }
   
   sub iclicker_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[0] eq 'Question') {
       for (my $i=3;$i<$#entries;$i+=6) {
    $$questiontitles[$number]=$entries[$i];
    $number++;
       }
    }
    if ($entries[0]=~/^\#/) {
       my $id=$entries[0];
       my @idresponses;
       $id=~s/^[\#0]+//;
       for (my $i=0;$i<$number;$i++) {
    my $idx=3+$i*6;
    push(@idresponses,$entries[$idx]);
       }
       $$responses{$id}=join(',',@idresponses);
    }
       }
       return ($errormsg,$number);
   }
   
   sub interwrite_eval {
       my ($questiontitles,$responses)=@_;
       my $number=0;
       my $errormsg='';
       my $skipline=1;
       my $questionnumber=0;
       my %idresponses=();
       foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
           my %components=&Apache::loncommon::record_sep($line);
           my @entries=map {$components{$_}} (sort(keys(%components)));
           if ($entries[1] eq 'Time') { $skipline=0; next; }
           if ($entries[1] eq 'Response') { $skipline=1; }
           next if $skipline;
           if ($entries[0]!=$questionnumber) {
              $questionnumber=$entries[0];
              $$questiontitles[$number]=&mt('Question [_1]',$questionnumber);
              $number++;
           }
           my $id=$entries[4];
           $id=~s/^[\#0]+//;
           $id=~s/^v\d*\://i;
           $id=~s/[\-\:]//g;
           $idresponses{$id}[$number]=$entries[6];
       }
       foreach my $id (keys(%idresponses)) {
          $$responses{$id}=join(',',@{$idresponses{$id}});
          $$responses{$id}=~s/^\s*\,//;
       }
       return ($errormsg,$number);
   }
   
   sub assign_clicker_grades {
       my ($r,$symb)=@_;
       if (!$symb) {return '';}
   # See which part we are saving to
       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
       my $part=$$partlist[0];
   # Start screen output
       my $result='';
   
       my $heading=&mt('Assigning grades based on clicker file');
       $result.=(<<ENDHEADER);
   <br /><table width="100%" border="0"><tr><td bgcolor="#777777">
   <table width="100%" border="0"><tr bgcolor="#e6ffff"><td>
   <b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>
   ENDHEADER
   # Get correct result
   # FIXME: Possibly need delimiter other than ":"
       my @correct=();
       my $gradingmechanism=$env{'form.gradingmechanism'};
       my $number=$env{'form.number'};
       if ($gradingmechanism ne 'attendance') {
          foreach my $key (keys(%env)) {
             if ($key=~/^form\.correct\:/) {
                my @input=split(/\,/,$env{$key});
                for (my $i=0;$i<=$#input;$i++) {
                    if (($correct[$i]) && ($input[$i]) &&
                        ($correct[$i] ne $input[$i])) {
                       $result.='<br /><span class="LC_warning">'.
                                &mt('More than one correct result given for question "[_1]": [_2] versus [_3].',
                                    $env{'form.question:'.$i},$correct[$i],$input[$i]).'</span>';
                    } elsif ($input[$i]) {
                       $correct[$i]=$input[$i];
                    }
                }
             }
          }
          for (my $i=0;$i<$number;$i++) {
             if (!$correct[$i]) {
                $result.='<br /><span class="LC_error">'.
                         &mt('No correct result given for question "[_1]"!',
                             $env{'form.question:'.$i}).'</span>';
             }
          }
          $result.='<br />'.&mt("Correct answer: [_1]",join(', ',map { ($_?$_:'-') } @correct));
       }
   # Start grading
       my $pcorrect=$env{'form.pcorrect'};
       my $pincorrect=$env{'form.pincorrect'};
       my $storecount=0;
       foreach my $key (keys(%env)) {
          my $user='';
          if ($key=~/^form\.student\:(.*)$/) {
             $user=$1;
          }
          if ($key=~/^form\.unknown\:(.*)$/) {
             my $id=$1;
             if (($env{'form.uname'.$id}) && ($env{'form.udom'.$id})) {
                $user=$env{'form.uname'.$id}.':'.$env{'form.udom'.$id};
             } elsif ($env{'form.multi'.$id}) {
                $user=$env{'form.multi'.$id};
             }
          }
          if ($user) { 
             my @answer=split(/\,/,$env{$key});
             my $sum=0;
             my $realnumber=$number;
             for (my $i=0;$i<$number;$i++) {
                if  ($correct[$i] eq '-') {
                   $realnumber--;
                } elsif ($answer[$i]) {
                   if ($gradingmechanism eq 'attendance') {
                      $sum+=$pcorrect;
                   } elsif ($correct[$i] eq '*') {
                      $sum+=$pcorrect;
                   } else {
                      if ($answer[$i] eq $correct[$i]) {
                         $sum+=$pcorrect;
                      } else {
                         $sum+=$pincorrect;
                      }
                   }
                }
             }
             my $ave=$sum/(100*$realnumber);
   # Store
             my ($username,$domain)=split(/\:/,$user);
             my %grades=();
             $grades{"resource.$part.solved"}='correct_by_override';
             $grades{"resource.$part.awarded"}=$ave;
             $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
             my $returncode=&Apache::lonnet::cstore(\%grades,$symb,
                                                    $env{'request.course.id'},
                                                    $domain,$username);
             if ($returncode ne 'ok') {
                $result.="<br /><span class=\"LC_error\">Failed to save student $username:$domain. Message when trying to save was ($returncode)</span>";
             } else {
                $storecount++;
             }
          }
       }
   # We are done
       $result.='<br />'.&mt('Successfully stored grades for [quant,_1,student].',$storecount).
                '</td></tr></table>'."\n".
                '</td></tr></table><br /><br />'."\n";
       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) = @_;
       unshift(@$crumbs,{href=>&href_symb_cmd($symb,'gradingmenu'),text=>"Grading"});
       $r->print(&Apache::loncommon::start_page('Grading',undef,
                                             {'bread_crumbs' => $crumbs}));
       unless ($nodisplayflag) {
          $r->print(&Apache::lonhtmlcommon::resource_info_box($symb,$onlyfolderflag));
       }
   }
   
   sub select_problem {
       my ($r)=@_;
       $r->print('<h2>'.&mt('Select the problem or one of the problems you want to grade').'</h2><form action="/adm/grades">');
       $r->print(&Apache::lonstathelpers::problem_selector('.',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();
     undef(%perm);      if ($env{'browser.mathml'}) {
     if ($ENV{'browser.mathml'}) {  
  &Apache::loncommon::content_type($request,'text/xml');   &Apache::loncommon::content_type($request,'text/xml');
     } else {      } else {
  &Apache::loncommon::content_type($request,'text/html');   &Apache::loncommon::content_type($request,'text/html');
Line 4977  sub handler { Line 9187  sub handler {
     $request->send_http_header;      $request->send_http_header;
     return '' if $request->header_only;      return '' if $request->header_only;
     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});      &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});
     my $url=$ENV{'form.url'};  
     my $symb=$ENV{'form.symb'};  # 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];
   
     if ($#commands > 0) {      if ($#commands > 0) {
  &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));   &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
     }      }
     if (!$url) {  
  my ($temp1,$temp2);  # see what the symb is
  ($temp1,$temp2,$ENV{'form.url'})=&Apache::lonnet::decode_symb($symb);  
  $url = $ENV{'form.url'};      my $symb=$env{'form.symb'};
     }      unless ($symb) {
     &send_header($request);         (my $url=$env{'form.url'}) =~ s-^https*://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
     if ($url eq '' && $symb eq '' && $command eq '') {         $symb=&Apache::lonnet::symbread($url);
  if ($ENV{'user.adv'}) {      }
     if (($ENV{'form.codeone'}) && ($ENV{'form.codetwo'}) &&      &Apache::lonenc::check_decrypt(\$symb);                             
  ($ENV{'form.codethree'})) {  
  my $token=$ENV{'form.codeone'}.'*'.$ENV{'form.codetwo'}.'*'.      $ssi_error = 0;
     $ENV{'form.codethree'};      if ($symb eq '' || $command eq '') {
  my ($tsymb,$tuname,$tudom,$tcrsid)=  #
     &Apache::lonnet::checkin($token);  # Not called from a resource
  if ($tsymb) {  #    
     my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb);          &startpage($request,undef,[],1,1);
     if (&Apache::lonnet::allowed('mgr',$tcrsid)) {          &select_problem($request);
  $request->print(&Apache::lonnet::ssi_body('/res/'.$url,  
   ('grade_username' => $tuname,  
    '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 {
  if (!($perm{'vgr'}=&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'}))) {   &init_perm();
     if ($perm{'vgr'}=&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'}.'/'.$ENV{'request.course.sec'})) {  
  $perm{'vgr_section'}=$ENV{'request.course.sec'};  
     } else {  
  delete($perm{'vgr'});  
     }  
  }  
  if (!($perm{'mgr'}=&Apache::lonnet::allowed('mgr',$ENV{'request.course.id'}))) {  
     if ($perm{'mgr'}=&Apache::lonnet::allowed('mgr',$ENV{'request.course.id'}.'/'.$ENV{'request.course.sec'})) {  
  $perm{'mgr_section'}=$ENV{'request.course.sec'};  
     } else {  
  delete($perm{'mgr'});  
     }  
  }  
  if ($command eq 'submission' && $perm{'vgr'}) {   if ($command eq 'submission' && $perm{'vgr'}) {
     ($ENV{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0));              &startpage($request,$symb,[{href=>"", text=>"Student Submissions"}]);
       ($env{'form.student'} eq '' ? &listStudents($request,$symb) : &submission($request,0,0,$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(&gradingmenu($request));              &startpage($request,$symb);
       $request->print(&grading_menu($request,$symb));
    } 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'}) {
               &startpage($request,$symb,[{href=>'', text=>'Process clicker'}]);
               $request->print(&process_clicker($request,$symb));
           } elsif ($command eq 'processclickerfile' && $perm{'mgr'}) {
               &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'}) {
               &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);
  } elsif ($command eq 'csvuploadassign' && $perm{'mgr'}) {      $request->print(&csvuploadmap($request,$symb));
     if ($ENV{'form.associate'} ne 'Reverse Association') {   } elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) {
  $request->print(&csvuploadassign($request));      if ($env{'form.associate'} ne 'Reverse Association') {
                   &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'} ) {
               &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);
       $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);
        $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 submissions'}]);
               &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>');
  }   }
     }      }
     &send_footer($request);      if ($ssi_error) {
    &ssi_print_error($request);
       }
       $request->print(&Apache::loncommon::end_page());
       &reset_caches();
     return '';      return '';
 }  }
   
 sub send_header {  
     my ($request)= @_;  
     $request->print(&Apache::lontexconvert::header());  
 #  $request->print("  
 #<script>  
 #remotewindow=open('','homeworkremote');  
 #remotewindow.close();  
 #</script>");   
     $request->print(&Apache::loncommon::bodytag('Grading'));  
     $request->rflush();  
 }  
   
 sub send_footer {  
     my ($request)= @_;  
     $request->print('</body></html>');  
 }  
   
 1;  1;
   
 __END__;  __END__;
   
   
   =head1 NAME
   
   Apache::grades
   
   =head1 SYNOPSIS
   
   Handles the viewing of grades.
   
   This is part of the LearningOnline Network with CAPA project
   described at http://www.lon-capa.org.
   
   =head1 OVERVIEW
   
   Do an ssi with retries:
   While I'd love to factor out this with the vesrion in lonprintout,
   that would either require a data coupling between modules, which I refuse to perpetuate (there's quite enough of that already), or would require the invention of another infrastructure
   I'm not quite ready to invent (e.g. an ssi_with_retry object).
   
   At least the logic that drives this has been pulled out into loncommon.
   
   
   
   ssi_with_retries - Does the server side include of a resource.
                        if the ssi call returns an error we'll retry it up to
                        the number of times requested by the caller.
                        If we still have a proble, no text is appended to the
                        output and we set some global variables.
                        to indicate to the caller an SSI error occurred.  
                        All of this is supposed to deal with the issues described
                        in LonCAPA BZ 5631 see:
                        http://bugs.lon-capa.org/show_bug.cgi?id=5631
                        by informing the user that this happened.
   
   Parameters:
     resource   - The resource to include.  This is passed directly, without
                  interpretation to lonnet::ssi.
     form       - The form hash parameters that guide the interpretation of the resource
                  
     retries    - Number of retries allowed before giving up completely.
   Returns:
     On success, returns the rendered resource identified by the resource parameter.
   Side Effects:
     The following global variables can be set:
      ssi_error                - If an unrecoverable error occurred this becomes true.
                                 It is up to the caller to initialize this to false
                                 if desired.
      ssi_error_resource  - If an unrecoverable error occurred, this is the value
                                 of the resource that could not be rendered by the ssi
                                 call.
      ssi_error_message   - The error string fetched from the ssi response
                                 in the event of an error.
   
   
   =head1 HANDLER SUBROUTINE
   
   ssi_with_retries()
   
   =head1 SUBROUTINES
   
   =over
   
   =item scantron_get_correction() : 
   
      Builds the interface screen to interact with the operator to fix a
      specific error condition in a specific scanline
   
    Arguments:
       $r           - Apache request object
       $i           - number of the current scanline
       $scan_record - hash ref as returned from &scantron_parse_scanline()
       $scan_config - hash ref as returned from &get_scantron_config()
       $line        - full contents of the current scanline
       $error       - error condition, valid values are
                      'incorrectCODE', 'duplicateCODE',
                      'doublebubble', 'missingbubble',
                      'duplicateID', 'incorrectID'
       $arg         - extra information needed
          For errors:
            - duplicateID   - paper number that this studentID was seen before on
            - duplicateCODE - array ref of the paper numbers this CODE was
                              seen on before
            - incorrectCODE - current incorrect CODE 
            - doublebubble  - array ref of the bubble lines that have double
                              bubble errors
            - missingbubble - array ref of the bubble lines that have missing
                              bubble errors
   
   =item  scantron_get_maxbubble() : 
   
      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().
   
      Returns the maximum number of bubble lines that are expected to
      occur. Does this by walking the selected sequence rendering the
      resource and then checking &Apache::lonxml::get_problem_counter()
      for what the current value of the problem counter is.
   
      Caches the results to $env{'form.scantron_maxbubble'},
      $env{'form.scantron.bubble_lines.n'}, 
      $env{'form.scantron.first_bubble_line.n'} and
      $env{"form.scantron.sub_bubblelines.n"}
      which are the total number of bubble, lines, the number of bubble
      lines for response n and number of the first bubble line for response n,
      and a comma separated list of numbers of bubble lines for sub-questions
      (for optionresponse, matchresponse, and rankresponse items), for response n.  
   
   
   =item  scantron_validate_missingbubbles() : 
   
      Validates all scanlines in the selected file to not have any
       answers that don't have bubbles that have not been verified
       to be bubble free.
   
   =item  scantron_process_students() : 
   
      Routine that does the actual grading of the bubble sheet information.
   
      The parsed scanline hash is added to %env 
   
      Then foreach unskipped scanline it does an &Apache::lonnet::ssi()
      foreach resource , with the form data of
   
    'submitted'     =>'scantron' 
    'grade_target'  =>'grade',
    'grade_username'=> username of student
    'grade_domain'  => domain of student
    'grade_courseid'=> of course
    'grade_symb'    => symb of resource to grade
   
       This triggers a grading pass. The problem grading code takes care
       of converting the bubbled letter information (now in %env) into a
       valid submission.
   
   =item  scantron_upload_scantron_data() :
   
       Creates the screen for adding a new bubble sheet data file to a course.
   
   =item  scantron_upload_scantron_data_save() : 
   
      Adds a provided bubble information data file to the course if user
      has the correct privileges to do so. 
   
   =item  valid_file() :
   
      Validates that the requested bubble data file exists in the course.
   
   =item  scantron_download_scantron_data() : 
   
      Shows a list of the three internal files (original, corrected,
      skipped) for a specific bubble sheet data file that exists in the
      course.
   
   =item  scantron_validate_ID() : 
   
      Validates all scanlines in the selected file to not have any
      invalid or underspecified student/employee IDs
   
   =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
   
   =cut

Removed from v.1.204.2.7  
changed lines
  Added in v.1.628


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