Diff for /loncom/homework/grades.pm between versions 1.62 and 1.489

version 1.62, 2002/11/27 16:39:37 version 1.489, 2007/11/13 01:47:36
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  
 #  
   
 package Apache::grades;  package Apache::grades;
 use strict;  use strict;
Line 39  use Apache::style; Line 32  use Apache::style;
 use Apache::lonxml;  use Apache::lonxml;
 use Apache::lonnet;  use Apache::lonnet;
 use Apache::loncommon;  use Apache::loncommon;
   use Apache::lonhtmlcommon;
   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::lonenc;
   use String::Similarity;
   use LONCAPA;
   
   use POSIX qw(floor);
   
   
   my %perm=();
   my %bubble_lines_per_response = ();     # no. bubble lines for each response.
                                      # index is "symb.part_id"
   
   my %first_bubble_line = (); # First bubble line no. for each bubble.
   
   # Save and restore the bubble lines array to the form env.
   
   
   sub save_bubble_lines {
       foreach my $line (keys(%bubble_lines_per_response)) {
    $env{"form.scantron.bubblelines.$line"}  = $bubble_lines_per_response{$line};
    $env{"form.scantron.first_bubble_line.$line"} =
       $first_bubble_line{$line};
       }
   }
   
   
   sub restore_bubble_lines {
       my $line = 0;
       %bubble_lines_per_response = ();
       while ($env{"form.scantron.bubblelines.$line"}) {
    my $value = $env{"form.scantron.bubblelines.$line"};
    $bubble_lines_per_response{$line} = $value;
    $first_bubble_line{$line}  =
       $env{"form.scantron.first_bubble_line.$line"};
    $line++;
       }
   
   }
   
   #  Given the parsed scanline, get the response for 
   #  '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;
   }
   
   
   # ----- 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;
   }
   
 # ----- These first few routines are general use routines.-----  
 #  #
 # --- Retrieve the parts that matches stores_\d+ from the metadata file.---  # --- Retrieve the parts from the metadata file.---
 sub getpartlist {  sub getpartlist {
     my ($url) = @_;      my ($symb) = @_;
     my @parts =();  
     my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys'));      my $navmap   = Apache::lonnavmaps::navmap->new();
     foreach my $key (@metakeys) {      my $res      = $navmap->getBySymb($symb);
  if ( $key =~ m/stores_(\w+)_.*/) {      my $partlist = $res->parts();
     push(@parts,$key);      my $url      = $res->src();
       my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys'));
   
       my @stores;
       foreach my $part (@{ $partlist }) {
    foreach my $key (@metakeys) {
       if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }
  }   }
     }      }
     return @parts;      return @stores;
 }  }
   
 # --- Get the symbolic name of a problem and the url  # --- Get the symbolic name of a problem and the url
 sub get_symb_and_url {  sub get_symb {
     my ($request) = @_;      my ($request,$silent) = @_;
     (my $url=$ENV{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;      (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)));      my $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url)));
     if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; }      if ($symb eq '') { 
     return ($symb,$url);   if (!$silent) {
 }      $request->print("Unable to handle ambiguous references:$url:.");
       return ();
 # --- Retrieve the fullname for a user. Return lastname, first middle ---   }
 # --- Generation is attached next to the lastname if it exists. ---      }
 sub get_fullname {      &Apache::lonenc::check_decrypt(\$symb);
     my ($uname,$udom) = @_;      return ($symb);
     my %name=&Apache::lonnet::get('environment', ['lastname','generation',  }
   'firstname','middlename'],  
                                   $udom,$uname);  #--- Format fullname, username:domain if different for display
     my $fullname;  #--- Use anywhere where the student names are listed
     my ($tmp) = keys(%name);  sub nameUserString {
     if ($tmp !~ /^(con_lost|error|no_such_host)/i) {      my ($type,$fullname,$uname,$udom) = @_;
         $fullname = &Apache::loncoursedata::ProcessFullName      if ($type eq 'header') {
             (@name{qw/lastname generation firstname middlename/});   return '<b>&nbsp;'.&mt('Fullname').'&nbsp;</b><span class="LC_internal_info">('.&mt('Username').')</span>';
     } else {      } else {
         &Apache::lonnet::logthis('grades.pm: no name data for '.$uname.   return '&nbsp;'.$fullname.'<span class="LC_internal_info">&nbsp;('.$uname.
                                  '@'.$udom.':'.$tmp);      ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</span>';
     }      }
     return $fullname;  
 }  }
   
 #--- 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. ---
 sub response_type {  sub response_type {
     my ($url) = shift;      my ($symb) = shift;
     my $allkeys = &Apache::lonnet::metadata($url,'keys');  
     my %seen = ();      my $navmap = Apache::lonnavmaps::navmap->new();
     my (@partlist,%handgrade);      my $res = $navmap->getBySymb($symb);
     foreach (split(/,/,&Apache::lonnet::metadata($url,'packages'))) {      my $partlist = $res->parts();
  if (/^\w+response_\w+.*/) {      my %vPart = 
     my ($responsetype,$part) = split(/_/,$_,2);   map { $_ => 1 } (&Apache::loncommon::get_env_multiple('form.vPart'));
     my ($partid,$respid) = split(/_/,$part);      my (%response_types,%handgrade);
     $handgrade{$part} = $responsetype.':'.($allkeys =~ /parameter_$part\_handgrade/ ? 'yes' : 'no');      foreach my $part (@{ $partlist }) {
     next if ($seen{$partid} > 0);   next if (%vPart && !exists($vPart{$part}));
     $seen{$partid}++;  
     push @partlist,$partid;   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 {
       my ($partID,$symb)=@_;
       my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);
       if (defined($display) and $display ne '') {
    $display.= " (<span class=\"LC_internal_info\">id $partID</span>)";
       } else {
    $display=$partID;
       }
       return $display;
   }
   
   #--- Show resource title
   #--- and parts and response type
   sub showResourceInfo {
       my ($symb,$probTitle,$checkboxes) = @_;
       my $col=3;
       if ($checkboxes) { $col=4; }
       my $result = '<h3>'.&mt('Current Resource').': '.$probTitle.'</h3>'."\n";
       $result .='<table border="0">';
       my ($partlist,$handgrade,$responseType) = &response_type($symb);
       my %resptype = ();
       my $hdgrade='no';
       my %partsseen;
       foreach my $partID (sort keys(%$responseType)) {
    foreach my $resID (sort keys(%{ $responseType->{$partID} })) {
       my $handgrade=$$handgrade{$partID.'_'.$resID};
       my $responsetype = $responseType->{$partID}->{$resID};
       $hdgrade = $handgrade if ($handgrade eq 'yes');
       $result.='<tr>';
       if ($checkboxes) {
    if (exists($partsseen{$partID})) {
       $result.="<td>&nbsp;</td>";
    } else {
       $result.="<td><input type='checkbox' name='vPart' value='$partID' checked='checked' /></td>";
    }
    $partsseen{$partID}=1;
       }
       my $display_part=&get_display_part($partID,$symb);
       $result.='<td>'.&mt('<b>Part: </b>[_1]',$display_part).' <span class="LC_internal_info">'.
    $resID.'</span></td>'.
    '<td>'.&mt('<b>Type: </b>[_1]',$responsetype).'</td></tr>';
   #    '<td>'.&mt('<b>Handgrade: </b>[_1]',$handgrade).'</td></tr>';
    }
       }
       $result.='</table>'."\n";
       return $result,$responseType,$hdgrade,$partlist,$handgrade;
   }
   
   sub reset_caches {
       &reset_analyze_cache();
       &reset_perm();
   }
   
   {
       my %analyze_cache;
   
       sub reset_analyze_cache {
    undef(%analyze_cache);
       }
   
       sub get_analyze {
    my ($symb,$uname,$udom)=@_;
    my $key = "$symb\0$uname\0$udom";
    return $analyze_cache{$key} if (exists($analyze_cache{$key}));
   
    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_cache{$key} = \%analyze;
       }
   
       sub get_order {
    my ($partid,$respid,$symb,$uname,$udom)=@_;
    my $analyze = &get_analyze($symb,$uname,$udom);
    return $analyze->{"$partid.$respid.shown"};
       }
   
       sub get_radiobutton_correct_foil {
    my ($partid,$respid,$symb,$uname,$udom)=@_;
    my $analyze = &get_analyze($symb,$uname,$udom);
    foreach my $foil (@{&get_order($partid,$respid,$symb,$uname,$udom)}) {
       if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {
    return $foil;
       }
    }
       }
   }
   
   #--- Clean response type for display
   #--- Currently filters option/rank/radiobutton/match/essay/Task
   #        response types only.
   sub cleanRecord {
       my ($answer,$response,$symb,$partid,$respid,$record,$order,$version,
    $uname,$udom) = @_;
       my $grayFont = '<span class="LC_internal_info">';
       if ($response =~ /^(option|rank)$/) {
    my %answer=&Apache::lonnet::str2hash($answer);
    my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
    my ($toprow,$bottomrow);
    foreach my $foil (@$order) {
       if ($grading{$foil} == 1) {
    $toprow.='<td><b>'.$answer{$foil}.'&nbsp;</b></td>';
       } else {
    $toprow.='<td><i>'.$answer{$foil}.'&nbsp;</i></td>';
       }
       $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
    }
    return '<blockquote><table border="1">'.
       '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
       '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
       $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';
       } elsif ($response eq 'match') {
    my %answer=&Apache::lonnet::str2hash($answer);
    my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
    my @items=&Apache::lonnet::str2array($record->{$version."resource.$partid.$respid.submissionitems"});
    my ($toprow,$middlerow,$bottomrow);
    foreach my $foil (@$order) {
       my $item=shift(@items);
       if ($grading{$foil} == 1) {
    $toprow.='<td><b>'.$item.'&nbsp;</b></td>';
    $middlerow.='<td><b>'.$grayFont.$answer{$foil}.'&nbsp;</span></b></td>';
       } else {
    $toprow.='<td><i>'.$item.'&nbsp;</i></td>';
    $middlerow.='<td><i>'.$grayFont.$answer{$foil}.'&nbsp;</span></i></td>';
       }
       $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
    }
    return '<blockquote><table border="1">'.
       '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
       '<tr valign="top"><td>'.$grayFont.&mt('Item ID').'</span></td>'.
       $middlerow.'</tr>'.
       '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
       $bottomrow.'</tr>'.'</table></blockquote>';
       } elsif ($response eq 'radiobutton') {
    my %answer=&Apache::lonnet::str2hash($answer);
    my ($toprow,$bottomrow);
    my $correct = 
       &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom);
    foreach my $foil (@$order) {
       if (exists($answer{$foil})) {
    if ($foil eq $correct) {
       $toprow.='<td><b>'.&mt('true').'</b></td>';
    } else {
       $toprow.='<td><i>'.&mt('true').'</i></td>';
    }
       } else {
    $toprow.='<td>'.&mt('false').'</td>';
       }
       $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
    }
    return '<blockquote><table border="1">'.
       '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
       '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
       $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';
       } elsif ($response eq 'essay') {
    if (! exists ($env{'form.'.$symb})) {
       my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade',
     $env{'course.'.$env{'request.course.id'}.'.domain'},
     $env{'course.'.$env{'request.course.id'}.'.num'});
   
       my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
       $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
       $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
       $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
       $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.
    }
    $answer =~ s-\n-<br />-g;
    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;
   }
   
   #-- A couple of common js functions
   sub commonJSfunctions {
       my $request = shift;
       $request->print(<<COMMONJSFUNCTIONS);
   <script type="text/javascript" language="javascript">
       function radioSelection(radioButton) {
    var selection=null;
    if (radioButton.length > 1) {
       for (var i=0; i<radioButton.length; i++) {
    if (radioButton[i].checked) {
       return radioButton[i].value;
    }
       }
    } else {
       if (radioButton.checked) return radioButton.value;
    }
    return selection;
       }
   
       function pullDownSelection(selectOne) {
    var selection="";
    if (selectOne.length > 1) {
       for (var i=0; i<selectOne.length; i++) {
    if (selectOne[i].selected) {
       return selectOne[i].value;
    }
       }
    } else {
               // only one value it must be the selected one
       return selectOne.value;
  }   }
     }      }
     return \@partlist,\%handgrade;  </script>
   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,$hideexpired) = @_;      my ($getsec,$filterlist,$getgroup) = @_;
     my $classlist=&Apache::loncoursedata::get_classlist();      my @getsec;
       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    = 
  # still a student?              $classlist->{$student}->[&Apache::loncoursedata::CL_START()];
  if (($hideexpired) && ($status ne 'Active')) {          my $id       = 
             delete ($classlist->{$_});              $classlist->{$student}->[&Apache::loncoursedata::CL_ID()];
             next;          my $section  = 
         }              $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
  $section = ($section ne '' ? $section : 'no');          my $fullname = 
  if ($getsec eq 'all' || $getsec eq $section) {              $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()];
             $sections{$section}++;          my $status   = 
             $fullnames{$_}=$fullname;              $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()];
         } else {          my $group   = 
             delete($classlist->{$_});              $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
         }   # filter students according to status selected
    if ($filterlist && (!($stu_status =~ /Any/))) {
       if (!($stu_status =~ $status)) {
    delete($classlist->{$student});
    next;
       }
    }
    # filter students according to groups selected
    my @stu_groups = split(/,/,$group);
    if (@getgroup) {
       my $exclude = 1;
       foreach my $grp (@getgroup) {
           foreach my $stu_group (@stu_groups) {
               if ($stu_group eq $grp) {
                   $exclude = 0;
                  } 
           }
              if (($grp eq 'none') && !$group) {
                  $exclude = 0;
           }
       }
       if ($exclude) {
           delete($classlist->{$student});
       }
    }
    $section = ($section ne '' ? $section : 'none');
    if (&canview($section)) {
       if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {
    $sections{$section}++;
    if ($classlist->{$student}) {
       $fullnames{$student}=$fullname;
    }
       } else {
    delete($classlist->{$student});
       }
    } else {
       delete($classlist->{$student});
    }
     }      }
     my %seen = ();      my %seen = ();
     my @sections = sort(keys(%sections));      my @sections = sort(keys(%sections));
     return ($classlist,\@sections,\%fullnames);      return ($classlist,\@sections,\%fullnames);
 }  }
   
 #find user domain  sub canmodify {
 sub finduser {      my ($sec)=@_;
     my ($name) = @_;      if ($perm{'mgr'}) {
     my $domain = '';   if (!defined($perm{'mgr_section'})) {
     if ( $Apache::grades::viewgrades eq 'F' ) {      # can modify whole class
  my %classlist=&Apache::lonnet::dump('classlist',      return 1;
     $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},   } else {
     $ENV{'course.'.$ENV{'request.course.id'}.'.num'});      if ($sec eq $perm{'mgr_section'}) {
  my (@fields) = grep /^$name:/, keys %classlist;   #can modify the requested section
  ($name, $domain) = split(/:/,$fields[0]);   return 1;
  return ($name,$domain);      } else {
     } else {   # can't modify the request section
  return ($ENV{'user.name'},$ENV{'user.domain'});   return 0;
       }
    }
     }      }
       #can't modify
       return 0;
 }  }
   
 #--- Prompts a user to enter a username.  sub canview {
 sub moreinfo {      my ($sec)=@_;
     my ($request,$reason) = @_;      if ($perm{'vgr'}) {
     $request->print("Unable to process request: $reason");   if (!defined($perm{'vgr_section'})) {
     if ( $Apache::grades::viewgrades eq 'F' ) {      # can modify whole class
  $request->print('<form action="/adm/grades" method="post">'."\n");      return 1;
  if ($ENV{'form.url'}) {   } else {
     $request->print('<input type="hidden" name="url" value="'.$ENV{'form.url'}.'" />'."\n");      if ($sec eq $perm{'vgr_section'}) {
  }   #can modify the requested section
  if ($ENV{'form.symb'}) {   return 1;
     $request->print('<input type="hidden" name="symb" value="'.$ENV{'form.symb'}.'" />'."\n");      } else {
  }   # can't modify the request section
  $request->print('<input type="hidden" name="command" value="'.$ENV{'form.command'}.'" />'."\n");   return 0;
  $request->print("Student:".'<input type="text" name="student" value="'.$ENV{'form.student'}.'" />'."<br />\n");      }
  $request->print("Domain:".'<input type="text" name="domain" value="'.$ENV{'user.domain'}.'" />'."<br />\n");   }
  $request->print('<input type="submit" name="submit" value="ReSubmit" />'."<br />\n");  
  $request->print('</form>');  
     }      }
     return '';      #can't modify
       return 0;
 }  }
   
 #--- 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,$foo)    = split(/_/,$record{"resource.$_.solved"},2);   my ($status,undef)   = split(/_/,$record{"resource.$_.solved"},2);
  $status              = 'nothing' if ($status eq '');   $status              = 'nothing' if ($status eq '');
  $partstatus{$_}      = $status;   $partstatus{$_}      = $status;
  my $subkey           = "resource.$_.submitted_by";   my $subkey           = "resource.$_.submitted_by";
Line 195  sub student_gradeStatus { Line 621  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 $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
     my $jscript='<script type="text/javascript" language="javascript">'."\n".      my $jscript='<script type="text/javascript" language="javascript">'."\n".
  '    function viewOneStudent(user,domain) {'."\n".   '    function viewOneStudent(user,domain) {'."\n".
  ' document.onestudent.student.value = user;'."\n".   ' document.onestudent.student.value = user;'."\n".
Line 204  sub jscriptNform { Line 631  sub jscriptNform {
  '    }'."\n".   '    }'."\n".
  '</script>'."\n";   '</script>'."\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="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
    '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n".
    '<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".
  '<input type="hidden" name="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 213  sub jscriptNform { Line 642  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 --------------------
   
   #
   # Find most similar essay
   #
   
   sub most_similar {
       my ($uname,$udom,$uessay,$old_essays)=@_;
   
   # ignore spaces and punctuation
   
       $uessay=~s/\W+/ /gs;
   
   # ignore empty submissions (occuring when only files are sent)
   
       unless ($uessay=~/\w+/) { return ''; }
   
   # these will be returned. Do not care if not at least 50 percent similar
       my $limit=0.6;
       my $sname='';
       my $sdom='';
       my $scrsid='';
       my $sessay='';
   # go through all essays ...
       foreach my $tkey (keys(%$old_essays)) {
    my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey));
   # ... except the same student
           next if (($tname eq $uname) && ($tdom eq $udom));
    my $tessay=$old_essays->{$tkey};
    $tessay=~s/\W+/ /gs;
   # String similarity gives up if not even limit
    my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit);
   # Found one
    if ($tsimilar>$limit) {
       $limit=$tsimilar;
       $sname=$tname;
       $sdom=$tdom;
       $scrsid=$tcrsid;
       $sessay=$old_essays->{$tkey};
    }
       }
       if ($limit>0.6) {
          return ($sname,$sdom,$scrsid,$sessay,$limit);
       } else {
          return ('','','','',0);
       }
   }
   
 #-------------------------------------------------------------------  #-------------------------------------------------------------------
   
 #------------------------------------ Receipt Verification Routines  #------------------------------------ Receipt Verification Routines
Line 222  sub jscriptNform { Line 727  sub jscriptNform {
 sub verifyreceipt {  sub verifyreceipt {
     my $request  = shift;      my $request  = shift;
   
     my $courseid = $ENV{'request.course.id'};      my $courseid = $env{'request.course.id'};
     my $receipt  = unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'}).'-'.      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)   = &get_symb($request);
     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.url'}.'</font><br><br>'."\n";   &mt('Verifying Submission Receipt [_1]',$receipt).
    '</span></h3>'."\n".
    '<h4>'.&mt('<b>Resource: </b>[_1]',$env{'form.probTitle'}).
    '</h4>'."\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');
       
     foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {      my $receiptparts=0;
       if ($env{"course.$courseid.receiptalg"} eq 'receipt2' ||
    $env{"course.$courseid.receiptalg"} eq 'receipt3') { $receiptparts=1; }
       my $parts=['0'];
       if ($receiptparts) { ($parts)=&response_type($symb); }
       
       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(/\:/);
  if ($receipt eq    foreach my $part (@$parts) {
     &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb)) {      if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) {
     $contents.='<tr bgcolor="#ffffe6"><td>&nbsp;'."\n".   $contents.=
  '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.      &Apache::loncommon::start_data_table_row().
  '\')"; TARGET=_self>'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".      '<td>&nbsp;'."\n".
  '<td>&nbsp;'.$uname.'&nbsp;</td>'.      '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
  '<td>&nbsp;'.$udom.'&nbsp;</td></tr>'."\n";      '\');" target="_self">'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".
           '<td>&nbsp;'.$uname.'&nbsp;</td>'.
     $matches++;      '<td>&nbsp;'.$udom.'&nbsp;</td>';
    if ($receiptparts) {
       $contents.='<td>&nbsp;'.$part.'&nbsp;</td>';
    }
    $contents.= 
       &Apache::loncommon::end_data_table_row()."\n";
   
    $matches++;
       }
  }   }
     }      }
     if ($matches == 0) {      if ($matches == 0) {
  $string = $title.'No match found for the above receipt.';   $string = $title.&mt('No match found for the above receipt.');
     } 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 matches the following [numerate,_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".  
     '<td><b>&nbsp;Username&nbsp;</b></td>'."\n".  
     '<td><b>&nbsp;Domain&nbsp;</b></td></tr>'."\n".  
     $contents.      $contents.
     '</table></td></tr></table>'."\n";      &Apache::loncommon::end_data_table()."\n";
     }      }
     return $string.&show_grading_menu_form($symb,$url);      return $string.&show_grading_menu_form($symb);
 }  }
   
 #--- This is called by a number of programs.  #--- This is called by a number of programs.
Line 276  sub verifyreceipt { Line 809  sub verifyreceipt {
 sub listStudents {  sub listStudents {
     my ($request) = shift;      my ($request) = shift;
   
     my ($symb,$url) = &get_symb_and_url();      my ($symb) = &get_symb($request);
     my $cdom      = $ENV{"course.$ENV{'request.course.id'}.domain"};      my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
     my $cnum      = $ENV{"course.$ENV{'request.course.id'}.num"};      my $cnum      = $env{"course.$env{'request.course.id'}.num"};
     my $getsec    = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'};      my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
     my $submitonly= $ENV{'form.submitonly'} eq '' ? 'all' : $ENV{'form.submitonly'};      my $getgroup  = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
       my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
     my $result;      my $viewgrade = $env{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View';
     my ($partlist,$handgrade) = &response_type($url);      $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 
     for (sort keys(%$handgrade)) {   &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
  my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_});  
  $ENV{'form.handgrade'} = 'yes' if ($handgrade eq 'yes');      my $result='<h3><span class="LC_info">&nbsp;'.
  $result.='<tr><td><b>Part </b>'.(split(/_/))[0].'</td>'.   &mt($viewgrade.' Submissions for a Student or a Group of Students')
     '<td><b>Type: </b>'.$responsetype.'</td>'.   .'</span></h3>';
     '<td><b>Handgrade: </b>'.$handgrade.'</font></td></tr>';  
     }      my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($symb,$env{'form.probTitle'},($env{'form.showgrading'} eq 'yes'));
     $result.='</table>';  
       my %lt = ( 'multiple' =>
     my $viewgrade;         "Please select a student or group of students before clicking on the Next button.",
     if ($ENV{'form.handgrade'} eq 'yes') {         'single'   =>
  $viewgrade = 'View/Grade';         "Please select the student before clicking on the Next button.",
     } else {         );
  $viewgrade = 'View';      %lt = &Apache::lonlocal::texthash(%lt);
     }  
   
     $result='<h3><font color="#339933">&nbsp;'.  
  $viewgrade.  
     ' Submissions for a Student or a Group of Students</font></h3>'.  
  '<table border="0"><tr><td colspan=3><font size=+1>'.  
     '<b>Resource: </b>'.$url.'</font></td></tr>'.$result;  
   
     $request->print(<<LISTJAVASCRIPT);      $request->print(<<LISTJAVASCRIPT);
 <script type="text/javascript" language="javascript">  <script type="text/javascript" language="javascript">
   function checkSelect(checkBox) {      function checkSelect(checkBox) {
     var ctr=0;   var ctr=0;
     var sense="";   var sense="";
     if (checkBox.length > 1) {   if (checkBox.length > 1) {
        for (var i=0; i<checkBox.length; i++) {      for (var i=0; i<checkBox.length; i++) {
   if (checkBox[i].checked) {   if (checkBox[i].checked) {
      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) {
       alert(sense);
       return false;
    }
    document.gradesub.submit();
     }      }
     if (ctr == 0) {  
        alert("Please select "+sense+" before clicking on the $viewgrade button.");      function reLoadList(formname) {
        return false;   if (formname.saveStatusOld.value == pullDownSelection(formname.Status)) {return;}
    formname.command.value = 'submission';
    formname.submit();
     }      }
     document.gradesub.submit();  
   }  
 </script>  </script>
 LISTJAVASCRIPT  LISTJAVASCRIPT
   
       &commonJSfunctions($request);
     $request->print($result);      $request->print($result);
   
     my $checkhdgrade = $ENV{'form.handgrade'} eq 'yes' ? 'checked' : '';      my $checkhdgrade = ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1 ) ? 'checked="checked"' : '';
     my $checklastsub = $ENV{'form.handgrade'} eq 'yes' ? '' : 'checked';      my $checklastsub = $checkhdgrade eq '' ? 'checked="checked"' : '';
       my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.
     my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'."\n".   "\n".$table;
  '&nbsp;<b>View Problem: </b><input type="radio" name="vProb" value="no" /> no '."\n".  
  '<input type="radio" name="vProb" value="yes" checked /> one student '."\n".      $gradeTable .= 
  '<input type="radio" name="vProb" value="all" /> all students <br />'."\n".   '&nbsp;'.
  '&nbsp;<b>Submissions: </b>'."\n";   &mt('<b>View Problem Text: </b>[_1]',
     if ($ENV{'form.handgrade'} eq 'yes') {      '<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n".
  $gradeTable.='<input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> handgrade only'."\n";      '<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n".
     }      '<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label>').'<br />'."\n";
     $gradeTable.='<input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> last sub only'."\n".      $gradeTable .= 
  '<input type="radio" name="lastSub" value="last" /> last sub & parts info'."\n".   '&nbsp;'.
  '<input type="radio" name="lastSub" value="all" /> all details'."\n".   &mt('<b>View Answer: </b>[_1]',
  '<input type="hidden" name="section"     value="'.$getsec.'" />'."\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 $submission_options;
       if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {
    $submission_options.=
       '<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> '.&mt('essay part only').' </label>'."\n";
       }
       my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
       my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status;
       $env{'form.Status'} = $saveStatus;
       $submission_options.=
    '<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> '.&mt('last submission only').' </label>'."\n".
    '<label><input type="radio" name="lastSub" value="last" /> '.&mt('last submission &amp; parts info').' </label>'."\n".
    '<label><input type="radio" name="lastSub" value="datesub" /> '.&mt('by dates and submissions').' </label>'."\n".
    '<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').'</label>';
       $gradeTable .= 
    '&nbsp;'.
    &mt('<b>Submissions: </b>[_1]',$submission_options).'<br />'."\n";
   
       $gradeTable .= 
           '&nbsp;'.
    &mt('<b>Grading Increments:</b> [_1]',
       '<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>');
       
       $gradeTable .= 
           &build_section_inputs().
  '<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".   '<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".
  '<input type="hidden" name="response"    value="'.$ENV{'form.response'}.'" />'."\n".   '<input type="hidden" name="handgrade"   value="'.$env{'form.handgrade'}.'" /><br />'."\n".
  '<input type="hidden" name="handgrade": #falue="'.$ENV{'form.handgrade'}.'" /><br />'."\n".   '<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" /><br />'."\n".
  '<input type="hidden" name="showgrading" value="'.$ENV{'form.showgrading'}.'" /><br />'."\n".   '<input type="hidden" name="saveState"   value="'.$env{'form.saveState'}.'" />'."\n".
  '<input type="hidden" name="url"  value="'.$url.'" />'."\n".   '<input type="hidden" name="probTitle"   value="'.$env{'form.probTitle'}.'" />'."\n".
  '<input type="hidden" name="symb" value="'.$symb.'" />'."\n".   '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
  'To '.lc($viewgrade).' a submission, click on the check box next to the student\'s name. Then '."\n".   '<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";
  'click on the '.$viewgrade.' button. To view the submissions for a group of students, click'."\n".  
  ' on the check boxes for the group of students.<br />'."\n".      if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) {
  '<input type="hidden" name="command" value="processGroup" />'."\n".   $gradeTable.='<input type="hidden" name="Status"   value="'.$stu_status.'" />'."\n";
  '<input type="button" '."\n".      } else {
    $gradeTable.=&mt('<b>Student Status:</b> [_1]',
    &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);')).'<br />';
       }
   
       $gradeTable.=&mt('To '.lc($viewgrade).' 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.').'<br />'."\n".
    '<input type="hidden" name="command" value="processGroup" />'."\n";
   
   # checkall buttons
       $gradeTable.=&check_script('gradesub', 'stuinfo');
       $gradeTable.='<input type="button" '."\n".
  'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n".   'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n".
  'value="'.$viewgrade.'" />'."\n";   'value="'.&mt('Next-&gt;').'" /> <br />'."\n";
        $gradeTable.=&check_buttons();
     my (undef,undef,$fullname) = &getclasslist($getsec,'0');      $gradeTable.='<label><input type="checkbox" name="checkPlag" checked="checked" />'.&mt('Check For Plagiarism').'</label>';
           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();
  '<td><b>&nbsp;Select&nbsp;</b></td><td><b>&nbsp;Fullname&nbsp;</b></td>'.      my $loop = 0;
  '<td><b>&nbsp;Username&nbsp;</b></td><td><b>&nbsp;Domain&nbsp;</b></td>';      while ($loop < 2) {
     foreach (sort(@$partlist)) {   $gradeTable.='<th>'.&mt('No.').'</th><th>'.&mt('Select').'</th>'.
  $gradeTable.='<td><b>&nbsp;Part '.(split(/_/))[0].' Status&nbsp;</b></td>';      '<th>'.&nameUserString('header').'&nbsp;'.&mt('Section/Group').'</th>';
    if ($env{'form.showgrading'} eq 'yes' 
       && $submitonly ne 'queued'
       && $submitonly ne 'all') {
       foreach my $part (sort(@$partlist)) {
    my $display_part=
       &get_display_part((split(/_/,$part))[0],$symb);
    $gradeTable.=
       '<th>'.&mt('Part: [_1] Status',$display_part).'</th>';
       }
    } elsif ($submitonly eq 'queued') {
       $gradeTable.='<th>'.&mt('Queue Status').'&nbsp;</th>';
    }
    $loop++;
   # $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) =&student_gradeStatus($url,$symb,$udom,$uname,$partlist);  
  my $statusflg = '';   my %status = ();
  foreach (keys(%status)) {  
     $statusflg = 1 if ($status{$_} ne 'nothing');   if ($submitonly eq 'queued') {
     my ($foo,$partid,$foo1) = split(/\./,$_);      my %queue_status = 
     if ($status{'resource.'.$partid.'.submitted_by'} ne '') {   &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
  $statusflg = '';   $udom,$uname);
  $gradeTable.='<input type="hidden" name="'.      next if (!defined($queue_status{'gradingqueue'}));
     $student.':submitted_by" value="'.      $status{'gradingqueue'} = $queue_status{'gradingqueue'};
     $status{'resource.'.$partid.'.submitted_by'}.'" />';   }
   
    if ($env{'form.showgrading'} eq 'yes' 
       && $submitonly ne 'queued'
       && $submitonly ne 'all') {
       (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist);
       my $submitted = 0;
       my $graded = 0;
       my $incorrect = 0;
       foreach (keys(%status)) {
    $submitted = 1 if ($status{$_} ne 'nothing');
    $graded = 1 if ($status{$_} =~ /^ungraded/);
    $incorrect = 1 if ($status{$_} =~ /^incorrect/);
   
    my ($foo,$partid,$foo1) = split(/\./,$_);
    if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
       $submitted = 0;
       my ($part)=split(/\./,$partid);
       $gradeTable.='<input type="hidden" name="'.
    $student.':'.$part.':submitted_by" value="'.
    $status{'resource.'.$partid.'.submitted_by'}.'" />';
    }
     }      }
       
       next if (!$submitted && ($submitonly eq 'yes' ||
        $submitonly eq 'incorrect' ||
        $submitonly eq 'graded'));
       next if (!$graded && ($submitonly eq 'graded'));
       next if (!$incorrect && $submitonly eq 'incorrect');
  }   }
  next if ($statusflg eq '' && $submitonly eq 'yes');  
   
  $ctr++;   $ctr++;
  if ( $Apache::grades::viewgrades eq 'F' ) {   my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
     $gradeTable.='<tr bgcolor="#ffffe6">'.          my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
  '<td align="center"><input type=checkbox name="stuinfo" value="'.   if ( $perm{'vgr'} eq 'F' ) {
  $student.':'.$$fullname{$student}.'"></td>'."\n".      if ($ctr%2 ==1) {
  '<td>&nbsp;'.$$fullname{$student}.'&nbsp;</td>'."\n".   $gradeTable.= &Apache::loncommon::start_data_table_row();
  '<td>&nbsp;'.$uname.'&nbsp;</td>'."\n".      }
  '<td align="middle">&nbsp;'.$udom.'&nbsp;</td>'."\n";      $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.
                      '<td align="center"><label><input type=checkbox name="stuinfo" value="'.
     foreach (sort keys(%status)) {                 $student.':'.$$fullname{$student}.':::SECTION'.$section.
  next if (/^resource.*?submitted_by$/);         ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.
  $gradeTable.='<td align="middle">&nbsp;'.$status{$_}.'&nbsp;</td>'."\n";         &nameUserString(undef,$$fullname{$student},$uname,$udom).
          '&nbsp;'.$section.($group ne '' ?'/'.$group:'').'</td>'."\n";
   
       if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {
    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);
       if ($ctr%2 ==0) {
    $gradeTable.=&Apache::loncommon::end_data_table_row()."\n";
     }      }
     $gradeTable.='</tr>'."\n";  
  }   }
     }      }
     $gradeTable.='</table></td></tr></table>'.      if ($ctr%2 ==1) {
    $gradeTable.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>';
       if ($env{'form.showgrading'} eq 'yes' 
    && $submitonly ne 'queued'
    && $submitonly ne 'all') {
    foreach (@$partlist) {
       $gradeTable.='<td>&nbsp;</td>';
    }
       } elsif ($submitonly eq 'queued') {
    $gradeTable.='<td>&nbsp;</td>';
       }
    $gradeTable.=&Apache::loncommon::end_data_table_row();
       }
   
       $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="'.$viewgrade.'" /></form>'."\n";   'value="'.&mt('Next-&gt;').'" /></form>'."\n";
     if ($ctr == 0) {      if ($ctr == 0) {
  $gradeTable='<br />&nbsp;<font color="red">'.   my $num_students=(scalar(keys(%$fullname)));
     'No submission found for this resource.</font><br />';   if ($num_students eq 0) {
       $gradeTable='<br />&nbsp;<span class="LC_warning">'.&mt('There are no students currently enrolled.').'</span>';
    } else {
       my $submissions='submissions';
       if ($submitonly eq 'incorrect') { $submissions = 'incorrect submissions'; }
       if ($submitonly eq 'graded'   ) { $submissions = 'ungraded submissions'; }
       if ($submitonly eq 'queued'   ) { $submissions = 'queued submissions'; }
       $gradeTable='<br />&nbsp;<span class="LC_warning">'.
    &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);      $gradeTable.=&show_grading_menu_form($symb);
     $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='<script type="text/javascript">
       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;
                                          }
           }
       }
   
   </script>'."\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)  = shift;
     my $ctr        = 0;      my $ctr        = 0;
     my @stuchecked = (ref($ENV{'form.stuinfo'}) ? @{$ENV{'form.stuinfo'}}      my @stuchecked = &Apache::loncommon::get_env_multiple('form.stuinfo');
       : ($ENV{'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);
  $ctr++;   $ctr++;
     }      }
Line 453  sub sub_page_js { Line 1155  sub sub_page_js {
     my $request = shift;      my $request = shift;
     $request->print(<<SUBJAVASCRIPT);      $request->print(<<SUBJAVASCRIPT);
 <script type="text/javascript" language="javascript">  <script type="text/javascript" language="javascript">
   function updateRadio(radioButton,formtextbox,formsel,scores,weight) {      function updateRadio(formname,id,weight) {
      var pts = formtextbox.value;   var gradeBox = formname["GD_BOX"+id];
      var resetbox =false;   var radioButton = formname["RADVAL"+id];
      if (isNaN(pts) || pts < 0) {   var oldpts = formname["oldpts"+id].value;
         alert("A number equal or greater than 0 is expected. Entered value = "+pts);   var pts = checkSolved(formname,id) == 'update' ? gradeBox.value : oldpts;
         for (var i=0; i<radioButton.length; i++) {   gradeBox.value = pts;
            if (radioButton[i].checked) {   var resetbox = false;
       formtextbox.value = i;   if (isNaN(pts) || pts < 0) {
       resetbox = true;      alert("A number equal or greater than 0 is expected. Entered value = "+pts);
    }      for (var i=0; i<radioButton.length; i++) {
    if (radioButton[i].checked) {
       gradeBox.value = i;
       resetbox = true;
    }
       }
       if (!resetbox) {
    formtextbox.value = "";
       }
       return;
    }
   
    if (pts > weight) {
       var resp = confirm("You entered a value ("+pts+
          ") greater than the weight for the part. Accept?");
       if (resp == false) {
    gradeBox.value = oldpts;
    return;
       }
    }
   
    for (var i=0; i<radioButton.length; i++) {
       radioButton[i].checked=false;
       if (pts == i && pts != "") {
    radioButton[i].checked=true;
       }
  }   }
  if (!resetbox) {   updateSelect(formname,id);
    formtextbox.value = "";   formname["stores"+id].value = "0";
       }
   
       function writeBox(formname,id,pts) {
    var gradeBox = formname["GD_BOX"+id];
    if (checkSolved(formname,id) == 'update') {
       gradeBox.value = pts;
    } else {
       var oldpts = formname["oldpts"+id].value;
       gradeBox.value = oldpts;
       var radioButton = formname["RADVAL"+id];
       for (var i=0; i<radioButton.length; i++) {
    radioButton[i].checked=false;
    if (i == oldpts) {
       radioButton[i].checked=true;
    }
       }
  }   }
    formname["stores"+id].value = "0";
    updateSelect(formname,id);
  return;   return;
      }      }
   
      if (pts > weight) {      function clearRadBox(formname,id) {
         var resp = confirm("You entered a value ("+pts+   if (checkSolved(formname,id) == 'noupdate') {
           ") greater than the weight for the part. Accept?");      updateSelect(formname,id);
         if (resp == false) {      return;
            formtextbox.value = "";   }
            return;   gradeSelect = formname["GD_SEL"+id];
        }   for (var i=0; i<gradeSelect.length; i++) {
       if (gradeSelect[i].selected) {
    var selectx=i;
       }
    }
    var stores = formname["stores"+id];
    if (selectx == stores.value) { return };
    var gradeBox = formname["GD_BOX"+id];
    gradeBox.value = "";
    var radioButton = formname["RADVAL"+id];
    for (var i=0; i<radioButton.length; i++) {
       radioButton[i].checked=false;
    }
    stores.value = selectx;
     }      }
   
     for (var i=0; i<radioButton.length; i++) {      function checkSolved(formname,id) {
  radioButton[i].checked=false;   if (formname["solved"+id].value == "correct_by_student" && formname.overRideScore.value == 'no') {
  if (pts == i) {      var reply = confirm("This problem has been graded correct by the computer. Do you want to change the score?");
    radioButton[i].checked=true;      if (!reply) {return "noupdate";}
       formname.overRideScore.value = 'yes';
  }   }
    return "update";
     }      }
     updateSelect(formsel);  
     scores.value = "0";  
   }  
   
   function writeBox(formrad,formsel,pts,scores) {      function updateSelect(formname,id) {
     formrad.value = pts;   formname["GD_SEL"+id][0].selected = true;
     scores.value = "0";   return;
     updateSelect(formsel,pts);      }
     return;  
   }  
   
   function clearRadBox(radioButton,formbox,formsel,scores) {  //=========== Check that a point is assigned for all the parts  ============
     for (var i=0; i<formsel.length; i++) {      function checksubmit(formname,val,total,parttot) {
  if (formsel[i].selected) {   formname.gradeOpt.value = val;
     var selectx=i;   if (val == "Save & Next") {
       for (i=0;i<=total;i++) {
    for (j=0;j<parttot;j++) {
       var partid = formname["partid"+i+"_"+j].value;
       if (formname["GD_SEL"+i+"_"+partid][0].selected) {
    var points = formname["GD_BOX"+i+"_"+partid].value;
    if (points == "") {
       var name = formname["name"+i].value;
       var studentID = (name != '' ? name : formname["unamedom"+i].value);
       var resp = confirm("You did not assign a score for "+studentID+
          ", part "+partid+". Continue?");
       if (resp == false) {
    formname["GD_BOX"+i+"_"+partid].focus();
    return false;
       }
    }
       }
       
    }
       }
       
  }   }
    if (val == "Grade Student") {
       formname.showgrading.value = "yes";
       if (formname.Status.value == "") {
    formname.Status.value = "Active";
       }
       formname.studentNo.value = total;
    }
    formname.submit();
     }      }
     if (selectx == scores.value) { return };  
     formbox.value = "";  //======= Check that a score is assigned for all the problems (page/sequence grading only) =========
     for (var i=0; i<radioButton.length; i++) {      function checkSubmitPage(formname,total) {
  radioButton[i].checked=false;   noscore = new Array(100);
    var ptr = 0;
    for (i=1;i<total;i++) {
       var partid = formname["q_"+i].value;
       if (formname["GD_SEL"+i+"_"+partid][0].selected) {
    var points = formname["GD_BOX"+i+"_"+partid].value;
    var status = formname["solved"+i+"_"+partid].value;
    if (points == "" && status != "correct_by_student") {
       noscore[ptr] = i;
       ptr++;
    }
       }
    }
    if (ptr != 0) {
       var sense = ptr == 1 ? ": " : "s: ";
       var prolist = "";
       if (ptr == 1) {
    prolist = noscore[0];
       } else {
    var i = 0;
    while (i < ptr-1) {
       prolist += noscore[i]+", ";
       i++;
    }
    prolist += "and "+noscore[i];
       }
       var resp = confirm("You did not assign any score for the following problem"+sense+prolist+". Continue?");
       if (resp == false) {
    return false;
       }
    }
   
    formname.submit();
     }      }
     scores.value = selectx;  </script>
   }  SUBJAVASCRIPT
   }
   
   function updateSelect(formsel) {  #--- javascript for essay type problem --
     formsel[0].selected = true;  sub sub_page_kw_js {
     return;      my $request = shift;
   }      my $iconpath = $request->dir_config('lonIconsURL');
       &commonJSfunctions($request);
   
 //=================== Check that a point is assigned for all the parts  ==============      my $inner_js_msg_central=<<INNERJS;
   function checksubmit(val,total,parttot) {      <script text="text/javascript">
      document.SCORE.gradeOpt.value = val;      function checkInput() {
      if (val == "Save & Next") {        opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value);
  for (i=0;i<=total;i++) {        var nmsg   = opener.document.SCORE.savemsgN.value;
    for (j=0;j<parttot;j++) {        var usrctr = document.msgcenter.usrctr.value;
       var partid = eval("document.SCORE.partid"+i+"_"+j+".value");        var newval = opener.document.SCORE["newmsg"+usrctr];
       var selopt = eval("document.SCORE.GD_SEL"+i+"_"+partid);        newval.value = opener.checkEntities(document.msgcenter.newmsg.value);
       if (selopt[0].selected) {  
  var points = eval("document.SCORE.GD_BOX"+i+"_"+partid+".value");        var msgchk = "";
  if (points == "") {        if (document.msgcenter.subchk.checked) {
      var name = eval("document.SCORE.name"+i+".value");           msgchk = "msgsub,";
      alert("Please assign a score for "+name+", part "+partid+".");        }
      return false;        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()
   
       }
       </script>
   INNERJS
   
       my $inner_js_highlight_central=<<INNERJS;
    <script type="text/javascript">
       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()
       }
   </script>
   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();
      document.SCORE.submit();      $docopen=~s/^document\.//;
  }      $request->print(<<SUBJAVASCRIPT);
   <script type="text/javascript" language="javascript">
   
 //===================== Show list of keywords ====================  //===================== Show list of keywords ====================
   function keywords(keyform) {    function keywords(formname) {
     var keywds = keyform.value;      var nret = prompt("Keywords list, separated by a space. Add/delete to list if desired.",formname.keywords.value);
     var nret = prompt("Keywords list, separated by a space. Add/delete to list if desired.",keywds);  
     if (nret==null) return;      if (nret==null) return;
     keyform.value = nret;      formname.keywords.value = nret;
   
     document.SCORE.refresh.value = "on";      if (formname.keywords.value != "") {
     if (document.SCORE.keywords.value != "") {   formname.refresh.value = "on";
  document.SCORE.submit();   formname.submit();
     }      }
     return;      return;
   }    }
Line 574  sub sub_page_js { Line 1445  sub sub_page_js {
     }      }
     var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);      var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);
     if (nret==null) return;      if (nret==null) return;
     var curlist = document.SCORE.keywords.value;      document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret;
     document.SCORE.keywords.value = curlist+" "+nret;  
     document.SCORE.refresh.value = "on";  
     if (document.SCORE.keywords.value != "") {      if (document.SCORE.keywords.value != "") {
    document.SCORE.refresh.value = "on";
  document.SCORE.submit();   document.SCORE.submit();
     }      }
     return;      return;
   }    }
   
 //====================== Script for composing message ==============  //====================== Script for composing message ==============
      // preload images
      img1 = new Image();
      img1.src = "$iconpath/mailbkgrd.gif";
      img2 = new Image();
      img2.src = "$iconpath/mailto.gif";
   
   function msgCenter(msgform,usrctr,fullname) {    function msgCenter(msgform,usrctr,fullname) {
     var Nmsg  = msgform.savemsgN.value;      var Nmsg  = msgform.savemsgN.value;
     savedMsgHeader(Nmsg,usrctr,fullname);      savedMsgHeader(Nmsg,usrctr,fullname);
     var subject = msgform.msgsub.value;      var subject = msgform.msgsub.value;
     var rtrchk  = eval("document.SCORE.includemsg"+usrctr);      var msgchk = document.SCORE["includemsg"+usrctr].value;
     var msgchk = rtrchk.value;  
     re = /msgsub/;      re = /msgsub/;
     var shwsel = "";      var shwsel = "";
     if (re.test(msgchk)) { shwsel = "checked" }      if (re.test(msgchk)) { shwsel = "checked" }
     displaySubject(subject,shwsel);      subject = (document.SCORE.shownSub.value == 0 ? checkEntities(subject) : subject);
       displaySubject(checkEntities(subject),shwsel);
     for (var i=1; i<=Nmsg; i++) {      for (var i=1; i<=Nmsg; i++) {
  var testpt = "savemsg"+i+",";   var testmsg = "savemsg"+i+",";
  re = /testpt/;   re = new RegExp(testmsg,"g");
  shwsel = "";   shwsel = "";
  if (re.test(msgchk)) { shwsel = "checked" }   if (re.test(msgchk)) { shwsel = "checked" }
  var message = eval("document.SCORE.savemsg"+i+".value");   var message = document.SCORE["savemsg"+i].value;
  displaySavedMsg(i,message,shwsel);   message = (document.SCORE["shownOnce"+i].value == 0 ? checkEntities(message) : message);
    displaySavedMsg(i,message,shwsel); //I do not get it. w/o checkEntities on saved messages,
                                      //any &lt; is already converted to <, etc. However, only once!!
     }      }
     newmsg = eval("document.SCORE.newmsg"+usrctr+".value");      newmsg = document.SCORE["newmsg"+usrctr].value;
     shwsel = "";      shwsel = "";
     re = /newmsg/;      re = /newmsg/;
     if (re.test(msgchk)) { shwsel = "checked" }      if (re.test(msgchk)) { shwsel = "checked" }
Line 611  sub sub_page_js { Line 1489  sub sub_page_js {
     return;      return;
   }    }
   
     function checkEntities(strx) {
       if (strx.length == 0) return strx;
       var orgStr = ["&", "<", ">", '"']; 
       var newStr = ["&amp;", "&lt;", "&gt;", "&quot;"];
       var counter = 0;
       while (counter < 4) {
    strx = strReplace(strx,orgStr[counter],newStr[counter]);
    counter++;
       }
       return strx;
     }
   
     function strReplace(strx, orgStr, newStr) {
       return strx.split(orgStr).join(newStr);
     }
   
   function savedMsgHeader(Nmsg,usrctr,fullname) {    function savedMsgHeader(Nmsg,usrctr,fullname) {
     var height = 30*Nmsg+250;      var height = 70*Nmsg+250;
     var scrollbar = "no";      var scrollbar = "no";
     if (height > 600) {      if (height > 600) {
  height = 600;   height = 600;
  scrollbar = "yes";   scrollbar = "yes";
     }      }
 /*    if (window.pWin)      var xpos = (screen.width-600)/2;
  window.pWin.close(); */      xpos = (xpos < 0) ? '0' : xpos;
     pWin = window.open('', 'MessageCenter', 'toolbar=no,location=no,scrollbars='+scrollbar+',screenx=70,screeny=75,width=600,height='+height);      var ypos = (screen.height-height)/2-30;
     pWin.document.write("<html><head>");      ypos = (ypos < 0) ? '0' : ypos;
     pWin.document.write("<title>Message Central</title>");  
       pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars='+scrollbar+',screenx='+xpos+',screeny='+ypos+',width=600,height='+height);
     pWin.document.write("<script language=javascript>");      pWin.focus();
     pWin.document.write("function checkInput() {");      pDoc = pWin.document;
     pWin.document.write("  opener.document.SCORE.msgsub.value = document.msgcenter.msgsub.value;");      pDoc.$docopen;
     pWin.document.write("  var nmsg   = opener.document.SCORE.savemsgN.value;");      pDoc.write('$start_page_msg_central');
     pWin.document.write("  var usrctr = document.msgcenter.usrctr.value;");  
     pWin.document.write("  var newval = eval(\\"opener.document.SCORE.newmsg\\"+usrctr);");      pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");
     pWin.document.write("  newval.value = document.msgcenter.newmsg.value;");      pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");
       pDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Compose Message for \"+fullname+\"<\\/span><\\/h3><br /><br />");
     pWin.document.write("  var msgchk = \\"\\";");  
     pWin.document.write("  if (document.msgcenter.subchk.checked) {");      pDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
     pWin.document.write("     msgchk = \\"msgsub,\\";");      pDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
     pWin.document.write("  }");      pDoc.write("<td><b>Type<\\/b><\\/td><td><b>Include<\\/b><\\/td><td><b>Message<\\/td><\\/tr>");
     pWin.document.write(   "for (var i=1; i<=nmsg; i++) {");  
     pWin.document.write("      var opnmsg = eval(\\"opener.document.SCORE.savemsg\\"+i);");  
     pWin.document.write("      var frmmsg = eval(\\"document.msgcenter.msg\\"+i);");  
     pWin.document.write("      opnmsg.value = frmmsg.value;");  
     pWin.document.write("      var chkbox = eval(\\"document.msgcenter.msgn\\"+i);");  
     pWin.document.write("      if (chkbox.checked) {");  
     pWin.document.write("         msgchk += \\"savemsg\\"+i+\\",\\";");  
     pWin.document.write("      }");  
     pWin.document.write("  }");  
     pWin.document.write("  if (document.msgcenter.newmsgchk.checked) {");  
     pWin.document.write("     msgchk += \\"newmsg\\"+usrctr;");  
     pWin.document.write("  }");  
     pWin.document.write("  var includemsg = eval(\\"opener.document.SCORE.includemsg\\"+usrctr);");  
     pWin.document.write("  includemsg.value = msgchk;");  
   
     pWin.document.write("  self.close()");  
   
     pWin.document.write("}");  
   
     pWin.document.write("<");  
     pWin.document.write("/script>");  
   
     pWin.document.write("</head><body bgcolor=white>");  
   
     pWin.document.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");  
     pWin.document.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");  
     pWin.document.write("<font color=\\"green\\" size=+1>&nbsp;Compose Message for \"+fullname+\"</font><br><br>");  
   
     pWin.document.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");  
     pWin.document.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");  
     pWin.document.write("<td><b>Type</b></td><td><b>Include</b></td><td><b>Message</td></tr>");  
 }  }
     function displaySubject(msg,shwsel) {      function displaySubject(msg,shwsel) {
     pWin.document.write("<tr bgcolor=\\"#ffffdd\\">");      pDoc = pWin.document;
     pWin.document.write("<td>Subject</td>");      pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     pWin.document.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"></td>");      pDoc.write("<td>Subject<\\/td>");
     pWin.document.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"60\\" maxlength=\\"80\\"></td></tr>");      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>");
 }  }
   
 function displaySavedMsg(ctr,msg,shwsel) {    function displaySavedMsg(ctr,msg,shwsel) {
     pWin.document.write("<tr bgcolor=\\"#ffffdd\\">");      pDoc = pWin.document;
     pWin.document.write("<td align=\\"center\\">"+ctr+"</td>");      pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     pWin.document.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"></td>");      pDoc.write("<td align=\\"center\\">"+ctr+"<\\/td>");
     pWin.document.write("<td><input name=\\"msg"+ctr+"\\" type=\\"text\\" value=\\""+msg+"\\" size=\\"60\\" maxlength=\\"80\\"></td></tr>");      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>");
 }  }
   
   function newMsg(newmsg,shwsel) {    function newMsg(newmsg,shwsel) {
     pWin.document.write("<tr bgcolor=\\"#ffffdd\\">");      pDoc = pWin.document;
     pWin.document.write("<td align=\\"center\\">New</td>");      pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     pWin.document.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"></td>");      pDoc.write("<td align=\\"center\\">New<\\/td>");
     pWin.document.write("<td><input name=\\"newmsg\\" type=\\"text\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" value=\\""+newmsg+"\\" size=\\"60\\" maxlength=\\"80\\"></td></tr>");      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>");
 }  }
   
   function msgTail() {    function msgTail() {
     pWin.document.write("</table>");      pDoc = pWin.document;
     pWin.document.write("</td></tr></table>&nbsp;");      pDoc.write("<\\/table>");
     pWin.document.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");      pDoc.write("<\\/td><\\/tr><\\/table>&nbsp;");
     pWin.document.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br><br>");      pDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");
     pWin.document.write("</form>");      pDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");
     pWin.document.write("</body></html>");      pDoc.write("<\\/form>");
       pDoc.write('$end_page_msg_central');
       pDoc.close();
 }  }
   
 //====================== Script for keyword highlight options ==============  //====================== Script for keyword highlight options ==============
Line 729  function displaySavedMsg(ctr,msg,shwsel) Line 1597  function displaySavedMsg(ctr,msg,shwsel)
     return;      return;
   }    }
   
   
   function highlightCentral() {    function highlightCentral() {
     hwdWin = window.open('', 'KeywordHighlightCentral', 'toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx=100,screeny=75');  //    if (window.hwdWin) window.hwdWin.close();
     hwdWin.document.write("<html><head>");      var xpos = (screen.width-400)/2;
     hwdWin.document.write("<title>Highlight Central</title>");      xpos = (xpos < 0) ? '0' : xpos;
       var ypos = (screen.height-330)/2-30;
     hwdWin.document.write("<script language=javascript>");      ypos = (ypos < 0) ? '0' : ypos;
     hwdWin.document.write("function updateChoice(flag) {");  
     hwdWin.document.write("  opener.document.SCORE.kwclr.value = radioSelection(document.hlCenter.kwdclr);");      hwdWin = window.open('', 'KeywordHighlightCentral', 'resizeable=yes,toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx='+xpos+',screeny='+ypos);
     hwdWin.document.write("  opener.document.SCORE.kwsize.value = radioSelection(document.hlCenter.kwdsize);");      hwdWin.focus();
     hwdWin.document.write("  opener.document.SCORE.kwstyle.value = radioSelection(document.hlCenter.kwdstyle);");      var hDoc = hwdWin.document;
     hwdWin.document.write("  opener.document.SCORE.refresh.value = \\"on\\";");      hDoc.$docopen;
     hwdWin.document.write("  if (opener.document.SCORE.keywords.value!=\\"\\"){");      hDoc.write('$start_page_highlight_central');
     hwdWin.document.write("     opener.document.SCORE.submit();");      hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");
     hwdWin.document.write("  }");      hDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Keyword Highlight Options<\\/span><\\/h3><br /><br />");
     hwdWin.document.write("  self.close()");  
     hwdWin.document.write("}");      hDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
       hDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
     hwdWin.document.write("function radioSelection(radioButton) {");      hDoc.write("<td><b>Text Color<\\/b><\\/td><td><b>Font Size<\\/b><\\/td><td><b>Font Style<\\/td><\\/tr>");
     hwdWin.document.write("    var selection=null;");  
     hwdWin.document.write("    for (var i=0; i<radioButton.length; i++) {");  
     hwdWin.document.write("        if (radioButton[i].checked) {");  
     hwdWin.document.write("            selection=radioButton[i].value;");  
     hwdWin.document.write("            return selection;");  
     hwdWin.document.write("        }");  
     hwdWin.document.write("    }");  
     hwdWin.document.write("}");  
   
     hwdWin.document.write("<");  
     hwdWin.document.write("/script>");  
   
     hwdWin.document.write("</head><body bgcolor=white>");  
   
     hwdWin.document.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");  
     hwdWin.document.write("<font color=\\"green\\" size=+1>&nbsp;Keyword Highlight Options</font><br><br>");  
   
     hwdWin.document.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");  
     hwdWin.document.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");  
     hwdWin.document.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) { 
     hwdWin.document.write("<tr bgcolor=\\"#ffffdd\\">");      var hDoc = hwdWin.document;
     hwdWin.document.write("<td align=\\"left\\">");      hDoc.write("<tr bgcolor=\\"#ffffdd\\">");
     hwdWin.document.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+">&nbsp;"+clrtxt+"</td>");      hDoc.write("<td align=\\"left\\">");
     hwdWin.document.write("<td align=\\"left\\">");      hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+">&nbsp;"+clrtxt+"<\\/td>");
     hwdWin.document.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+">&nbsp;"+sztxt+"</td>");      hDoc.write("<td align=\\"left\\">");
     hwdWin.document.write("<td align=\\"left\\">");      hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+">&nbsp;"+sztxt+"<\\/td>");
     hwdWin.document.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+">&nbsp;"+sytxt+"</td>");      hDoc.write("<td align=\\"left\\">");
     hwdWin.document.write("</tr>");      hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+">&nbsp;"+sytxt+"<\\/td>");
       hDoc.write("<\\/tr>");
   }    }
   
   function highlightend() {     function highlightend() { 
     hwdWin.document.write("</table>");      var hDoc = hwdWin.document;
     hwdWin.document.write("</td></tr></table>&nbsp;");      hDoc.write("<\\/table>");
 //    hwdWin.document.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:updateChoice(0)\\">&nbsp;&nbsp;");      hDoc.write("<\\/td><\\/tr><\\/table>&nbsp;");
     hwdWin.document.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;");
     hwdWin.document.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br><br>");      hDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");
     hwdWin.document.write("</form>");      hDoc.write("<\\/form>");
     hwdWin.document.write("</body></html>");      hDoc.write('$end_page_highlight_central');
       hDoc.close();
   }    }
   
 </script>  </script>
 SUBJAVASCRIPT  SUBJAVASCRIPT
 }  }
   
   sub get_increment {
       my $increment = $env{'form.increment'};
       if ($increment != 1 && $increment != .5 && $increment != .25 &&
           $increment != .1) {
           $increment = 1;
       }
       return $increment;
   }
   
   #--- displays the grading box, used in essay type problem and grading by page/sequence
   sub gradeBox {
       my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;
       my $checkIcon = '<img alt="'.&mt('Check Mark').
    '" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
       my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);
       my $wgtmsg = ($wgt > 0) ? &mt('(problem weight)') 
                              : '<span class="LC_info">'.&mt('problem weight assigned by computer').'</span>';
       $wgt       = ($wgt > 0 ? $wgt : '1');
       my $score  = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
     '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));
       my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
       my $display_part= &get_display_part($partid,$symb);
       my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
          [$partid]);
       my $aggtries = $$record{'resource.'.$partid.'.tries'};
       if ($last_resets{$partid}) {
           $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
       }
       $result.='<table border="0"><tr>';
       my $ctr = 0;
       my $thisweight = 0;
       my $increment = &get_increment();
   
       my $radio.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across
       while ($thisweight<=$wgt) {
    $radio.= '<td><span style="white-space: nowrap;"><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
       '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++;
       }
       $radio.='</tr></table>';
   
       my $line.='<input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
    ($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
    'onChange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','.
    $wgt.')" /></td>'."\n";
       $line.='<td>/'.$wgt.' '.$wgtmsg.
    ($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? '&nbsp;'.$checkIcon : '').
    ' </td><td>'."\n";
       $line.='<select name="GD_SEL'.$counter.'_'.$partid.'" '.
    'onChange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";
       if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
    $line.='<option></option>'.
       '<option value="excused" selected="selected">'.&mt('excused').'</option>';
       } else {
    $line.='<option selected="selected"></option>'.
       '<option value="excused" >'.&mt('excused').'</option>';
       }
       $line.='<option value="reset status">'.&mt('reset status').'</option></select>'."\n";
   
   
       $result .= 
    &mt('<td><b>Part:</b></td><td>[_1]</td><td><b>Points:</b></td><td>[_2]</td><td>or</td><td>[_3]</td>',$display_part,$radio,$line);
   
       
       $result.='</tr></table>'."\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="solved'.$counter.'_'.$partid.'" value="'.
    $$record{'resource.'.$partid.'.solved'}.'" />'."\n".
           '<input type="hidden" name="totaltries'.$counter.'_'.$partid.'" value="'.
           $$record{'resource.'.$partid.'.tries'}.'" />'."\n".
           '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.
           $aggtries.'" />'."\n";
       $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record);
       return $result;
   }
   
   sub handback_box {
       my ($symb,$uname,$udom,$counter,$partid,$record) = @_;
       my ($partlist,$handgrade,$responseType) = &response_type($symb);
       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) = @_;      my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode,$form) = @_;
     my $rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,      my $rendered;
       $ENV{'request.course.id'});      my %form = ((ref($form) eq 'HASH')? %{$form} : ());
       &Apache::lonxml::remember_problem_counter();
       if ($mode eq 'both' or $mode eq 'text') {
    $rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,
          $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;
       if ($mode eq 'both' or $mode eq 'answer') {
    &Apache::lonxml::restore_problem_counter();
    $companswer=
       &Apache::loncommon::get_student_answers($symb,$uname,$udom,
       $env{'request.course.id'},
       %form);
     }      }
     my $companswer=&Apache::loncommon::get_student_answers($symb,$uname,$udom,  
    $ENV{'request.course.id'});  
     if ($removeform) {      if ($removeform) {
  $companswer=~s|<form(.*?)>||g;   $companswer=~s|<form(.*?)>||g;
  $companswer=~s|</form>||g;   $companswer=~s|</form>||g;
  $rendered=~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%"><tr><td bgcolor="#e6ffff">';   '<div class="LC_grade_show_problem_header">'.
     $result.='<b> View of the problem - '.$ENV{'form.fullname'}.   &mt('View of the problem').
  '</b></td></tr><tr><td bgcolor="#ffffff">'.$rendered.'<br />';   '</div><div class="LC_grade_show_problem_problem">'.
     $result.='<b>Correct answer:</b><br />'.$companswer;   $rendered.
     $result.='</td></tr></table>';   '</div>';
     $result.='</td></tr></table><br />';      $companswer=
     $request->print($result);   '<div class="LC_grade_show_problem_header">'.
    &mt('Correct answer').
    '</div><div class="LC_grade_show_problem_problem">'.
    $companswer.
    '</div>';
       my $result;
       if ($mode eq 'both') {
    $result=$rendered.$companswer;
       } elsif ($mode eq 'text') {
    $result=$rendered;
       } elsif ($mode eq 'answer') {
    $result=$companswer;
       }
       $result='<div class="LC_grade_show_problem">'.$result.'</div>';
       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) = @_;
       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 build_section_inputs {
       my $section_inputs;
       if ($env{'form.section'} eq '') {
           $section_inputs .= '<input type="hidden" name="section" value="all" />'."\n";
       } else {
           my @sections = &Apache::loncommon::get_env_multiple('form.section');
           foreach my $section (@sections) {
               $section_inputs .= '<input type="hidden" name="section" value="'.$section.'" />'."\n";
           }
       }
       return $section_inputs;
 }  }
   
 # --------------------------- show submissions of a student, option to grade   # --------------------------- show submissions of a student, option to grade 
 sub submission {  sub submission {
     my ($request,$counter,$total) = @_;      my ($request,$counter,$total) = @_;
       my ($uname,$udom)     = ($env{'form.student'},$env{'form.userdom'});
       $udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student?
       my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
       $env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq '';
       my $symb = &get_symb($request); 
       if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; }
   
       if (!&canview($usec)) {
    $request->print('<span class="LC_warning">Unable to view requested student.('.
    $uname.':'.$udom.' in section '.$usec.' in course id '.
    $env{'request.course.id'}.')</span>');
    $request->print(&show_grading_menu_form($symb));
    return;
       }
   
     (my $url=$ENV{'form.url'})=~s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;      if (!$env{'form.lastSub'}) { $env{'form.lastSub'} = 'datesub'; }
 #    if ($ENV{'form.student'} eq '') { &moreinfo($request,'Need student login id'); return ''; }      if (!$env{'form.vProb'}) { $env{'form.vProb'} = 'yes'; }
     my ($uname,$udom)     = ($ENV{'form.student'},$ENV{'form.userdom'});      if (!$env{'form.vAns'}) { $env{'form.vAns'} = 'yes'; }
     ($uname,$udom)        = &finduser($uname) if $udom eq '';      my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
     $ENV{'form.fullname'} = &get_fullname ($uname,$udom) if $ENV{'form.fullname'} eq '';      my $checkIcon = '<img alt="'.&mt('Check Mark').
 #    if ($uname eq '') { &moreinfo($request,'Unable to find student'); return ''; }   '" src="'.$request->dir_config('lonIconsURL').
    '/check.gif" height="16" border="0" />';
     my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));  
     if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; }  
     my $last = ($ENV{'form.lastSub'} eq 'last' ? 'last' : '');  
 #    $ENV{'form.vProb'} = $ENV{'form.vProb'} ne '' ? $ENV{'form.vProb'} : 'yes';  
   
       my %old_essays;
     # header info      # header info
     if ($counter == 0) {      if ($counter == 0) {
  &sub_page_js($request);   &sub_page_js($request);
  $request->print('<h3>&nbsp;<font color="#339933">Submission Record</font></h3>'."\n".   &sub_page_kw_js($request) if ($env{'form.handgrade'} eq 'yes');
  '<font size=+1>&nbsp;<b>Resource: </b>'.$url.'</font>'."\n");   $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 
       &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
    if ($env{'form.handgrade'} eq 'yes' && &files_exist($request, $symb)) {
       &download_all_link($request, $symb);
    }
    $request->print('<h3>&nbsp;<span class="LC_info">'.&mt('Submission Record').'</span></h3>'."\n".
    '<h4>&nbsp;'.&mt('<b>Resource: </b> [_1]',$env{'form.probTitle'}).'</h4>'."\n");
   
  # 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') {   if ($env{'form.vProb'} eq 'yes' or $env{'form.vAns'} eq 'yes') {
     &show_problem($request,$symb,$uname,$udom,0);      my $mode;
       if ($env{'form.vProb'} eq 'yes' && $env{'form.vAns'} eq 'yes') {
    $mode='both';
       } elsif ($env{'form.vProb'} eq 'yes') {
    $mode='text';
       } elsif ($env{'form.vAns'} eq 'yes') {
    $mode='answer';
       }
       &Apache::lonxml::clear_problem_counter();
       $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 '') {   if ($env{'form.kwclr'} eq '' && $env{'form.handgrade'} eq 'yes') {
     %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'};  
     $ENV{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';  
     $ENV{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';  
     $ENV{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';  
     $ENV{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';  
     $ENV{'form.msgsub'}   = $keyhash{$symb.'_subject'} ne '' ?   
  $keyhash{$symb.'_subject'} : &Apache::lonnet::metadata($url,'title');  
     $ENV{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0';  
   
  }  
   
  $request->print('<form action="/adm/grades" method="post" name="SCORE">'."\n".      my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
       $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
       $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
       $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
       $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
       $env{'form.msgsub'}   = $keyhash{$symb.'_subject'} ne '' ? 
    $keyhash{$symb.'_subject'} : $env{'form.probTitle'};
       $env{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0';
    }
    my $overRideScore = $env{'form.overRideScore'} eq '' ? 'no' : $env{'form.overRideScore'};
    my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
    $request->print('<form action="/adm/grades" method="post" name="SCORE" enctype="multipart/form-data">'."\n".
  '<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="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="symb"       value="'.$symb.'" />'."\n".   '<input type="hidden" name="studentNo"  value="" />'."\n".
  '<input type="hidden" name="url"        value="'.$url.'" />'."\n".   '<input type="hidden" name="gradeOpt"   value="" />'."\n".
  '<input type="hidden" name="showgrading" value="'.$ENV{'form.showgrading'}.'" />'."\n".   '<input type="hidden" name="symb"       value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\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="lastSub"    value="'.$ENV{'form.lastSub'}.'" />'."\n".   '<input type="hidden" name="vProb"      value="'.$env{'form.vProb'}.'" />'."\n".
  '<input type="hidden" name="section"    value="'.$ENV{'form.section'}.'">'."\n".   '<input type="hidden" name="vAns"       value="'.$env{'form.vAns'}.'" />'."\n".
  '<input type="hidden" name="submitonly" value="'.$ENV{'form.submitonly'}.'">'."\n".   '<input type="hidden" name="lastSub"    value="'.$env{'form.lastSub'}.'" />'."\n".
  '<input type="hidden" name="response"   value="'.$ENV{'form.response'}.'">'."\n".   &build_section_inputs().
  '<input type="hidden" name="handgrade"  value="'.$ENV{'form.handgrade'}.'">'."\n".   '<input type="hidden" name="submitonly" value="'.$env{'form.submitonly'}.'" />'."\n".
  '<input type="hidden" name="keywords"   value="'.$ENV{'form.keywords'}.'" />'."\n".   '<input type="hidden" name="handgrade"  value="'.$env{'form.handgrade'}.'" />'."\n".
  '<input type="hidden" name="kwclr"      value="'.$ENV{'form.kwclr'}.'" />'."\n".  
  '<input type="hidden" name="kwsize"     value="'.$ENV{'form.kwsize'}.'" />'."\n".  
  '<input type="hidden" name="kwstyle"    value="'.$ENV{'form.kwstyle'}.'" />'."\n".  
  '<input type="hidden" name="msgsub"     value="'.$ENV{'form.msgsub'}.'" />'."\n".  
  '<input type="hidden" name="savemsgN"   value="'.$ENV{'form.savemsgN'}.'" />'."\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') {
       $request->print('<input type="hidden" name="keywords" value="'.$env{'form.keywords'}.'" />'."\n".
       '<input type="hidden" name="kwclr"    value="'.$env{'form.kwclr'}.'" />'."\n".
       '<input type="hidden" name="kwsize"   value="'.$env{'form.kwsize'}.'" />'."\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="savemsgN" value="'.$env{'form.savemsgN'}.'" />'."\n");
       foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) {
    $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="'.
  ($keyhash{$symb.'_savemsg'.$cts} eq '' ? $ENV{'form.savemsg'.$cts} : $keyhash{$symb.'_savemsg'.$cts}).   (!exists($keyhash{$symb.'_savemsg'.$cts}) ? 
  '" />'."\n";   &Apache::lonfeedback::clear_out_html($env{'form.savemsg'.$cts}) :
    &Apache::lonfeedback::clear_out_html($keyhash{$symb.'_savemsg'.$cts})).
    '" />'."\n".
    '<input type="hidden" name="shownOnce'.$cts.'" value="0" />'."\n";
     $cts++;      $cts++;
  }   }
  $request->print($prnmsg);   $request->print($prnmsg);
   
  if ($ENV{'form.handgrade'} eq 'yes' && $ENV{'form.showgrading'} eq 'yes') {   if ($env{'form.handgrade'} eq 'yes' && $env{'form.showgrading'} eq 'yes') {
   #
   # 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.keywords)"; 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
   #
               my (undef,undef,$essayurl) = &Apache::lonnet::decode_symb($symb);
       my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);
       $apath=&escape($apath);
       $apath=~s/\W/\_/gs;
       %old_essays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
         }          }
     }      }
   
     if ($ENV{'form.vProb'} eq 'all') {  # This is where output for one specific student would start
  $request->print('<br /><br /><br />');      my $add_class = ($counter%2) ? 'LC_grade_show_user_odd_row' : '';
  &show_problem($request,$symb,$uname,$udom,1);      $request->print("\n\n".
                       '<div class="LC_grade_show_user '.$add_class.'">'.
       '<div class="LC_grade_user_name">'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'</div>'.
       '<div class="LC_grade_show_user_body">'."\n");
   
       if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {
    my $mode;
    if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {
       $mode='both';
    } elsif ($env{'form.vProb'} eq 'all' ) {
       $mode='text';
    } elsif ($env{'form.vAns'} eq 'all') {
       $mode='answer';
    }
    &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 ($partlist,$handgrade) = &response_type($url);      my ($partlist,$handgrade,$responseType) = &response_type($symb);
   
     # 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".      my $result='<div class="LC_grade_submissions">';
  '<table border="0" width=100%><tr bgcolor="#edffff"><td>'."\n";      
       $result.='<div class="LC_grade_submissions_header">';
     $result.='<b>Fullname: </b>'.$ENV{'form.fullname'}.      $result.= &mt('Submissions');
  '<font color="#999999">&nbsp; &nbsp;Username: '.$uname.'</font>'.  
  '<font color="#999999">&nbsp; &nbsp;Domain: '.$udom.'</font><br />'."\n";  
     $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') {
    $result.='<span class="LC_grade_check_note">'.
       &mt('Part(s) graded correct by the computer is marked with a [_1] symbol.',$checkIcon)."</span>\n";
   
     # If this is handgraded, then check for collaborators  
     my @col_fullnames;  
     my ($classlist,$fullname);  
     if ($ENV{'form.handgrade'} eq 'yes') {  
  my @col_list;  
  ($classlist,undef,$fullname) = &getclasslist('all','0');  
  for (keys (%$handgrade)) {  
     my $ncol = &Apache::lonnet::EXT('resource.'.$_.  
     '.maxcollaborators',  
                                             $symb,$udom,$uname);  
     next if ($ncol <= 0);  
             s/\_/\./g;  
             next if ($record{'resource.'.$_.'.collaborators'} eq '');  
             my (@collaborators) = split(/,?\s+/,  
                                    $record{'resource.'.$_.'.collaborators'});  
             my (@badcollaborators);  
             if (scalar(@collaborators) != 0) {  
                 $result.='<b>Collaborators: </b>';  
                 foreach my $collaborator (@collaborators) {  
                     my ($co_name,$co_dom) = split /\@|:/,$collaborator;  
                     $co_dom = $udom if (! defined($co_dom));  
                     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,$collaborator;  
                         next;  
                     }  
                     push @col_list, @Matches;  
                     foreach (@Matches) {  
                         my ($lastname,$givenn) = split(/,/,$$fullname{$_});  
                         push @col_fullnames, $givenn.' '.$lastname;  
                         $result.=$$fullname{$_}.'&nbsp; &nbsp; &nbsp;';  
                     }  
                 }  
                 $result.='<br />'."\n";  
                 if (scalar(@badcollaborators) > 0) {  
                     $result.='<table border="0"><tr bgcolor="#ffbbbb"><td>';  
                     $result.='This student has submitted ';  
                     if (scalar(@badcollaborators) == 1) {  
                         $result .= 'an invalid collaborator';  
                     } else {  
                         $result .= 'invalid collaborators';  
                     }  
                     $result .= ': '.join(', ',@badcollaborators);  
                       
                 }  
                 if (scalar(@collaborators > $ncol)) {  
                     $result .= '<table border="0"><tr bgcolor="#ffbbbb"><td>';  
                     $result .= 'This student has sumbitted too many '.  
                         'collaborators.  Maximum is '.$ncol;  
                     $result .= '</td></tr></table>';  
                 }  
                 $result.='<input type="hidden" name="collaborator'.$counter.  
                     '" value="'.(join ':',@col_list).'" />'."\n";  
             }  
  }  
     }      }
     $request->print($result."\n");  
   
   
   
       # If any part of the problem is an essay-response (handgraded), then check for collaborators
       my $fullname;
       my $col_fullnames = [];
       if ($env{'form.handgrade'} eq 'yes') {
    (my $sub_result,$fullname,$col_fullnames)=
       &check_collaborators($symb,$uname,$udom,\%record,$handgrade,
    $counter);
    $result.=$sub_result;
       }
       $request->print($result."\n");
       $request->print('</div>'."\n");
     # print student answer/submission      # print student answer/submission
     # Options are (1) Handgaded submission only      # Options are (1) Handgaded 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)$/) {
  if ($ENV{'form.'.$uname.':'.$udom.':submitted_by'}) {   my ($string,$timestamp)= &get_last_submission(\%record);
     my $submitby=''.  
  '<b>Collaborative submission by: </b>'.   my $lastsubonly;
  '<a href="javascript:viewSubmitter(\''.  
  $ENV{'form.'.$uname.':'.$udom.':submitted_by'}.   if ($$timestamp eq '') {
  '\')"; TARGET=_self>'.      $lastsubonly.='<div class="LC_grade_submissions_body">'.$$string[0].'</div>'; 
  $$fullname{$ENV{'form.'.$uname.':'.$udom.':submitted_by'}}.'</a>';   } else {
     $request->print($submitby);      $lastsubonly = '<div class="LC_grade_submissions_body"> <b>Date Submitted:</b> '.$$timestamp."\n";
  } else {  
     my ($string,$timestamp)=      my %seenparts;
  &get_last_submission (%record);      my @part_response_id = &flatten_responseType($responseType);
     my $lastsubonly.=''.      foreach my $part (@part_response_id) {
  ($$timestamp eq '' ? '' : '<b>Date Submitted:</b> '.   next if ($env{'form.lastSub'} eq 'hdgrade' 
  $$timestamp).'';   && $$handgrade{$$part[0].'_'.$$part[1]} ne 'yes');
     if ($$timestamp eq '') {  
  $lastsubonly.='<tr><td bgcolor="#ffffe6">'.$$string[0].'</td></tr>'."\n";   my ($partid,$respid) = @{ $part };
     } else {   my $display_part=&get_display_part($partid,$symb);
  for my $part (sort keys(%$handgrade)) {   if ($env{"form.$uname:$udom:$partid:submitted_by"}) {
     foreach (@$string) {      if (exists($seenparts{$partid})) { next; }
  my ($partid,$respid) = /^resource\.(\d+)\.(\d+)\.submission/;      $seenparts{$partid}=1;
  if ($part eq ($partid.'_'.$respid)) {      my $submitby='<b>Part:</b> '.$display_part.
     my ($ressub,$subval) = split(/:/,$_,2);   ' <b>Collaborative submission by:</b> '.
     $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part '.   '<a href="javascript:viewSubmitter(\''.
  $partid.'</b> <font color="#999999">( ID '.$respid.   $env{"form.$uname:$udom:$partid:submitted_by"}.
  ' )</font>&nbsp; &nbsp;<b>Answer: </b>'.   '\');" target="_self">'.
  &keywords_highlight($subval).'</td></tr>'."\n"   $$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a><br />';
  if ($ENV{'form.lastSub'} eq 'lastonly' ||       $request->print($submitby);
     ($ENV{'form.lastSub'} eq 'hdgrade' &&       next;
      $$handgrade{$part} =~ /:yes$/));   }
    my $responsetype = $responseType->{$partid}->{$respid};
    if (!exists($record{"resource.$partid.$respid.submission"})) {
       $lastsubonly.="\n".'<div class="LC_grade_submission_part"><b>Part:</b> '.
    $display_part.' <span class="LC_internal_info">( ID '.$respid.
    ' )</span>&nbsp; &nbsp;'.
    '<span class="LC_warning">'.&mt('Nothing submitted - no attempts').'</span><br /><br /></div>';
       next;
    }
    foreach my $submission (@$string) {
       my ($partid,$respid) = ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
       if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }
       my ($ressub,$subval) = split(/:/,$submission,2);
       # Similarity check
       my $similar='';
       if($env{'form.checkPlag'}){
    my ($oname,$odom,$ocrsid,$oessay,$osim)=
       &most_similar($uname,$udom,$subval,\%old_essays);
    if ($osim) {
       $osim=int($osim*100.0);
       my %old_course_desc = 
    &Apache::lonnet::coursedescription($ocrsid,
      {'one_time' => 1});
   
       $similar="<hr /><h3><span class=\"LC_warning\">".
    &mt('Essay is [_1]% similar to an essay by [_2] ([_3]:[_4]) in course [_5] (course id [_6]:[_7])',
       $osim,
       &Apache::loncommon::plainname($oname,$odom),
       $oname,$odom,
       $old_course_desc{'description'},
       $old_course_desc{'num'},
       $old_course_desc{'domain'}).
    '</span></h3><blockquote><i>'.
    &keywords_highlight($oessay).
    '</i></blockquote><hr />';
  }   }
     }      }
       my $order=&get_order($partid,$respid,$symb,$uname,$udom);
       if ($env{'form.lastSub'} eq 'lastonly' || 
    ($env{'form.lastSub'} eq 'hdgrade' && 
    $$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) {
    my $display_part=&get_display_part($partid,$symb);
    $lastsubonly.='<div class="LC_grade_submission_part"><b>Part:</b> '.
       $display_part.' <span class="LC_internal_info">( ID '.$respid.
       ' )</span>&nbsp; &nbsp;';
    my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
    if (@$files) {
       $lastsubonly.='<br /><span class="LC_warning">'.&mt('Like all files provided by users, this file may contain virusses').'</span><br />';
       my $file_counter = 0;
       foreach my $file (@$files) {
           $file_counter++;
    &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>'.&mt('Submitted Answer:').' </b>'.
       &cleanRecord($subval,$responsetype,$symb,$partid,
    $respid,\%record,$order);
    if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
    $lastsubonly.='</div>';
       }
  }   }
     }      }
     $lastsubonly.='</td></tr>'."\n";      $lastsubonly.='</div>'."\n";
     $request->print($lastsubonly);  
  }   }
     } else {   $request->print($lastsubonly);
      } elsif ($env{'form.lastSub'} eq 'datesub') {
    my (undef,$responseType,undef,$parts) = &showResourceInfo($symb);
    $request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom));
       } elsif ($env{'form.lastSub'} =~ /^(last|all)$/) {
  $request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,   $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.':'
    .$udom.'" />'."\n");
     # return if view submission with no grading option      # return if view submission with no grading option
     if ($ENV{'form.showgrading'} eq '') {      if ($env{'form.showgrading'} eq '' || (!&canmodify($usec))) {
  $request->print('</td></tr></table></td></tr></table></form>'."\n");   my $toGrade.='<input type="button" value="Grade Student" '.
       'onClick="javascript:checksubmit(this.form,\'Grade Student\',\''
       .$counter.'\');" target="_self" /> &nbsp;'."\n" if (&canmodify($usec));
    $toGrade.='</div>'."\n";
    if (($env{'form.command'} eq 'submission') || 
       ($env{'form.command'} eq 'processGroup' && $counter == $total)) {
       $toGrade.='</form>'.&show_grading_menu_form($symb); 
    }
    $request->print($toGrade);
  return;   return;
       } else {
    $request->print('</div>'."\n");
     }      }
   
     # Grading options      # essay grading message center
     $result='<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n".      if ($env{'form.handgrade'} eq 'yes') {
  '<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".   my $result='<div class="LC_grade_message_center">';
  '<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'      
  .$udom.'" />'."\n";   $result.='<div class="LC_grade_message_center_header">'.
     my ($lastname,$givenn) = split(/,/,$ENV{'form.fullname'});      &mt('Send Message').'</div><div class="LC_grade_message_center_body">';
     my $msgfor = $givenn.' '.$lastname;   my ($lastname,$givenn) = split(/,/,$env{'form.fullname'});
     if (scalar(@col_fullnames) > 0) {   my $msgfor = $givenn.' '.$lastname;
  my $lastone = pop @col_fullnames;   if (scalar(@$col_fullnames) > 0) {
  $msgfor .= ', '.(join ', ',@col_fullnames).' and '.$lastone.'.';      my $lastone = pop(@$col_fullnames);
     }      $msgfor .= ', '.(join ', ',@$col_fullnames).' and '.$lastone.'.';
     $result.='<tr><td bgcolor="#ffffff">'."\n".   }
  '&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.   $msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript
  ',\''.$msgfor.'\')"; TARGET=_self>'.   $result.='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".
  'Compose Message to student'.(scalar(@col_fullnames) >= 1 ? 's' : '').'</a>'.      '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n";
  '<br />&nbsp;(Message will be sent when you click on Save & Next below.)'."\n"    $result.='&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.
  if ($ENV{'form.handgrade'} eq 'yes');      ',\''.$msgfor.'\');" target="_self">'.
     $request->print($result);      &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').
       '/mailbkgrd.gif" width="14" height="10" name="mailicon'.$counter.'" />'."\n".
       '<br />&nbsp;('.
       &mt('Message will be sent when you click on Save &amp; Next below.').")\n";
    $result.='</div></div>';
    $request->print($result);
       }
   
     my %seen = ();      my %seen = ();
     my @partlist;      my @partlist;
     for (sort keys(%$handgrade)) {      my @gradePartRespid;
  my ($partid,$respid) = split(/_/);      my @part_response_id = &flatten_responseType($responseType);
       $request->print('<div class="LC_grade_assign">'.
       
       '<div class="LC_grade_assign_header">'.
       &mt('Assign Grades').'</div>'.
       '<div class="LC_grade_assign_body">');
       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$/);   next if ($$handgrade{$part_resp} ne 'yes' 
    && $env{'form.lastSub'} eq 'hdgrade');
  push @partlist,$partid;   push @partlist,$partid;
  my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);   push @gradePartRespid,$partid.'.'.$respid;
  my $wgtmsg = ($wgt > 0 ? '(problem weight)' :    $request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
       '<font color="red">problem weight assigned by computer</font>');      }
  $wgt       = ($wgt > 0 ? $wgt : '1');      $request->print('</div></div>');
  my $score  = ($record{'resource.'.$partid.'.awarded'} eq '' ?  
       '' : $record{'resource.'.$partid.'.awarded'}*$wgt);  
  $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />';  
  $result.='<table border="0"><tr><td><b>Part </b>'.$partid.' <b>Points: </b></td><td>';  
   
  my $ctr = 0;  
  $result.='<table border="0"><tr>';  # display radio buttons in a nice table 10 across  
  while ($ctr<=$wgt) {  
     $result.= '<td><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.  
  'onclick="javascript:writeBox(this.form.GD_BOX'.$counter.'_'.$partid.  
  ',this.form.GD_SEL'.$counter.'_'.$partid.','.$ctr.  
  ',this.form.stores'.$counter.'_'.$partid.')" '.  
  ($score eq $ctr ? 'checked':'').' /> '.$ctr."</td>\n";  
     $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');  
     $ctr++;  
  }  
  $result.='</tr></table>';  
   
  $result.='</td><td>&nbsp;<b>or</b>&nbsp;</td>';      $request->print('<div class="LC_grade_info_links">');
  $result.='<td><input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.      if ($perm{'vgr'}) {
     ($score ne ''? ' value = "'.$score.'"':'').' size="4" '.   $request->print(
     'onChange="javascript:updateRadio(this.form.RADVAL'.$counter.'_'.$partid.      &Apache::loncommon::track_student_link(&mt('View recent activity'),
     ',this.form.GD_BOX'.$counter.'_'.$partid.     $uname,$udom,'check'));
     ',this.form.GD_SEL'.$counter.'_'.$partid.      }
     ',this.form.stores'.$counter.'_'.$partid.      if ($perm{'opa'}) {
     ','.$wgt.')" /></td>'."\n";   $request->print(
  $result.='<td>/'.$wgt.' '.$wgtmsg.' </td><td>';      &Apache::loncommon::pprmlink(&mt('Set/Change parameters'),
    $uname,$udom,$symb,'check'));
  $result.='<select name="GD_SEL'.$counter.'_'.$partid.'" '.  
     'onChange="javascript:clearRadBox(this.form.RADVAL'.$counter.'_'.$partid.  
     ',this.form.GD_BOX'.$counter.'_'.$partid.  
     ',this.form.GD_SEL'.$counter.'_'.$partid.  
     ',this.form.stores'.$counter.'_'.$partid.')" >'."\n";  
  if ($record{'resource.'.$partid.'.solved'} eq 'excused') {  
     $result.='<option> </option>'.  
  '<option selected="on">excused</option></select>';  
  } else {  
     $result.='<option selected="on"> </option>'.  
  '<option>excused</option></select>';  
  }  
  $result.="&nbsp&nbsp\n";  
  $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="0" />';  
  $result.='</td></tr></table>'."\n";  
  $request->print($result);  
     }      }
       $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'.
    '" value="'.(join ":",@gradePartRespid).'" />'."\n" if ($counter == 0);
     my $ctr = 0;      my $ctr = 0;
     while ($ctr < scalar(@partlist)) {      while ($ctr < scalar(@partlist)) {
  $result.='<input type="hidden" name="partid'.$counter.'_'.$ctr.'" value="'.   $result.='<input type="hidden" name="partid'.$counter.'_'.$ctr.'" value="'.
     $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_body
       $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>'.   my $endform='<table border="0"><tr><td>'."\n";
     '<input type="hidden" name="gradeOpt" value="" />'."\n";   $endform.='<input type="button" value="'.&mt('Save &amp; Next').'" '.
  if ($ENV{'form.handgrade'} eq 'yes') {      'onClick="javascript:checksubmit(this.form,\'Save & Next\','.
     $endform.='<input type="button" value="Save & Next" '.      $total.','.scalar(@partlist).');" target="_self" /> &nbsp;'."\n";
  'onClick="javascript:checksubmit(\'Save & Next\','.   my $ntstu ='<select name="NTSTU">'.
  $total.','.scalar(@partlist).');" TARGET=_self> &nbsp;'."\n";      '<option>1</option><option>2</option>'.
     my $ntstu ='<select name="NTSTU">'.      '<option>3</option><option>5</option>'.
  '<option>1</option><option>2</option>'.      '<option>7</option><option>10</option></select>'."\n";
  '<option>3</option><option>5</option>'.   my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1');
  '<option>7</option><option>10</option></select>'."\n";   $ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</;
     my $nsel = ($ENV{'form.NTSTU'} ne '' ? $ENV{'form.NTSTU'} : '1');   $endform.=&mt('[_1]student(s)',$ntstu);
     $ntstu =~ s/<option>$nsel</<option selected="on">$nsel</;   $endform.='&nbsp;&nbsp;<input type="button" value="'.&mt('Previous').'" '.
     $endform.=$ntstu.'student(s) &nbsp;&nbsp;';      'onClick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> &nbsp;'."\n".
  } else {      '<input type="button" value="'.&mt('Next').'" '.
     $endform.='<input type="hidden" name="NTSTU" value="1" />'."\n";      'onClick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> &nbsp;';
  }   $endform.=&mt('(Next and Previous (student) do not save the scores.)')."\n" ;
  $endform.='<input type="button" value="Next" '.          $endform.="<input type='hidden' value='".&get_increment().
     'onClick="javascript:checksubmit(\'Next\');" TARGET=_self> &nbsp;'."\n".              "' name='increment' />";
     '<input type="button" value="Previous" '.   $endform.='</td></tr></table></form>';
     'onClick="javascript:checksubmit(\'Previous\');" TARGET=_self> &nbsp;';   $endform.=&show_grading_menu_form($symb);
  $endform.='(Next and Previous do not save the scores.)'."\n"   
     if ($ENV{'form.handgrade'} eq 'yes');  
  $endform.='</td><tr></table></form>';  
  $endform.=&show_grading_menu_form($symb,$url);  
  $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);
     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'}))) {
 # if ($returnhash{$version.':'.$_} =~ /(SUBMITTED|DRAFT)$/) {   $lasthash{$key}=$$returnhash{$version.':'.$key};
    $timestamp = scalar(localtime($returnhash{$version.':timestamp'}));   $timestamp = 
 #       }       scalar(localtime($$returnhash{$version.':timestamp'}));
     }      }
  }   }
  foreach ((keys %lasthash)) {   foreach my $key (keys(%lasthash)) {
     if ($_ =~ /\.submission$/) {      next if ($key !~ /\.submission$/);
  my ($partid,$foo) = split(/submission$/,$_);  
  my $draft  = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ?      my ($partid,$foo) = split(/submission$/,$key);
     '<font color="red">Draft Copy</font> ' : '';      my $draft  = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ?
  push @string, (join(':',$_,$draft.$lasthash{$_}));   '<span class="LC_warning">Draft Copy</span> ' : '';
     }      push(@string, join(':', $key, $draft.$lasthash{$key}));
  }   }
     }      }
     @string = $string[0] eq '' ? 'Nothing submitted - no attempts.' : @string;      if (!@string) {
     return \@string,\$timestamp;   $string[0] =
       '<span class="LC_warning">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;
     }      }
     # This is not really the right place to do this, but I cannot find a  
     # better one at this time.  So here we go - the m in the s:::mg causes  
     # ^ to match the beginning of a new line.  So we replace(???) the beginning  
     # of the line with <br /> to make things formatted a little better.  
     $string =~ s:^:<br />:mg;  
     return $string;      return $string;
 }  }
   
 #--- Called from submission routine  #--- Called from submission routine
 sub processHandGrade {  sub processHandGrade {
     my ($request) = shift;      my ($request) = shift;
     my $url    = $ENV{'form.url'};      my $symb   = &get_symb($request);
     my $symb   = $ENV{'form.symb'};      my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
     my $button = $ENV{'form.gradeOpt'};      my $button = $env{'form.gradeOpt'};
     my $ngrade = $ENV{'form.NCT'};      my $ngrade = $env{'form.NCT'};
     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) = &saveHandGrade($request,$url,$symb,$uname,$udom,$ctr);      my ($errorflag,$pts,$wgt) = &saveHandGrade($request,$symb,$uname,$udom,$ctr);
       if ($errorflag eq 'no_score') {
     my $includemsg = $ENV{'form.includemsg'.$ctr};   $ctr++;
    next;
       }
       if ($errorflag eq 'not_allowed') {
    $request->print("<span class=\"LC_warning\">Not allowed to modify grades for $uname:$udom</span>");
    $ctr++;
    next;
       }
       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 =~ s/\s+/ /g;   $message =&Apache::lonfeedback::clear_out_html($message);
  $msgstatus = &Apache::lonmsg::user_normal_msg ($uname,$udom,   if ($env{'form.withgrades'.$ctr}) {
        $ENV{'form.msgsub'},$message);      $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt;
     }      $messagetail = " for <a href=\"".
     if ($ENV{'form.collaborator'.$ctr}) {                     $feedurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
  my (@collaborators) = split(/:/,$ENV{'form.collaborator'.$ctr});   }
  foreach (@collaborators) {   $msgstatus = 
     &saveHandGrade($request,$url,$symb,$_,$udom,$ctr,                      &Apache::lonmsg::user_normal_msg($uname,$udom,$subject,
    $ENV{'form.unamedom'.$ctr});       $message.$messagetail,
     if ($message ne '') {                                                       undef,$feedurl,undef,
  $msgstatus = &Apache::lonmsg::user_normal_msg ($_,$udom,                                                       undef,undef,$showsymb,
        $ENV{'form.msgsub'},                                                       $restitle);
        $message);   $request->print('<br />'.&mt('Sending message to [_1]:[_2]',$uname,$udom).': '.
    $msgstatus);
       }
       if ($env{'form.collaborator'.$ctr}) {
    my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr");
    foreach my $collabstr (@collabstrs) {
       my ($part,@collaborators) = split(/:/,$collabstr);
       foreach my $collaborator (@collaborators) {
    my ($errorflag,$pts,$wgt) = 
       &saveHandGrade($request,$symb,$collaborator,$udom,$ctr,
      $env{'form.unamedom'.$ctr},$part);
    if ($errorflag eq 'not_allowed') {
       $request->print("<span class=\"LC_error\">".&mt('Not allowed to modify grades for [_1]',"$collaborator:$udom")."</span>");
       next;
    } elsif ($message ne '') {
       my ($baseurl,$showsymb) = 
    &get_feedurl_and_symb($symb,$collaborator,
         $udom);
       if ($env{'form.withgrades'.$ctr}) {
    $messagetail = " for <a href=\"".
                                       $baseurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
       }
       $msgstatus = 
    &Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
    }
     }      }
  }   }
     }      }
Line 1252  sub processHandGrade { Line 2471  sub processHandGrade {
  }   }
     }      }
   
     # Keywords sorted in alphabatical order      if ($env{'form.handgrade'} eq 'yes') {
     my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'};   # Keywords sorted in alphabatical order
     my %keyhash = ();   my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
     $ENV{'form.keywords'}           =~ s/,\s{0,}|\s+/ /g;   my %keyhash = ();
     $ENV{'form.keywords'}           =~ s/^\s+|\s+$//;   $env{'form.keywords'}           =~ s/,\s{0,}|\s+/ /g;
     my (@keywords) = sort(split(/\s+/,$ENV{'form.keywords'}));   $env{'form.keywords'}           =~ s/^\s+|\s+$//;
     $ENV{'form.keywords'} = join(' ',@keywords);   my (@keywords) = sort(split(/\s+/,$env{'form.keywords'}));
     $keyhash{$symb.'_keywords'}     = $ENV{'form.keywords'};   $env{'form.keywords'} = join(' ',@keywords);
     $keyhash{$symb.'_subject'}      = $ENV{'form.msgsub'};   $keyhash{$symb.'_keywords'}     = $env{'form.keywords'};
     $keyhash{$loginuser.'_kwclr'}   = $ENV{'form.kwclr'};   $keyhash{$symb.'_subject'}      = $env{'form.msgsub'};
     $keyhash{$loginuser.'_kwsize'}  = $ENV{'form.kwsize'};   $keyhash{$loginuser.'_kwclr'}   = $env{'form.kwclr'};
     $keyhash{$loginuser.'_kwstyle'} = $ENV{'form.kwstyle'};   $keyhash{$loginuser.'_kwsize'}  = $env{'form.kwsize'};
    $keyhash{$loginuser.'_kwstyle'} = $env{'form.kwstyle'};
     # message center - Order of message gets changed. Blank line is eliminated.  
     # New messages are saved in ENV for the next student.   # message center - Order of message gets changed. Blank line is eliminated.
     # All messages are saved in nohist_handgrade.db   # New messages are saved in env for the next student.
     my ($ctr,$idx) = (1,1);   # All messages are saved in nohist_handgrade.db
     while ($ctr <= $ENV{'form.savemsgN'}) {   my ($ctr,$idx) = (1,1);
  if ($ENV{'form.savemsg'.$ctr} ne '') {   while ($ctr <= $env{'form.savemsgN'}) {
     $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.savemsg'.$ctr};      if ($env{'form.savemsg'.$ctr} ne '') {
     $idx++;   $keyhash{$symb.'_savemsg'.$idx} = $env{'form.savemsg'.$ctr};
    $idx++;
       }
       $ctr++;
  }   }
  $ctr++;   $ctr = 0;
     }   while ($ctr < $ngrade) {
     $ctr = 0;      if ($env{'form.newmsg'.$ctr} ne '') {
     while ($ctr < $ngrade) {   $keyhash{$symb.'_savemsg'.$idx} = $env{'form.newmsg'.$ctr};
  if ($ENV{'form.newmsg'.$ctr} ne '') {   $env{'form.savemsg'.$idx} = $env{'form.newmsg'.$ctr};
     $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.newmsg'.$ctr};   $idx++;
     $ENV{'form.savemsg'.$idx} = $ENV{'form.newmsg'.$ctr};      }
     $idx++;      $ctr++;
  }   }
  $ctr++;   $env{'form.savemsgN'} = --$idx;
    $keyhash{$symb.'_savemsgN'} = $env{'form.savemsgN'};
    my $putresult = &Apache::lonnet::put
       ('nohist_handgrade',\%keyhash,$cdom,$cnum);
     }      }
     $ENV{'form.savemsgN'} = --$idx;  
     $keyhash{$symb.'_savemsgN'} = $ENV{'form.savemsgN'};  
     my $putresult = &Apache::lonnet::put  
  ('nohist_handgrade',\%keyhash,  
  $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
     if ($ENV{'form.refresh'} eq 'on') {      my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
  my $ctr = 0;      if ($env{'form.refresh'} eq 'on') {
  $ENV{'form.NTSTU'}=$ngrade;   my ($ctr,$total) = (0,0);
  while ($ctr < $ngrade) {   while ($ctr < $ngrade) {
     ($ENV{'form.student'},$ENV{'form.userdom'}) = split(/:/,$ENV{'form.unamedom'.$ctr});      $total++ if  $env{'form.unamedom'.$ctr} ne '';
     &submission($request,$ctr,$ngrade-1);      $ctr++;
    }
    $env{'form.NTSTU'}=$ngrade;
    $ctr = 0;
    while ($ctr < $total) {
       my $processUser = $env{'form.unamedom'.$ctr};
       ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
       $env{'form.fullname'} = $$fullname{$processUser};
       &submission($request,$ctr,$total-1);
     $ctr++;      $ctr++;
  }   }
  return '';   return '';
     }      }
   
   # Go directly to grade student - from submission or link from chart page
       if ($button eq 'Grade Student') {
    (undef,undef,$env{'form.handgrade'},undef,undef) = &showResourceInfo($symb);
    my $processUser = $env{'form.unamedom'.$env{'form.studentNo'}};
    ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
    $env{'form.fullname'} = $$fullname{$processUser};
    &submission($request,0,0);
    return '';
       }
   
     # Get the next/previous one or group of students      # Get the next/previous one or group of students
     my $firststu = $ENV{'form.unamedom0'};      my $firststu = $env{'form.unamedom0'};
     my $laststu = $ENV{'form.unamedom'.($ngrade-1)};      my $laststu = $env{'form.unamedom'.($ngrade-1)};
     $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 (undef,undef,$fullname) = &getclasslist($ENV{'form.section'},'0');  
     my (@parsedlist,@nextlist);      my (@parsedlist,@nextlist);
     my ($nextflg) = 0;      my ($nextflg) = 0;
     foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {      foreach (sort 
        {
    if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
        return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
    }
    return $a cmp $b;
        } (keys(%$fullname))) {
  if ($nextflg == 1 && $button =~ /Next$/) {   if ($nextflg == 1 && $button =~ /Next$/) {
     push @parsedlist,$_;      push @parsedlist,$_;
  }   }
Line 1328  sub processHandGrade { Line 2569  sub processHandGrade {
  }   }
     }      }
     $ctr = 0;      $ctr = 0;
     my ($partlist,$handgrade) = &response_type($ENV{'form.url'});  
     @parsedlist = reverse @parsedlist if ($button eq 'Previous');      @parsedlist = reverse @parsedlist if ($button eq 'Previous');
       my ($partlist) = &response_type($symb);
     foreach my $student (@parsedlist) {      foreach my $student (@parsedlist) {
    my $submitonly=$env{'form.submitonly'};
  my ($uname,$udom) = split(/:/,$student);   my ($uname,$udom) = split(/:/,$student);
  if ($ENV{'form.submitonly'} eq 'yes') {  
     my (%status) = &student_gradeStatus($ENV{'form.url'},$symb,$udom,$uname,$partlist) ;   if ($submitonly eq 'queued') {
     my $statusflg = '';      my %queue_status = 
    &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
    $udom,$uname);
       next if (!defined($queue_status{'gradingqueue'}));
    }
   
    if ($submitonly =~ /^(yes|graded|incorrect)$/) {
   #    my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
       my %status=&student_gradeStatus($symb,$udom,$uname,$partlist);
       my $submitted = 0;
       my $ungraded = 0;
       my $incorrect = 0;
     foreach (keys(%status)) {      foreach (keys(%status)) {
  $statusflg = 1 if ($status{$_} ne 'nothing');   $submitted = 1 if ($status{$_} ne 'nothing');
  my ($foo,$partid,$foo1) = split(/\./);   $ungraded = 1 if ($status{$_} =~ /^ungraded/);
  $statusflg = '' if ($status{'resource.'.$partid.'.submitted_by'} ne '');   $incorrect = 1 if ($status{$_} =~ /^incorrect/);
    my ($foo,$partid,$foo1) = split(/\./,$_);
    if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
       $submitted = 0;
    }
     }      }
     next if ($statusflg eq '');      next if (!$submitted && ($submitonly eq 'yes' ||
        $submitonly eq 'incorrect' ||
        $submitonly eq 'graded'));
       next if (!$ungraded && ($submitonly eq 'graded'));
       next if (!$incorrect && $submitonly eq 'incorrect');
  }   }
  push @nextlist,$student if ($ctr < $ntstu);   push @nextlist,$student if ($ctr < $ntstu);
    last if ($ctr == $ntstu);
  $ctr++;   $ctr++;
     }      }
   
Line 1351  sub processHandGrade { Line 2613  sub processHandGrade {
   
     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{$_};
 # $ENV{'form.'.$_.':submitted_by'} = $submitter;  
 # print "submitter=$ENV{'form.'.$_.':submitted_by'}= $submitter:<br>";  
  &submission($request,$ctr,$total);   &submission($request,$ctr,$total);
  $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);   $the_end.=&show_grading_menu_form($symb);
  $request->print($the_end);   $request->print($the_end);
     }      }
     return '';      return '';
Line 1371  sub processHandGrade { Line 2631  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) = @_;      my ($request,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_;
     my %record=&Apache::lonnet::restore($symb,$ENV{'request.course.id'},$domain,$stuname);      my @version_parts;
     my %newrecord;      my $usec = &Apache::lonnet::getsection($domain,$stuname,
     foreach (split(/:/,$ENV{'form.partlist'.$newflg})) {     $env{'request.course.id'});
  if ($ENV{'form.GD_SEL'.$newflg.'_'.$_} eq 'excused') {      if (!&canmodify($usec)) { return('not_allowed'); }
     if ($record{'resource.'.$_.'.solved'} ne 'excused') {      my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$stuname);
  $newrecord{'resource.'.$_.'.solved'} = 'excused';      my @parts_graded;
  if (exists($record{'resource.'.$_.'.awarded'})) {      my %newrecord  = ();
     $newrecord{'resource.'.$_.'.awarded'} = '';      my ($pts,$wgt) = ('','');
       my %aggregate = ();
       my $aggregateflag = 0;
       my @parts = split(/:/,$env{'form.partlist'.$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 ($record{'resource.'.$new_part.'.solved'} ne 'excused') {
    $newrecord{'resource.'.$new_part.'.solved'} = 'excused';
    if (exists($record{'resource.'.$new_part.'.awarded'})) {
       $newrecord{'resource.'.$new_part.'.awarded'} = '';
  }   }
           $newrecord{'resource.'.$new_part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
     }      }
  } else {   } elsif ($dropMenu eq 'reset status'
     my $pts = ($ENV{'form.GD_BOX'.$newflg.'_'.$_} ne '' ?    && exists($record{'resource.'.$new_part.'.solved'})) { #don't bother if no old records -> no attempts
        $ENV{'form.GD_BOX'.$newflg.'_'.$_} :       foreach my $key (keys (%record)) {
        $ENV{'form.RADVAL'.$newflg.'_'.$_});   if ($key=~/^resource\.\Q$new_part\E\./) { $newrecord{$key} = ''; }
     my $wgt = $ENV{'form.WGT'.$newflg.'_'.$_} eq '' ? 1 :       }
  $ENV{'form.WGT'.$newflg.'_'.$_};      $newrecord{'resource.'.$new_part.'.regrader'}=
    "$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 '') {
       $pts = ($env{'form.GD_BOX'.$newflg.'_'.$new_part} ne '' ? 
       $env{'form.GD_BOX'.$newflg.'_'.$new_part} : 
       $env{'form.RADVAL'.$newflg.'_'.$new_part});
       if ($pts eq '' && $env{'form.GD_SEL'.$newflg.'_'.$new_part} eq '') {
    next;
       }
       $wgt = $env{'form.WGT'.$newflg.'_'.$new_part} eq '' ? 1 : 
    $env{'form.WGT'.$newflg.'_'.$new_part};
     my $partial= $pts/$wgt;      my $partial= $pts/$wgt;
     $newrecord{'resource.'.$_.'.awarded'}  = $partial       if ($partial eq $record{'resource.'.$new_part.'.awarded'}) {
  if ($record{'resource.'.$_.'.awarded'} ne $partial);   #do not update score for part if not changed.
     my $reckey = 'resource.'.$_.'.solved';                  &handback_files($request,$symb,$stuname,$domain,$newflg,$new_part,\%newrecord);
    next;
       } else {
           push @parts_graded, $new_part;
       }
       if ($record{'resource.'.$new_part.'.awarded'} ne $partial) {
    $newrecord{'resource.'.$new_part.'.awarded'}  = $partial;
       }
       my $reckey = 'resource.'.$new_part.'.solved';
     if ($partial == 0) {      if ($partial == 0) {
  $newrecord{$reckey} = 'incorrect_by_override'    if ($record{$reckey} ne 'incorrect_by_override') {
     if ($record{$reckey} ne 'incorrect_by_override');      $newrecord{$reckey} = 'incorrect_by_override';
    }
     } else {      } else {
  $newrecord{$reckey} = 'correct_by_override'    if ($record{$reckey} ne 'correct_by_override') {
     if ($record{$reckey} ne 'correct_by_override');      $newrecord{$reckey} = 'correct_by_override';
    }
       }    
       if ($submitter && 
    ($record{'resource.'.$new_part.'.submitted_by'} ne $submitter)) {
    $newrecord{'resource.'.$new_part.'.submitted_by'} = $submitter;
     }      }
     $newrecord{'resource.'.$_.'.submitted_by'} = $submitter       $newrecord{'resource.'.$new_part.'.regrader'}=
  if ($submitter && ($record{'resource.'.$_.'.submitted_by'} ne $submitter));   "$env{'user.name'}:$env{'user.domain'}";
  }   }
    # unless problem has been graded, set flag to version the submitted files
    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,
    $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);
     }      }
   }
   
     if (scalar(keys(%newrecord)) > 0) {  sub handback_files {
  $newrecord{'resource.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}";      my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_;
  &Apache::lonnet::cstore(\%newrecord,$symb,      my $portfolio_root = &propath($domain,$stuname).'/userfiles/portfolio';
  $ENV{'request.course.id'},$domain,$stuname);      my ($partlist,$handgrade,$responseType) = &response_type($symb);
   
       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 @dir_list = &Apache::lonnet::dirlist($portfolio_path,$domain,$stuname,$portfolio_root);
       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('<span class="LC_error">An error occurred ('.$result.
                           ') while trying to upload '.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'</span><br />');
                       } 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);
           }
     }      }
     return '';      if ($$record{"resource.$partid.$respid.uploadedurl"}) {
           push(@files,$$record{"resource.$partid.$respid.uploadedurl"});
       }
       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 = &propath($domain,$stu_name).
    '/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 @dir_list = &Apache::lonnet::dirlist($directory,$domain,$stu_name,$portfolio_root);
                   my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
                   my $new_answer = &version_selected_portfile($domain, $stu_name, $directory, $answer_file, $version);
                   if ($new_answer ne 'problem getting file') {
                       push(@versioned_portfiles, $directory.$new_answer);
                       &Apache::lonnet::mark_as_readonly($domain,$stu_name,
                           [$directory.$new_answer],
                           [$symb,$env{'request.course.id'},'graded']);
                   }
               }
               $$record{$key} = join(',',@versioned_portfiles);
               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 1423  sub viewgrades_js { Line 3016  sub viewgrades_js {
     $request->print(<<VIEWJAVASCRIPT);      $request->print(<<VIEWJAVASCRIPT);
 <script type="text/javascript" language="javascript">  <script type="text/javascript" language="javascript">
    function writePoint(partid,weight,point) {     function writePoint(partid,weight,point) {
  var radioButton = eval("document.classgrade.RADVAL_"+partid);   var radioButton = document.classgrade["RADVAL_"+partid];
  var textbox = eval("document.classgrade.TEXTVAL_"+partid);   var textbox = document.classgrade["TEXTVAL_"+partid];
  if (point == "textval") {   if (point == "textval") {
     var point = eval("document.classgrade.TEXTVAL_"+partid+".value");      point = document.classgrade["TEXTVAL_"+partid].value;
     if (isNaN(point) || point < 0) {      if (isNaN(point) || parseFloat(point) < 0) {
  alert("A number equal or greater than 0 is expected. Entered value = "+point);   alert("A number equal or greater than 0 is expected. Entered value = "+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 1441  sub viewgrades_js { Line 3034  sub viewgrades_js {
  }   }
  return;   return;
     }      }
     if (point > weight) {      if (parseFloat(point) > parseFloat(weight)) {
  var resp = confirm("You entered a value ("+point+   var resp = confirm("You entered a value ("+parseFloat(point)+
    ") greater than the weight for the part. Accept?");     ") greater than the weight for the part. Accept?");
  if (resp == false) {   if (resp == false) {
     textbox.value = "";      textbox.value = "";
Line 1451  sub viewgrades_js { Line 3044  sub viewgrades_js {
     }      }
     for (var i=0; i<radioButton.length; i++) {      for (var i=0; i<radioButton.length; i++) {
  radioButton[i].checked=false;   radioButton[i].checked=false;
  if (point == i) {   if (parseFloat(point) == i) {
     radioButton[i].checked=true;      radioButton[i].checked=true;
  }   }
     }      }
   
  } else {   } else {
     textbox.value = point;      textbox.value = parseFloat(point);
  }   }
  for (i=0;i<document.classgrade.total.value;i++) {   for (i=0;i<document.classgrade.total.value;i++) {
     var user = eval("document.classgrade.ctr"+i+".value");      var user = document.classgrade["ctr"+i].value;
     var scorename = eval("document.classgrade.GD_"+user+      user = user.replace(new RegExp(':', 'g'),"_");
  "_"+partid+"_awarded");      var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
     var saveval   = eval("document.classgrade.GD_"+user+      var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
  "_"+partid+"_solved_s.value");      var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
     var selname   = eval("document.classgrade.GD_"+user+"_"+partid+"_solved");  
     if (saveval != "correct") {      if (saveval != "correct") {
  scorename.value = point;   scorename.value = point;
  if (selname[0].selected != true) {   if (selname[0].selected != true) {
Line 1473  sub viewgrades_js { Line 3065  sub viewgrades_js {
  }   }
     }      }
  }   }
  var selval   = eval("document.classgrade.SELVAL_"+partid);   document.classgrade["SELVAL_"+partid][0].selected = true;
  selval[0].selected = true;  
     }      }
   
     function writeRadText(partid,weight) {      function writeRadText(partid,weight) {
  var selval   = eval("document.classgrade.SELVAL_"+partid);   var selval   = document.classgrade["SELVAL_"+partid];
  var radioButton = eval("document.classgrade.RADVAL_"+partid);   var radioButton = document.classgrade["RADVAL_"+partid];
  var textbox = eval("document.classgrade.TEXTVAL_"+partid);          var override = document.classgrade["FORCE_"+partid].checked;
  if (selval[1].selected) {   var textbox = document.classgrade["TEXTVAL_"+partid];
    if (selval[1].selected || selval[2].selected) {
     for (var i=0; i<radioButton.length; i++) {      for (var i=0; i<radioButton.length; i++) {
  radioButton[i].checked=false;   radioButton[i].checked=false;
   
Line 1489  sub viewgrades_js { Line 3081  sub viewgrades_js {
     textbox.value = "";      textbox.value = "";
   
     for (i=0;i<document.classgrade.total.value;i++) {      for (i=0;i<document.classgrade.total.value;i++) {
  var user = eval("document.classgrade.ctr"+i+".value");   var user = document.classgrade["ctr"+i].value;
  var scorename = eval("document.classgrade.GD_"+user+   user = user.replace(new RegExp(':', 'g'),"_");
      "_"+partid+"_awarded");   var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
  var saveval   = eval("document.classgrade.GD_"+user+   var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
      "_"+partid+"_solved_s.value");   var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
  var selname   = eval("document.classgrade.GD_"+user+   if ((saveval != "correct") || override) {
      "_"+partid+"_solved");  
  if (saveval != "correct") {  
     scorename.value = "";      scorename.value = "";
     selname[1].selected = true;      if (selval[1].selected) {
    selname[1].selected = true;
       } else {
    selname[2].selected = true;
    if (Number(document.classgrade["GD_"+user+"_"+partid+"_tries"].value)) 
    {document.classgrade["GD_"+user+"_"+partid+"_tries"].value = '0';}
       }
  }   }
     }      }
  } else {   } else {
     for (i=0;i<document.classgrade.total.value;i++) {      for (i=0;i<document.classgrade.total.value;i++) {
  var user = eval("document.classgrade.ctr"+i+".value");   var user = document.classgrade["ctr"+i].value;
  var scorename = eval("document.classgrade.GD_"+user+   user = user.replace(new RegExp(':', 'g'),"_");
      "_"+partid+"_awarded");   var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
  var saveval   = eval("document.classgrade.GD_"+user+   var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
      "_"+partid+"_solved_s.value");   var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
  var selname   = eval("document.classgrade.GD_"+user+   if ((saveval != "correct") || override) {
      "_"+partid+"_solved");      scorename.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
  if (saveval != "correct") {  
     scorename.value = eval("document.classgrade.GD_"+user+  
      "_"+partid+"_awarded_s.value");;  
     selname[0].selected = true;      selname[0].selected = true;
  }   }
     }      }
Line 1520  sub viewgrades_js { Line 3113  sub viewgrades_js {
     }      }
   
     function changeSelect(partid,user) {      function changeSelect(partid,user) {
  var selval = eval("document.classgrade.GD_"+user+'_'+partid+"_solved");   var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
  var textbox = eval("document.classgrade.GD_"+user+'_'+partid+"_awarded");   var textbox = document.classgrade["GD_"+user+'_'+partid+"_awarded"];
  var point  = textbox.value;   var point  = textbox.value;
  var weight = eval("document.classgrade.weight_"+partid+".value");   var weight = document.classgrade["weight_"+partid].value;
   
  if (isNaN(point) || point < 0) {   if (isNaN(point) || parseFloat(point) < 0) {
     alert("A number equal or greater than 0 is expected. Entered value = "+point);      alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point));
     textbox.value = "";      textbox.value = "";
     return;      return;
  }   }
  if (point > weight) {   if (parseFloat(point) > parseFloat(weight)) {
     var resp = confirm("You entered a value ("+point+      var resp = confirm("You entered a value ("+parseFloat(point)+
        ") greater than the weight of the part. Accept?");         ") greater than the weight of the part. Accept?");
     if (resp == false) {      if (resp == false) {
  textbox.value = "";   textbox.value = "";
Line 1542  sub viewgrades_js { Line 3135  sub viewgrades_js {
     }      }
   
     function changeOneScore(partid,user) {      function changeOneScore(partid,user) {
  var selval = eval("document.classgrade.GD_"+user+'_'+partid+"_solved");   var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
  if (selval[1].selected) {   if (selval[1].selected || selval[2].selected) {
     var boxval = eval("document.classgrade.GD_"+user+'_'+partid+"_awarded");      document.classgrade["GD_"+user+'_'+partid+"_awarded"].value = "";
     boxval.value = "";      if (selval[2].selected) {
  }   document.classgrade["GD_"+user+'_'+partid+"_tries"].value = "0";
       }
           }
     }      }
   
     function resetEntry(numpart) {      function resetEntry(numpart) {
  for (ctpart=0;ctpart<numpart;ctpart++) {   for (ctpart=0;ctpart<numpart;ctpart++) {
     var partid = eval("document.classgrade.partid_"+ctpart+".value");      var partid = document.classgrade["partid_"+ctpart].value;
     var radioButton = eval("document.classgrade.RADVAL_"+partid);      var radioButton = document.classgrade["RADVAL_"+partid];
     var textbox = eval("document.classgrade.TEXTVAL_"+partid);      var textbox = document.classgrade["TEXTVAL_"+partid];
     var selval  = eval("document.classgrade.SELVAL_"+partid);      var selval  = document.classgrade["SELVAL_"+partid];
     for (var i=0; i<radioButton.length; i++) {      for (var i=0; i<radioButton.length; i++) {
  radioButton[i].checked=false;   radioButton[i].checked=false;
   
Line 1563  sub viewgrades_js { Line 3158  sub viewgrades_js {
     selval[0].selected = true;      selval[0].selected = true;
   
     for (i=0;i<document.classgrade.total.value;i++) {      for (i=0;i<document.classgrade.total.value;i++) {
  var user = eval("document.classgrade.ctr"+i+".value");   var user = document.classgrade["ctr"+i].value;
  var resetscore = eval("document.classgrade.GD_"+user+   user = user.replace(new RegExp(':', 'g'),"_");
       "_"+partid+"_awarded");   var resetscore = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
  resetscore.value = eval("document.classgrade.GD_"+user+   resetscore.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
  "_"+partid+"_awarded_s.value");   var resettries = document.classgrade["GD_"+user+"_"+partid+"_tries"];
    resettries.value = document.classgrade["GD_"+user+"_"+partid+"_tries_s"].value;
  var saveselval   = eval("document.classgrade.GD_"+user+   var saveselval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
      "_"+partid+"_solved_s.value");   var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
   
  var selname   = eval("document.classgrade.GD_"+user+"_"+partid+"_solved");  
  if (saveselval == "excused") {   if (saveselval == "excused") {
     if (selname[1].selected == false) { selname[1].selected = true;}      if (selname[1].selected == false) { selname[1].selected = true;}
  } else {   } else {
Line 1591  sub viewgrades { Line 3184  sub viewgrades {
     my ($request) = shift;      my ($request) = shift;
     &viewgrades_js($request);      &viewgrades_js($request);
   
     my ($symb,$url) = ($ENV{'form.symb'},$ENV{'form.url'});       my ($symb) = &get_symb($request);
     my $result='<h3><font color="#339933">Manual Grading</font></h3>';      #need to make sure we have the correct data for later EXT calls, 
       #thus invalidate the cache
       &Apache::lonnet::devalidatecourseresdata(
                    $env{'course.'.$env{'request.course.id'}.'.num'},
                    $env{'course.'.$env{'request.course.id'}.'.domain'});
       &Apache::lonnet::clear_EXT_cache_status();
   
     $result.='<font size=+1><b>Resource: </b>'.$ENV{'form.url'}.'</font>'."\n";      my $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>';
       $result.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4>'."\n";
   
     #view individual student submission form - called using Javascript viewOneStudent      #view individual student submission form - called using Javascript viewOneStudent
     $result.=&jscriptNform($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().
     $result.='<h3>Assign Common Grade To ';   '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
     if ($ENV{'form.section'} eq 'all') {   '<input type="hidden" name="Status" value="'.$env{'stu_status'}.'" />'."\n".
  $result.='Class </h3>';   '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
     } elsif ($ENV{'form.section'} eq 'no') {  
  $result.='Students in no Section </h3>';      my $sectionClass;
     } else {      my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
  $result.='Students in Section '.$ENV{'form.section'}.'</h3>';      if ($env{'form.section'} eq 'all') {
     }   $sectionClass='Class';
     $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".      } elsif ($env{'form.section'} eq 'none') {
  '<table border=0><tr bgcolor="#ffffdd"><td>';   $sectionClass='Students in no Section';
 #    $result.='To assign the same score for all the students use the radio buttons or '.      } else {
 # 'text box below. To assign scores individually fill in the score boxes for '.   $sectionClass='Students in Section(s) [_1]';
 # 'each student in the table below. <font color="red">A part that has already '.      }
 # 'been graded does not get changed using the radio buttons or text box. '.      $result.=
 # 'If needed, it has to be changed individually.</font>';   '<h3>'.
 #    $result.='</td></tr><tr><td>';   &mt("Assign Common Grade To $sectionClass",$section_display).'</h3>';
       $result.= &Apache::loncommon::start_data_table();
     #radio buttons/text box for assigning points for a section or class.      #radio buttons/text box for assigning points for a section or class.
     #handles different parts of a problem      #handles different parts of a problem
     my ($partlist,$handgrade) = &response_type($ENV{'form.url'});      my ($partlist,$handgrade,$responseType) = &response_type($symb);
     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 ($responsetype,$handgrade)=split(/:/,$$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";  
  $result.='<tr><td><b>Part  '.$partid.'&nbsp; &nbsp;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}.' (problem weight)</td>'."\n";
  $result.= '</td><td><select name="SELVAL_'.$partid.'"'.   $line.= '<td><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></select></td></tr>'."\n";      '<option value="excused">'.&mt('excused').'</option>'.
       '<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".
       &mt('<td><b>Part:</b></td><td>[_1]</td><td><b>Points:</b></td><td>[_2]</td><td>or</td><td>[_3]</td>',$display_part,$radio,$line).
       &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> &nbsp; &nbsp;';   'onClick="javascript:resetEntry('.$ctsparts.');" />';
     $result.='<input type="button" value="Submit Changes" '.  
  'onClick="javascript:submit();" TARGET=_self />'."\n";  
   
     #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 ';      $result.= '<h3>'.&mt('Assign Grade to Specific Students in '.$sectionClass,
     if ($ENV{'form.section'} eq 'all') {   $section_display).'</h3>';
  $result.='the Class </h3>';      $result.= &Apache::loncommon::start_data_table().
     } elsif ($ENV{'form.section'} eq 'no') {   &Apache::loncommon::start_data_table_header_row().
  $result.='no Section </h3>';   '<th>'.&mt('No.').'</th>'.
     } else {   '<th>'.&nameUserString('header')."</th>\n";
  $result.='Section '.$ENV{'form.section'}.'</h3>';      my (@parts) = sort(&getpartlist($symb));
     }      my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
     $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".      my @partids = ();
  '<table border=0><tr bgcolor="#deffff">'.  
  '<td><b>Fullname</b></td><td><b>Username</b></td><td><b>Domain</b></td>'."\n";  
     my (@parts) = sort(&getpartlist($url));  
     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
  if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }   if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }
    my ($partid) = &split_part_type($part);
           push(@partids, $partid);
    my $display_part=&get_display_part($partid,$symb);
  if ($display =~ /^Partial Credit Factor/) {   if ($display =~ /^Partial Credit Factor/) {
     my ($partid) = &split_part_type($part);      $result.='<th>'.
     $result.='<td><b>Score Part '.$partid.'<br />(weight = '.   &mt('Score Part: [_1]<br /> (weight = [_2])',
  $weight{$partid}.')</b></td>'."\n";      $display_part,$weight{$partid}).'</th>'."\n";
     next;      next;
       
    } else {
       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.'</b></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'},'0');      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,$udom) = split(/:/);       {
  $result.='<input type="hidden" name="ctr'.$ctr.'" value="'.$uname.'" />'."\n";   if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
  $result.=&viewstudentgrade($url,$symb,$ENV{'request.course.id'},       return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
    $_,$$fullname{$_},\@parts,\%weight);   }
    return $a cmp $b;
        } (keys(%$fullname))) {
  $ctr++;   $ctr++;
    $result.=&viewstudentgrade($symb,$env{'request.course.id'},
      $_,$$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="Submit Changes" '.      $result.='<input type="button" value="'.&mt('Save').'" '.
  'onClick="javascript:submit();" TARGET=_self /></form>'."\n";   'onClick="javascript:submit();" target="_self" /></form>'."\n";
     $result.=&show_grading_menu_form($symb,$url);      if (scalar(%$fullname) eq 0) {
    my $colspan=3+scalar(@parts);
    my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
           my $stu_status = join(' or ',&Apache::loncommon::get_env_multiple('form.Status'));
    $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);
     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) = @_;      my ($symb,$courseid,$student,$fullname,$parts,$weight,$ctr,$last_resets) = @_;
     my ($uname,$udom) = split(/:/,$student);      my ($uname,$udom) = split(/:/,$student);
     my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);      my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
     my $result='<tr bgcolor="#ffffdd"><td>'.      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> '.
  '</td><td>'.$uname.'</td><td align="middle">'.$udom.'</td>'."\n";   '<span class="LC_internal_info">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</span></td>'."\n";
     foreach my $part (@$parts) {      $student=~s/:/_/; # colon doen't work in javascript for names
  my ($part,$type) = &split_part_type($part);      foreach my $apart (@$parts) {
    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_'.$uname.'_'.$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_'.$uname.'_'.$part.'_awarded" '.   'GD_'.$student.'_'.$part.'_awarded" '.
  'onChange="javascript:changeSelect(\''.$part.'\',\''.$uname.   '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_'.$uname.'_'.      $result.='<input type="hidden" name="'.'GD_'.$student.'_'.
  $part.'_solved_s" value="'.$status.'" />'."\n";   $part.'_solved_s" value="'.$status.'" />'."\n";
     $result.='<td align="middle"><select name="'.      $result.='&nbsp;<select name="'.
  'GD_'.$uname.'_'.$part.'_solved" '.   'GD_'.$student.'_'.$part.'_solved" '.
  'onChange="javascript:changeOneScore(\''.$part.'\',\''.$uname.'\')" >'."\n";   'onChange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n";
     my $optsel = '<option selected="on"> </option><option>excused</option>'."\n";      $result.= (($status eq 'excused') ? '<option> </option><option selected="selected" value="excused">'.&mt('excused').'</option>' 
     $optsel = '<option> </option><option selected="on">excused</option>'."\n"   : '<option selected="selected"> </option><option value="excused">'.&mt('excused').'</option>')."\n";
  if ($status eq 'excused');      $result.='<option value="reset status">'.&mt('reset status').'</option>';
     $result.=$optsel;      $result.="</select>&nbsp;</td>\n";
     $result.="</select></td>\n";  
  } else {   } else {
     $result.='<input type="hidden" name="'.      $result.='<input type="hidden" name="'.
  'GD_'.$uname.'_'.$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_'.$uname.'_'.$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;
 }  }
   
Line 1767  sub viewstudentgrade { Line 3418  sub viewstudentgrade {
 sub editgrades {  sub editgrades {
     my ($request) = @_;      my ($request) = @_;
   
     my $symb=$ENV{'form.symb'};      my $symb=&get_symb($request);
     my $url =$ENV{'form.url'};      my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
     my $title='<h3><font color="#339933">Current Grade Status</font></h3>';      my $title='<h2>'.&mt('Current Grade Status').'</h2>';
     $title.='<font size=+1><b>Resource: </b>'.$ENV{'form.url'}.'</font><br />'."\n";      $title.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4>'."\n";
     $title.='<font size=+1><b>Section: </b>'.$ENV{'form.section'}.'</font>'."\n";      $title.='<h4>'.&mt('<b>Section: </b>[_1]',$section_display).'</h4>'."\n";
     $title.= &show_grading_menu_form ($symb,$url);  
     my $result= '<table border="0"><tr><td bgcolor="#777777">'."\n";      my $result= &Apache::loncommon::start_data_table().
     $result.= '<table border="0"><tr bgcolor="#deffff">'.   &Apache::loncommon::start_data_table_header_row().
  '<td rowspan=2><b>Username</b></td><td rowspan=2><b>Fullname</b></td>'."\n";   '<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',
Line 1784  sub editgrades { Line 3435  sub editgrades {
     'ungraded' =>'ungraded_attempted',      'ungraded' =>'ungraded_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));      my (@parts) = sort(&getpartlist($symb));
     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);
Line 1809  sub editgrades { Line 3461  sub editgrades {
     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: (\w)+\]//;
     $header .= '<td align="center">&nbsp;<b>Old</b> '.$display.'&nbsp;</td>'.      $display =~ s/Number of Attempts/Tries/;
  '<td align="center">&nbsp;<b>New</b> '.$display.'&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) {
  $result .= '<td colspan="'.$columns{$partid}.   my $display_part=&get_display_part($partid,$symb);
     '" align="center"><b>Part '.$partid.   $result .= '<th colspan="'.$columns{$partid}.'" align="center">'.
     '</b> (Weight = '.$weight{$partid}.')</td>';      &mt('Part: [_1] (Weight = [_2])',$display_part,$weight{$partid}).
       '</th>';
     }  
     $result .= '</tr><tr bgcolor="#deffff">';      }
     $result .= $header;      $result .= &Apache::loncommon::end_data_table_header_row().
     $result .= '</tr>'."\n";   &Apache::loncommon::start_data_table_header_row().
    $header.
     for ($i=0; $i<$ENV{'form.total'}; $i++) {   &Apache::loncommon::end_data_table_header_row();
  my $user = $ENV{'form.ctr'.$i};      my @noupdate;
       my ($updateCtr,$noupdateCtr) = (1,1);
       for ($i=0; $i<$env{'form.total'}; $i++) {
    my $line;
    my $user = $env{'form.ctr'.$i};
    my ($uname,$udom)=split(/:/,$user);
  my %newrecord;   my %newrecord;
  my $updateflag = 0;   my $updateflag = 0;
  my @userdom = grep /^$user:/,keys %$classlist;   $line .= '<td>'.&nameUserString(undef,$$fullname{$user},$uname,$udom).'</td>';
  my (undef,$udom) = split(/:/,$userdom[0]);   my $usec=$classlist->{"$uname:$udom"}[5];
    if (!&canmodify($usec)) {
  $result .= '<tr bgcolor="#ffffde"><td>'.$user.'&nbsp;</td><td>'.      my $numcols=scalar(@partid)*4+2;
     $$fullname{$userdom[0]}.'&nbsp;</td>';      push(@noupdate,
    $line."<td colspan=\"$numcols\"><span class=\"LC_warning\">".
    &mt('Not allowed to modify student')."</span></td></tr>");
       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';
     }      }
     $score = 'excused' if (($ENV{'form.GD_'.$user.'_'.$_.'_solved'} eq 'excused') &&      my $dropMenu = $env{'form.GD_'.$user.'_'.$_.'_solved'};
    ($score ne 'excused'));      $score = 'excused' if (($dropMenu eq 'excused') && ($score ne 'excused'));
     $result .= '<td align="center">'.$old_aw.'&nbsp;</td>'.  
  '<td align="center">'.$awarded.  
  ($score eq 'excused' ? $score : '').'&nbsp;</td>';  
   
     if (!($old_part eq $partial && $old_score eq $score)) {      $newrecord{'resource.'.$_.'.regrader'}=
    "$env{'user.name'}:$env{'user.domain'}";
       if ($dropMenu eq 'reset status' &&
    $old_score ne '') { # ignore if no previous attempts => nothing to reset
    $newrecord{'resource.'.$_.'.tries'} = '';
    $newrecord{'resource.'.$_.'.solved'} = '';
    $newrecord{'resource.'.$_.'.award'} = '';
    $newrecord{'resource.'.$_.'.awarded'} = '';
    $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)) {
  $updateflag = 1;   $updateflag = 1;
  $newrecord{'resource.'.$_.'.awarded'}  = $partial if $partial ne '';   $newrecord{'resource.'.$_.'.awarded'}  = $partial if $partial ne '';
  $newrecord{'resource.'.$_.'.solved'}   = $score;   $newrecord{'resource.'.$_.'.solved'}   = $score;
  $rec_update++;   $rec_update++;
     }      }
   
       $line .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
    '<td align="center">'.$awarded.
    ($score eq 'excused' ? $score : '').'&nbsp;</td>';
   
   
     my $partid=$_;      my $partid=$_;
     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 $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'}";
     $updateflag=1;      $updateflag=1;
  }   }
  $result .= '<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>';
     }      }
  }   }
  $result .= '</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++;
     $newrecord{'resource.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}";      &Apache::lonnet::cstore(\%newrecord,$symb,$env{'request.course.id'},
     &Apache::lonnet::cstore(\%newrecord,$symb,$ENV{'request.course.id'},      $udom,$uname);
     $udom,$user);  
  }      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++;
    } else {
       push(@noupdate,
    '<td align="right">&nbsp;'.$noupdateCtr.'&nbsp;</td>'.$line);
       $noupdateCtr++;
    }
           if ($aggregateflag) {
               &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
     $cdom,$cnum);
           }
     }      }
     $result .= '</table></td></tr></table>'."\n";      if (@noupdate) {
     my $msg = '<b>Number of records updated = '.$rec_update.  # my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3;
  ' for '.$count.' student'.($count <= 1 ? '' : 's').'.</b><br />'.   my $numcols=scalar(@partid)*4+2;
  '<b>Total number of students = '.$ENV{'form.total'}.'</b><br />';   $result .= &Apache::loncommon::start_data_table_row('LC_empty_row').
       '<td align="center" colspan="'.$numcols.'">'.
       &mt('No Changes Occurred For the Students Below').
       '</td>'.
       &Apache::loncommon::end_data_table_row();
    foreach my $line (@noupdate) {
       $result.=
    &Apache::loncommon::start_data_table_row().
    $line.
    &Apache::loncommon::end_data_table_row();
    }
       }
       $result .= &Apache::loncommon::end_data_table().
    &show_grading_menu_form($symb);
       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 1897  sub split_part_type { Line 3637  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 1912  sub split_part_type { Line 3652  sub split_part_type {
 #  #
 #--- Javascript to handle csv upload  #--- Javascript to handle csv upload
 sub csvupload_javascript_reverse_associate {  sub csvupload_javascript_reverse_associate {
       my $error1=&mt('You need to specify the username or ID');
       my $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 1950  ENDPICK Line 3692  ENDPICK
 }  }
   
 sub csvupload_javascript_forward_associate {  sub csvupload_javascript_forward_associate {
       my $error1=&mt('You need to specify the username or 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 1985  ENDPICK Line 3729  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='<table border="0">';      my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
     $result.='<tr><td colspan=3><font size=+1><b>Resource: </b>'.$url.'</font></td></tr>';      my $checked=(($env{'form.noFirstLine'})?' checked="checked"':'');
     my ($partlist,$handgrade) = &response_type($url);      my $ignore=&mt('Ignore First Line');
     my ($resptype,$hdgrade)=('','no');      $symb = &Apache::lonenc::check_encrypt($symb);
     for (sort keys(%$handgrade)) {  
  my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_});  
  $resptype = $responsetype;  
  $hdgrade = $handgrade if ($handgrade eq 'yes');  
  $result.='<tr><td><b>Part </b>'.(split(/_/))[0].'</td>'.  
     '<td><b>Type: </b>'.$responsetype.'</td>'.  
     '<td><b>Handgrade: </b>'.$handgrade.'</font></td></tr>';  
     }  
     $result.='</table>';  
     $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);" />
   <label><input type="checkbox" name="noFirstLine" $checked />$ignore</label>
 <input type="hidden" name="associate"  value="" />  <input type="hidden" name="associate"  value="" />
 <input type="hidden" name="phase"      value="three" />  <input type="hidden" name="phase"      value="three" />
 <input type="hidden" name="datatoken"  value="$datatoken" />  <input type="hidden" name="datatoken"  value="$datatoken" />
 <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="saveState"  value="$env{'form.saveState'}" />
 <input type="hidden" name="command"    value="csvuploadassign" />  <input type="hidden" name="probTitle"  value="$env{'form.probTitle'}" />
   <input type="hidden" name="command"    value="csvuploadoptions" />
 <hr />  <hr />
 <script type="text/javascript" language="Javascript">  <script type="text/javascript" language="Javascript">
 $javascript  $javascript
 </script>  </script>
 ENDPICK  ENDPICK
 return '';      return '';
   
 }  }
   
 sub csvupload_fields {  sub csvupload_fields {
     my ($url) = @_;      my ($symb) = @_;
     my (@parts) = &getpartlist($url);      my (@parts) = &getpartlist($symb);
     my @fields=(['username','Student Username'],['domain','Student Domain']);      my @fields=(['ID','Student 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 2061  sub csvuploadmap_footer { Line 3804  sub csvuploadmap_footer {
 ENDPICK  ENDPICK
 }  }
   
   sub checkforfile_js {
       my $result =<<CSVFORMJS;
   <script type="text/javascript" language="javascript">
       function checkUpload(formname) {
    if (formname.upfile.value == "") {
       alert("Please use the browse button to select a file from your local directory.");
       return false;
    }
    formname.submit();
       }
       </script>
   CSVFORMJS
       return $result;
   }
   
   sub upcsvScores_form {
       my ($request) = shift;
       my ($symb)=&get_symb($request);
       if (!$symb) {return '';}
       my $result=&checkforfile_js();
       $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
       my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});
       $result.=$table;
       $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";
       my $upload=&mt("Upload Scores");
       my $upfile_select=&Apache::loncommon::upfile_select_html();
       my $ignore=&mt('Ignore First Line');
       $symb = &Apache::lonenc::check_encrypt($symb);
       $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="csvuploadmap" />
   <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
   <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
   $upfile_select
   <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="$upload" />
   <label><input type="checkbox" name="noFirstLine" />$ignore</label>
   </form>
   ENDUPFORM
       $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.=&show_grading_menu_form($symb);
       return $result;
   }
   
   
 sub csvuploadmap {  sub csvuploadmap {
     my ($request)= @_;      my ($request)= @_;
     my ($symb,$url)=&get_symb_and_url($request);      my ($symb)=&get_symb($request);
     if (!$symb) {return '';}      if (!$symb) {return '';}
   
     my $datatoken;      my $datatoken;
     if (!$ENV{'form.datatoken'}) {      if (!$env{'form.datatoken'}) {
  $datatoken=&Apache::loncommon::upfile_store($request);   $datatoken=&Apache::loncommon::upfile_store($request);
     } else {      } else {
  $datatoken=$ENV{'form.datatoken'};   $datatoken=$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);      if ($env{'form.noFirstLine'}) { shift(@records); }
       &csvuploadmap_header($request,$symb,$datatoken,$#records+1);
     my ($i,$keyfields);      my ($i,$keyfields);
     if (@records) {      if (@records) {
  my @fields=&csvupload_fields($url);   my @fields=&csvupload_fields($symb);
   
  if ($ENV{'form.upfile_associate'} eq 'reverse') {   if ($env{'form.upfile_associate'} eq 'reverse') {
     &Apache::loncommon::csv_print_samples($request,\@records);      &Apache::loncommon::csv_print_samples($request,\@records);
     $i=&Apache::loncommon::csv_print_select_table($request,\@records,      $i=&Apache::loncommon::csv_print_select_table($request,\@records,
   \@fields);    \@fields);
Line 2088  sub csvuploadmap { Line 3885  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));
   
     return '';      return '';
 }  }
   
 sub csvuploadassign {  sub csvuploadoptions {
     my ($request)= @_;      my ($request)= @_;
     my ($symb,$url)=&get_symb_and_url($request);      my ($symb)=&get_symb($request);
     if (!$symb) {return '';}      my $checked=(($env{'form.noFirstLine'})?'1':'0');
     &Apache::loncommon::load_tmp_file($request);      my $ignore=&mt('Ignore First Line');
     my @gradedata = &Apache::loncommon::upfile_record_sep();      $request->print(<<ENDPICK);
     my @keyfields = split(/\,/,$ENV{'form.keyfields'});  <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
     my %fields=();  <h3><span class="LC_info">Uploading Class Grade Options</span></h3>
     for (my $i=0; $i<=$ENV{'form.nfields'}; $i++) {  <input type="hidden" name="command"    value="csvuploadassign" />
  if ($ENV{'form.upfile_associate'} eq 'reverse') {  <!--
     if ($ENV{'form.f'.$i} ne 'none') {  <p>
  $fields{$keyfields[$i]}=$ENV{'form.f'.$i};  <label>
      <input type="checkbox" name="show_full_results" />
      Show a table of all changes
   </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");
       $request->print(&show_grading_menu_form($symb));
       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)= @_;
       my ($symb)=&get_symb($request);
       if (!$symb) {return '';}
       my $error_msg = '';
       &Apache::loncommon::load_tmp_file($request);
       my @gradedata = &Apache::loncommon::upfile_record_sep();
       if ($env{'form.noFirstLine'}) { shift(@gradedata); }
       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','1');      my ($classlist) = &getclasslist('all',0);
       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'}};
  my $domain=$entries{$fields{'domain'}};   $username=~s/\s//g;
    if (!$username) {
       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];
    if (!&canmodify($usec)) {
       push(@notallowed,"$username:$domain");
       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'
                                             : 'correct_by_override';
                       $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,"$username:$domain no data to save"); }
    $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') {
       $request->print('.');
    } else {
       $request->print("<p>
                                 <span class=\"LC_error\">
                                    Failed to save student $username:$domain.
                                    Message when trying to save was ($result)
                                 </span>
                                </p>" );
    }
  $request->rflush();   $request->rflush();
  $countdone++;   $countdone++;
     }      }
     $request->print("<br />Stored $countdone students\n");      $request->print("<br />Saved $countdone students\n");
     if (@skipped) {      if (@skipped) {
  $request->print('<br /><font size="+1"><b>Skipped Students</b></font><br />');   $request->print('<p><h4><b>Skipped Students</b></h4></p>');
  foreach my $student (@skipped) { $request->print("<br />$student"); }   foreach my $student (@skipped) { $request->print("$student<br />\n"); }
     }      }
     $request->print(&view_edit_entire_class_form($symb,$url));      if (@notallowed) {
     $request->print(&show_grading_menu_form($symb,$url));   $request->print('<p><span class="LC_error">Students Not Allowed to Modify</span></p>');
     return '';   foreach my $student (@notallowed) { $request->print("$student<br />\n"); }
       }
       $request->print("<br />\n");
       $request->print(&show_grading_menu_form($symb));
       return $error_msg;
 }  }
 #------------- end of section for handling csv file upload ---------  #------------- end of section for handling csv file upload ---------
 #  #
 #-------------------------------------------------------------------  #-------------------------------------------------------------------
   #
   #-------------- Next few routines handle grading by page/sequence
   #
   #--- Select a page/sequence and a student to grade
   sub pickStudentPage {
       my ($request) = shift;
   
       $request->print(<<LISTJAVASCRIPT);
   <script type="text/javascript" language="javascript">
   
   function checkPickOne(formname) {
       if (radioSelection(formname.student) == null) {
    alert("Please select the student you wish to grade.");
    return;
       }
       ptr = pullDownSelection(formname.selectpage);
       formname.page.value = formname["page"+ptr].value;
       formname.title.value = formname["title"+ptr].value;
       formname.submit();
   }
   
   </script>
   LISTJAVASCRIPT
       &commonJSfunctions($request);
       my ($symb) = &get_symb($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><span class="LC_info">&nbsp;'.
    &mt('Manual Grading by Page or Sequence').'</span></h3>';
   
       $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";
       my ($titles,$symbx) = &getSymbMap();
       my ($curpage) =&Apache::lonnet::decode_symb($symb); 
   #    my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb); 
   #    my $type=($curpage =~ /\.(page|sequence)/);
       my $select = '<select name="selectpage">'."\n";
       my $ctr=0;
       foreach (@$titles) {
    my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
    $select.='<option value="'.$ctr.'" '.
       ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
       '>'.$showtitle.'</option>'."\n";
    $ctr++;
       }
       $select.= '</select>';
       $result.=&mt('&nbsp;<b>Problems from:</b> [_1]',$select)."<br />\n";
   
       $ctr=0;
       foreach (@$titles) {
    my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
    $result.='<input type="hidden" name="page'.$ctr.'" value="'.$$symbx{$_}.'" />'."\n";
    $result.='<input type="hidden" name="title'.$ctr.'" value="'.$showtitle.'" />'."\n";
    $ctr++;
       }
       $result.='<input type="hidden" name="page" />'."\n".
    '<input type="hidden" name="title" />'."\n";
   
       my $options =
    '<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;'.&mt('<b>View Problems Text: </b> [_1]',$options);
   
       $options =
    '<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".
    '<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').' </label>'."\n";
       $result.='&nbsp;'.&mt('<b>Submission Details: </b>[_1]',$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="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
    '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n";
   
       $result.='&nbsp;'.&mt('<b>Use CODE: [_1] </b>',
     '<input type="text" name="CODE" value="" />').
         '<br />'."\n";
   
       $result.='&nbsp;<input type="button" '.
    'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next-&gt;').'" /><br />'."\n";
   
       $request->print($result);
   
       my $studentTable.='&nbsp;<b>'.&mt('Select a student you wish to grade and then click on the Next button.').'</b><br />'.
    &Apache::loncommon::start_data_table().
    &Apache::loncommon::start_data_table_header_row().
    '<th align="right">&nbsp;'.&mt('No.').'</th>'.
    '<th>'.&nameUserString('header').'</th>'.
    '<th align="right">&nbsp;'.&mt('No.').'</th>'.
    '<th>'.&nameUserString('header').'</th>'.
    &Apache::loncommon::end_data_table_header_row();
    
       my (undef,undef,$fullname) = &getclasslist($getsec,'1');
       my $ptr = 1;
       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);
    $studentTable.=($ptr%2==1 ? &Apache::loncommon::start_data_table_row()
                                     : '</td>');
    $studentTable.='<td align="right">'.$ptr.'&nbsp;</td>';
    $studentTable.='<td>&nbsp;<label><input type="radio" name="student" value="'.$student.'" /> '
       .&nameUserString(undef,$$fullname{$student},$uname,$udom)."</label>\n";
    $studentTable.=
       ($ptr%2 == 0 ? '</td>'.&Apache::loncommon::end_data_table_row() 
                            : '');
    $ptr++;
       }
       if ($ptr%2 == 0) {
    $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" '.
    'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next-&gt;').'" /></form>'."\n";
   
       $studentTable.=&show_grading_menu_form($symb);
       $request->print($studentTable);
   
       return '';
   }
   
   sub getSymbMap {
       my $navmap = Apache::lonnavmaps::navmap->new();
   
       my %symbx = ();
       my @titles = ();
       my $minder = 0;
   
       # Gather every sequence that has problems.
       my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); },
          1,0,1);
       for my $sequence ($navmap->getById('0.0'), @sequences) {
    if ($navmap->hasResource($sequence, sub { shift->is_problem(); }, 0) ) {
       my $title = $minder.'.'.
    &HTML::Entities::encode($sequence->compTitle(),'"\'&');
       push(@titles, $title); # minder in case two titles are identical
       $symbx{$title} = &HTML::Entities::encode($sequence->symb(),'"\'&');
       $minder++;
    }
       }
       return \@titles,\%symbx;
   }
   
 #-------------------------- Menu interface -------------------------  
 #  #
 #--- Show a Grading Menu button - Calls the next routine ---  #--- Displays a page/sequence w/wo problems, w/wo submissions
 sub show_grading_menu_form {  sub displayPage {
     my ($symb,$url)=@_;      my ($request) = shift;
     my $result.='<form action="/adm/grades" method="post">'."\n".  
  '<input type="hidden" name="symb" value="'.$symb.'" />'."\n".      my ($symb) = &get_symb($request);
  '<input type="hidden" name="url" value="'.$url.'" />'."\n".      my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
  '<input type="hidden" name="command" value="gradingmenu" />'."\n".      my $cnum      = $env{"course.$env{'request.course.id'}.num"};
  '<input type="submit" name="submit" value="Grading Menu" />'."\n".      my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
       my $pageTitle = $env{'form.page'};
       my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
       my ($uname,$udom) = split(/:/,$env{'form.student'});
       my $usec=$classlist->{$env{'form.student'}}[5];
   
       #need to make sure we have the correct data for later EXT calls, 
       #thus invalidate the cache
       &Apache::lonnet::devalidatecourseresdata(
                    $env{'course.'.$env{'request.course.id'}.'.num'},
                    $env{'course.'.$env{'request.course.id'}.'.domain'});
       &Apache::lonnet::clear_EXT_cache_status();
   
       if (!&canview($usec)) {
    $request->print('<span class="LC_warning">'.&mt('Unable to view requested student. ([_1])',$env{'form.student'}).'</span>');
    $request->print(&show_grading_menu_form($symb));
    return;
       }
       my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
       $result.='<h3>&nbsp;'.&mt('Student: [_1]',&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom)).
    '</h3>'."\n";
       if (&Apache::lonnet::validCODE($env{'form.CODE'})) {
    $result.='<h3>&nbsp;'.&mt('CODE: [_1]',$env{'form.CODE'}).'</h3>'."\n";
       } else {
    delete($env{'form.CODE'});
       }
       &sub_page_js($request);
       $request->print($result);
   
       my $navmap = Apache::lonnavmaps::navmap->new();
       my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'});
       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>');
    $request->print(&show_grading_menu_form($symb));
    return; 
       }
       my $iterator = $navmap->getIterator($map->map_start(),
    $map->map_finish());
   
       my $studentTable='<form action="/adm/grades" method="post" name="gradePage">'."\n".
    '<input type="hidden" name="command" value="gradeByPage" />'."\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="page"    value="'.$pageTitle.'" />'."\n".
    '<input type="hidden" name="title"   value="'.$env{'form.title'}.'" />'."\n".
    '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
    '<input type="hidden" name="overRideScore" value="no" />'."\n".
    '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n";
   
       if (defined($env{'form.CODE'})) {
    $studentTable.=
       '<input type="hidden" name="CODE" value="'.$env{'form.CODE'}.'" />'."\n";
       }
       my $checkIcon = '<img alt="'.&mt('Check Mark').
    '" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
   
       $studentTable.='&nbsp;'.&mt('<b>Note:</b> Problems graded correct by the computer are marked with a [_1] symbol.',$checkIcon)."\n".
    &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);
       $iterator->next(); # skip the first BEGIN_MAP
       my $curRes = $iterator->next(); # for "current resource"
       while ($depth > 0) {
           if($curRes == $iterator->BEGIN_MAP) { $depth++; }
           if($curRes == $iterator->END_MAP) { $depth--; }
   
           if (ref($curRes) && $curRes->is_problem()) {
       my $parts = $curRes->parts();
               my $title = $curRes->compTitle();
       my $symbx = $curRes->symb();
       $studentTable.=
    &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">';
       my %form = ('CODE' => $env{'form.CODE'},);
       if ($env{'form.vProb'} eq 'yes' ) {
    $studentTable.=&show_problem($request,$symbx,$uname,$udom,1,
        undef,'both',\%form);
       } else {
    my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'},%form);
    $companswer =~ s|<form(.*?)>||g;
    $companswer =~ s|</form>||g;
   # while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a>
   #    $companswer =~ s/$1/ /ms;
   #    $request->print('match='.$1."<br />\n");
   # }
   # $companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;
    $studentTable.='&nbsp;<b>'.$title.'</b>&nbsp;<br />&nbsp;'.&mt('<b>Correct answer:</b><br />[_1]',$companswer);
       }
   
       my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
   
       if ($env{'form.lastSub'} eq 'datesub') {
    if ($record{'version'} eq '') {
       $studentTable.='<br />&nbsp;<span class="LC_warning">'.&mt('No recorded submission for this problem.').'</span><br />';
    } else {
       my %responseType = ();
       foreach my $partid (@{$parts}) {
    my @responseIds =$curRes->responseIds($partid);
    my @responseType =$curRes->responseType($partid);
    my %responseIds;
    for (my $i=0;$i<=$#responseIds;$i++) {
       $responseIds{$responseIds[$i]}=$responseType[$i];
    }
    $responseType{$partid} = \%responseIds;
       }
       $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom);
   
    }
       } elsif ($env{'form.lastSub'} eq 'all') {
    my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
    $studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom,
    $env{'request.course.id'},
    '','.submission');
    
       }
       if (&canmodify($usec)) {
    foreach my $partid (@{$parts}) {
       $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record);
       $studentTable.='<input type="hidden" name="q_'.$question.'" value="'.$partid.'" />'."\n";
       $question++;
    }
    $prob++;
       }
       $studentTable.='</td></tr>';
   
    }
           $curRes = $iterator->next();
       }
   
       $studentTable.='</table>'."\n".
    '<input type="button" value="'.&mt('Save').'" '.
    'onClick="javascript:checkSubmitPage(this.form,'.$question.');" />'.
  '</form>'."\n";   '</form>'."\n";
       $studentTable.=&show_grading_menu_form($symb);
       $request->print($studentTable);
   
       return '';
   }
   
   sub displaySubByDates {
       my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;
       my $isCODE=0;
       my $isTask = ($symb =~/\.task$/);
       if (exists($record->{'resource.CODE'})) { $isCODE=1; }
       my $studentTable=&Apache::loncommon::start_data_table().
    &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 %mark;
       my %orders;
       $mark{'correct_by_student'} = $checkIcon;
       if (!exists($$record{'1:timestamp'})) {
    return '<br />&nbsp;<span class="LC_warning">'.&mt('Nothing submitted - no attempts').'</span><br />';
       }
   
       my $interaction;
       for ($version=1;$version<=$$record{'version'};$version++) {
    my $timestamp = 
       &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 @displaySub = ();
    foreach my $partid (@{$parts}) {
       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 '');
       my $display_part=&get_display_part($partid,$symb);
       foreach my $matchKey (@matchKey) {
    if (exists($$record{$version.':'.$matchKey}) &&
       $$record{$version.':'.$matchKey} ne '') {
   
       my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
                  : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
       $displaySub[0].='<b>'.&mt('Part:').'</b>&nbsp;'.$display_part.'&nbsp;';
       $displaySub[0].='<span class="LC_internal_info">('.&mt('ID').'&nbsp;'.
    $responseId.')</span>&nbsp;<b>';
       if ($$record{"$where.$partid.tries"} eq '') {
    $displaySub[0].=&mt('Trial&nbsp;not&nbsp;counted');
       } else {
    $displaySub[0].=&mt('Trial&nbsp;[_1]',
       $$record{"$where.$partid.tries"});
       }
       my $responseType=($isTask ? 'Task'
                                                 : $responseType->{$partid}->{$responseId});
       if (!exists($orders{$partid})) { $orders{$partid}={}; }
       if (!exists($orders{$partid}->{$responseId})) {
    $orders{$partid}->{$responseId}=
       &get_order($partid,$responseId,$symb,$uname,$udom);
       }
       $displaySub[0].='</b>&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 />';
       }
       if (exists $$record{"$where.$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.')';
       }
    }
    # needed because old essay regrader has not parts info
    if (exists $$record{"$version:resource.regrader"}) {
       $displaySub[2].=$$record{"$version:resource.regrader"};
    }
    $studentTable.='<td>'.$displaySub[0].'&nbsp;</td><td>'.$displaySub[1];
    if ($displaySub[2]) {
       $studentTable.=&mt('Manually graded by [_1]',$displaySub[2]);
    }
    $studentTable.='&nbsp;</td>'.
       &Apache::loncommon::end_data_table_row();
       }
       $studentTable.=&Apache::loncommon::end_data_table();
       return $studentTable;
   }
   
   sub updateGradeByPage {
       my ($request) = shift;
   
       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 $pageTitle = $env{'form.page'};
       my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
       my ($uname,$udom) = split(/:/,$env{'form.student'});
       my $usec=$classlist->{$env{'form.student'}}[5];
       if (!&canmodify($usec)) {
    $request->print('<span class="LC_warning">Unable to modify requested student.('.$env{'form.student'}.'</span>');
    $request->print(&show_grading_menu_form($env{'form.symb'}));
    return;
       }
       my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
       $result.='<h3>&nbsp;Student: '.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
    '</h3>'."\n";
   
       $request->print($result);
   
       my $navmap = Apache::lonnavmaps::navmap->new();
       my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'});
       my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
       if (!$map) {
    $request->print('<span class="LC_warning">Unable to grade requested sequence. ('.$resUrl.')</span>');
    my ($symb)=&get_symb($request);
    $request->print(&show_grading_menu_form($symb));
    return; 
       }
       my $iterator = $navmap->getIterator($map->map_start(),
    $map->map_finish());
   
       my $studentTable=
    &Apache::loncommon::start_data_table().
    &Apache::loncommon::start_data_table_header_row().
    '<th align="center">&nbsp;'.&mt('Prob.').'&nbsp;</th>'.
    '<th>&nbsp;'.&mt('Title').'&nbsp;</th>'.
    '<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
       my $curRes = $iterator->next(); # for "current resource"
       my ($depth,$question,$prob,$changeflag)= (1,1,1,0);
       while ($depth > 0) {
           if($curRes == $iterator->BEGIN_MAP) { $depth++; }
           if($curRes == $iterator->END_MAP) { $depth--; }
   
           if (ref($curRes) && $curRes->is_problem()) {
       my $parts = $curRes->parts();
               my $title = $curRes->compTitle();
       my $symbx = $curRes->symb();
       $studentTable.=
    &Apache::loncommon::start_data_table_row().
    '<td align="center" valign="top" >'.$prob.
    (scalar(@{$parts}) == 1 ? '' 
                                           : '<br />('.&mt('[quant,_1,&nbsp;parts]',scalar(@{$parts}))
    ).')</td>';
       $studentTable.='<td valign="top">&nbsp;<b>'.$title.'</b>&nbsp;</td>';
   
       my %newrecord=();
       my @displayPts=();
               my %aggregate = ();
               my $aggregateflag = 0;
       foreach my $partid (@{$parts}) {
    my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};
    my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};
   
    my $wgt = $env{'form.WGT'.$question.'_'.$partid} != 0 ? 
       $env{'form.WGT'.$question.'_'.$partid} : 1;
    my $partial = $newpts/$wgt;
    my $score;
    if ($partial > 0) {
       $score = 'correct_by_override';
    } elsif ($newpts ne '') { #empty is taken as 0
       $score = 'incorrect_by_override';
    }
    my $dropMenu = $env{'form.GD_SEL'.$question.'_'.$partid};
    if ($dropMenu eq 'excused') {
       $partial = '';
       $score = 'excused';
    } elsif ($dropMenu eq 'reset status'
    && $env{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists
       $newrecord{'resource.'.$partid.'.tries'} = 0;
       $newrecord{'resource.'.$partid.'.solved'} = '';
       $newrecord{'resource.'.$partid.'.award'} = '';
       $newrecord{'resource.'.$partid.'.awarded'} = 0;
       $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}";
       $changeflag++;
       $newpts = '';
                       
                       my $aggtries =  $env{'form.aggtries'.$question.'_'.$partid};
                       my $totaltries = $env{'form.totaltries'.$question.'_'.$partid};
                       my $solvedstatus = $env{'form.solved'.$question.'_'.$partid};
                       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>Part:</b> '.$display_part.' = '.
       (($oldstatus eq 'excused') ? 'excused' : $oldpts).
       '&nbsp;<br />';
    $displayPts[1].='&nbsp;<b>Part:</b> '.$display_part.' = '.
        (($score eq 'excused') ? 'excused' : $newpts).
       '&nbsp;<br />';
    $question++;
    next if ($dropMenu eq 'reset status' || ($newpts eq $oldpts && $score ne 'excused'));
   
    $newrecord{'resource.'.$partid.'.awarded'}  = $partial if $partial ne '';
    $newrecord{'resource.'.$partid.'.solved'}   = $score if $score ne '';
    $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}"
       if (scalar(keys(%newrecord)) > 0);
   
    $changeflag++;
       }
       if (scalar(keys(%newrecord)) > 0) {
    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);
    %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>'.
    '<td valign="top">'.$displayPts[1].'</td>'.
    &Apache::loncommon::end_data_table_row();
   
       $prob++;
    }
           $curRes = $iterator->next();
       }
   
       $studentTable.=&Apache::loncommon::end_data_table();
       $studentTable.=&show_grading_menu_form($env{'form.symb'});
       my $grademsg=($changeflag == 0 ? 'No score was changed or updated.' :
     'The scores were changed for '.
     $changeflag.' problem'.($changeflag == 1 ? '.' : 's.'));
       $request->print($grademsg.$studentTable);
   
       return '';
   }
   
   #-------- end of section for handling grading by page/sequence ---------
   #
   #-------------------------------------------------------------------
   
   #--------------------Scantron Grading-----------------------------------
   #
   #------ start of section for handling grading by page/sequence ---------
   
   =pod
   
   =head1 Bubble sheet grading routines
   
     For this documentation:
   
      'scanline' refers to the full line of characters
      from the file that we are parsing that represents one entire sheet
   
      'bubble line' refers to the data
      representing the line of bubbles that are on the physical bubble sheet
   
   
   The overall process is that a scanned in bubble sheet data is uploaded
   into a course. When a user wants to grade, they select a
   sequence/folder of resources, a file of bubble sheet info, and pick
   one of the predefined configurations for what each scanline looks
   like.
   
   Next each scanline is checked for any errors of either 'missing
   bubbles' (it's an error because it may have been mis-scanned
   because too light bubbling), 'double bubble' (each bubble line should
   have no more that one letter picked), invalid or duplicated CODE,
   invalid student ID
   
   If the CODE option is used that determines the randomization of the
   homework problems, either way the student ID is looked up into a
   username:domain.
   
   During the validation phase the instructor can choose to skip scanlines. 
   
   After the validation phase, there are now 3 bubble sheet files
   
     scantron_original_filename (unmodified original file)
     scantron_corrected_filename (file where the corrected information has replaced the original information)
     scantron_skipped_filename (contains the exact text of scanlines that where skipped)
   
   Also there is a separate hash nohist_scantrondata that contains extra
   correction information that isn't representable in the bubble sheet
   file (see &scantron_getfile() for more information)
   
   After all scanlines are either valid, marked as valid or skipped, then
   foreach line foreach problem in the picked sequence, an ssi request is
   made that simulates a user submitting their selected letter(s) against
   the homework problem.
   
   =over 4
   
   
   
   =item defaultFormData
   
     Returns html hidden inputs used to hold context/default values.
   
    Arguments:
     $symb - $symb of the current resource 
   
   =cut
   
   sub defaultFormData {
       my ($symb)=@_;
       return '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
        '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
        '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
   }
   
   
   =pod 
   
   =item getSequenceDropDown
   
      Return html dropdown of possible sequences to grade
    
    Arguments:
      $symb - $symb of the current resource 
   
   =cut
   
   sub getSequenceDropDown {
       my ($symb)=@_;
       my $result='<select name="selectpage">'."\n";
       my ($titles,$symbx) = &getSymbMap();
       my ($curpage)=&Apache::lonnet::decode_symb($symb); 
       my $ctr=0;
       foreach (@$titles) {
    my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
    $result.='<option value="'.$$symbx{$_}.'" '.
       ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
       '>'.$showtitle.'</option>'."\n";
    $ctr++;
       }
       $result.= '</select>';
     return $result;      return $result;
 }  }
   
 #--- Displays the main menu page -------  
 sub gradingmenu {  =pod 
     my ($request) = @_;  
     my ($symb,$url)=&get_symb_and_url($request);  =item scantron_filenames
   
      Returns a list of the scantron files in the current course 
   
   =cut
   
   sub scantron_filenames {
       my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my @files=&Apache::lonnet::dirlist('userfiles',$cdom,$cname,
       &propath($cdom,$cname));
       my @possiblenames;
       foreach my $filename (sort(@files)) {
    ($filename)=split(/&/,$filename);
    if ($filename!~/^scantron_orig_/) { next ; }
    $filename=~s/^scantron_orig_//;
    push(@possiblenames,$filename);
       }
       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 {
       my ($file2grade) = @_;
       my $result= '<select name="scantron_selectfile">';
       $result.="<option></option>";
       foreach my $filename (sort(&scantron_filenames())) {
    $result.="<option".($filename eq $file2grade ? ' selected="selected"':'').">$filename</option>\n";
       }
       $result.="</select>";
       return $result;
   }
   
   =pod 
   
   =item scantron_scantab
   
     Returns html drop down of the scantron formats in the scantronformat.tab
     file.
   
   =cut
   
   sub scantron_scantab {
       my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
       my $result='<select name="scantron_format">'."\n";
       $result.='<option></option>'."\n";
       foreach my $line (<$fh>) {
    my ($name,$descrip)=split(/:/,$line);
    if ($name =~ /^\#/) { next; }
    $result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";
       }
       $result.='</select>'."\n";
   
       return $result;
   }
   
   =pod 
   
   =item scantron_CODElist
   
     Returns html drop down of the saved CODE lists from current course,
     generated from earlier printings.
   
   =cut
   
   sub scantron_CODElist {
       my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
       my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
       my @names=&Apache::lonnet::getkeys('CODEs',$cdom,$cnum);
       my $namechoice='<option></option>';
       foreach my $name (sort {uc($a) cmp uc($b)} @names) {
    if ($name =~ /^error: 2 /) { next; }
    if ($name =~ /^type\0/) { next; }
    $namechoice.='<option value="'.$name.'">'.$name.'</option>';
       }
       $namechoice='<select name="scantron_CODElist">'.$namechoice.'</select>';
       return $namechoice;
   }
   
   =pod 
   
   =item scantron_CODEunique
   
     Returns the html for "Each CODE to be used once" radio.
   
   =cut
   
   sub scantron_CODEunique {
       my $result='<span style="white-space: nowrap;">
                    <label><input type="radio" name="scantron_CODEunique"
                           value="yes" checked="checked" />'.&mt('Yes').' </label>
                   </span>
                   <span style="white-space: nowrap;">
                    <label><input type="radio" name="scantron_CODEunique"
                           value="no" />'.&mt('No').' </label>
                   </span>';
       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 {
       my ($r,$file2grade) = @_;
       my ($symb)=&get_symb($r);
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $result='<h3>&nbsp;<font color="#339933">Select a Grading Method</font></h3>';      my $sequence_selector=&getSequenceDropDown($symb);
     $result.='<table border="0">';      my $default_form_data=&defaultFormData($symb);
     $result.='<tr><td colspan=3><font size=+1><b>Resource: </b>'.$url.'</font></td></tr>';      my $grading_menu_button=&show_grading_menu_form($symb);
     my ($partlist,$handgrade) = &response_type($url);      my $file_selector=&scantron_uploads($file2grade);
     my ($resptype,$hdgrade)=('','no');      my $format_selector=&scantron_scantab();
     for (sort keys(%$handgrade)) {      my $CODE_selector=&scantron_CODElist();
  my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_});      my $CODE_unique=&scantron_CODEunique();
  $resptype = $responsetype;      my $result;
  $hdgrade = $handgrade if ($handgrade eq 'yes');  
  $result.='<tr><td><b>Part </b>'.(split(/_/))[0].'</td>'.      # Chunk of form to prompt for a file to grade and how:
     '<td><b>Type: </b>'.$responsetype.'</td>'.  
     '<td><b>Handgrade: </b>'.$handgrade.'</font></td></tr>';      $result.= '
     }      <br />
     $result.='</table>';      <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">
     $result.=&view_edit_entire_class_form($symb,$url).'<br />';      <input type="hidden" name="command" value="scantron_warning" />
     $result.=&upcsvScores_form($symb,$url).'<br />';      '.$default_form_data.'
     $result.=&viewGradeaStu_form($symb,$url,$resptype,$hdgrade).'<br />';      '.&Apache::loncommon::start_data_table('LC_scantron_action').'
     $result.=&verifyReceipt_form($symb,$url)          '.&Apache::loncommon::start_data_table_header_row().'
  if ((&Apache::lonnet::allowed('mgr',$ENV{'request.course.id'})) && ($symb));              <th colspan="2">
                 &nbsp;Specify file and which Folder/Sequence to grade
               </th>
          '.&Apache::loncommon::end_data_table_header_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td> Sequence to grade: </td><td> '.$sequence_selector.' </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td> Filename of scoring office file: </td><td> '.$file_selector.' </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td> Format of data file: </td><td> '.$format_selector.' </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td> Saved CODEs to validate against: </td><td> '.$CODE_selector.' </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td> 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> Options: </td>
               <td>
          <label><input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> Do only previously skipped records</label> <br />
                  <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> Remove all existing corrections</label> <br />
                  <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> Skip hidden resources when grading</label>
       </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td colspan="2">
                 <input type="submit" value="Grading: Validate Scantron Records" />
               </td>
          '.&Apache::loncommon::end_data_table_row().'
       '.&Apache::loncommon::end_data_table().'
       </form>
   ';
      
       $r->print($result);
   
       if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
           &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
   
    # Chunk of form to prompt for a scantron file upload.
   
           $r->print('
       <br />
       '.&Apache::loncommon::start_data_table('LC_scantron_action').'
          '.&Apache::loncommon::start_data_table_header_row().'
               <th>
                 &nbsp;Specify a Scantron data file to upload.
               </th>
          '.&Apache::loncommon::end_data_table_header_row().'
          '.&Apache::loncommon::start_data_table_row().'
               <td>
   ');
       my $default_form_data=&defaultFormData(&get_symb($r,1));
       my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
       my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};
       $r->print(<<UPLOAD);
                 <script type="text/javascript" language="javascript">
       function checkUpload(formname) {
    if (formname.upfile.value == "") {
       alert("Please use the browse button to select a file from your local directory.");
       return false;
    }
    formname.submit();
       }
                 </script>
   
                 <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'>
                   $default_form_data
                   <input name='courseid' type='hidden' value='$cnum' />
                   <input name='domainid' type='hidden' value='$cdom' />
                   <input name='command' value='scantronupload_save' type='hidden' />
                   File to upload:<input type="file" name="upfile" size="50" />
                   <br />
                   <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" />
                 </form>
   UPLOAD
   
           $r->print('
               </td>
          '.&Apache::loncommon::end_data_table_row().'
          '.&Apache::loncommon::end_data_table().'
   ');
       }
   
       # 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;Download a scoring office file
                 </th>
          '.&Apache::loncommon::end_data_table_header_row().'
          '.&Apache::loncommon::start_data_table_row().'
                 <td> Filename of scoring office file: '.$file_selector.' 
                   <br />
                   <input type="submit" value="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($grading_menu_button);
       return
   }
   
   =pod
   
   =item get_scantron_config
   
      Parse and return the scantron configuration line selected as a
      hash of configuration file fields.
   
    Arguments:
       which - the name of the configuration to parse from the file.
   
   
    Returns:
               If the named configuration is not in the file, an empty
               hash is returned.
       a hash with the fields
         name         - internal name for the this configuration setup
         description  - text to display to operator that describes this config
         CODElocation - if 0 or the string 'none'
                             - no CODE exists for this config
                        if -1 || the string 'letter'
                             - a CODE exists for this config and is
                               a string of letters
                        Unsupported value (but planned for future support)
                             if a positive integer
                                  - The CODE exists as the first n items from
                                    the question section of the form
                             if the string 'number'
                                  - The CODE exists for this config and is
                                    a string of numbers
         CODEstart   - (only matter if a CODE exists) column in the line where
                        the CODE starts
         CODElength  - length of the CODE
         IDstart     - column where the student ID number starts
         IDlength    - length of the student ID info
         Qstart      - column where the information from the bubbled
                       'questions' start
         Qlength     - number of columns comprising a single bubble line from
                       the sheet. (usually either 1 or 10)
         Qon         - either a single character representing the character used
                       to signal a bubble was chosen in the positional setup, or
                       the string 'letter' if the letter of the chosen bubble is
                       in the final, or 'number' if a number representing the
                       chosen bubble is in the file (1->A 0->J)
         Qoff        - the character used to represent that a bubble was
                       left blank
         PaperID     - if the scanning process generates a unique number for each
                       sheet scanned the column that this ID number starts in
         PaperIDlength - number of columns that comprise the unique ID number
                         for the sheet of paper
         FirstName   - column that the first name starts in
         FirstNameLength - number of columns that the first name spans
     
     return $result;        LastName    - column that the last name starts in
         LastNameLength - number of columns that the last name spans
   
   =cut
   
   sub get_scantron_config {
       my ($which) = @_;
       my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
       my %config;
       #FIXME probably should move to XML it has already gotten a bit much now
       foreach my $line (<$fh>) {
    my ($name,$descrip)=split(/:/,$line);
    if ($name ne $which ) { next; }
    chomp($line);
    my @config=split(/:/,$line);
    $config{'name'}=$config[0];
    $config{'description'}=$config[1];
    $config{'CODElocation'}=$config[2];
    $config{'CODEstart'}=$config[3];
    $config{'CODElength'}=$config[4];
    $config{'IDstart'}=$config[5];
    $config{'IDlength'}=$config[6];
    $config{'Qstart'}=$config[7];
    $config{'Qlength'}=$config[8];
    $config{'Qoff'}=$config[9];
    $config{'Qon'}=$config[10];
    $config{'PaperID'}=$config[11];
    $config{'PaperIDlength'}=$config[12];
    $config{'FirstName'}=$config[13];
    $config{'FirstNamelength'}=$config[14];
    $config{'LastName'}=$config[15];
    $config{'LastNamelength'}=$config[16];
    last;
       }
       return %config;
   }
   
   =pod 
   
   =item username_to_idmap
   
       creates a hash keyed by student id with values of the corresponding
       student username:domain.
   
     Arguments:
   
       $classlist - reference to the class list hash. This is a hash
                    keyed by student name:domain  whose elements are references
                    to arrays containing various chunks of information
                    about the student. (See loncoursedata for more info).
   
     Returns
       %idmap - the constructed hash
   
   =cut
   
   sub username_to_idmap {
       my ($classlist)= @_;
       my %idmap;
       foreach my $student (keys(%$classlist)) {
    $idmap{$classlist->{$student}->[&Apache::loncoursedata::CL_ID]}=
       $student;
       }
       return %idmap;
   }
   
   =pod
   
   =item scantron_fixup_scanline
   
      Process a requested correction to a scanline.
   
     Arguments:
       $scantron_config   - hash from &get_scantron_config()
       $scan_data         - hash of correction information 
                             (see &scantron_getfile())
       $line              - existing scanline
       $whichline         - line number of the passed in scanline
       $field             - type of change to process 
                            (either 
                             'ID'     -> correct the student ID number
                             'CODE'   -> correct the CODE
                             'answer' -> fixup the submitted answers)
       
      $args               - hash of additional info,
                             - 'ID' 
                                  'newid' -> studentID to use in replacement
                                             of existing one
                             - 'CODE' 
                                  'CODE_ignore_dup' - set to true if duplicates
                                                      should be ignored.
                          'CODE' - is new code or 'use_unfound'
                                           if the existing unfound code should
                                           be used as is
                             - 'answer'
                                  'response' - new answer or 'none' if blank
                                  'question' - the bubble line to change
   
     Returns:
       $line - the modified scanline
   
     Side effects: 
       $scan_data - may be updated
   
   =cut
   
   
   sub scantron_fixup_scanline {
       my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
       
       
       if ($field eq 'ID') {
    if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {
       return ($line,1,'New value too large');
    }
    if (length($args->{'newid'}) < $$scantron_config{'IDlength'}) {
       $args->{'newid'}=sprintf('%-'.$$scantron_config{'IDlength'}.'s',
        $args->{'newid'});
    }
    substr($line,$$scantron_config{'IDstart'}-1,
          $$scantron_config{'IDlength'})=$args->{'newid'};
    if ($args->{'newid'}=~/^\s*$/) {
       &scan_data($scan_data,"$whichline.user",
          $args->{'username'}.':'.$args->{'domain'});
    }
       } elsif ($field eq 'CODE') {
    if ($args->{'CODE_ignore_dup'}) {
       &scan_data($scan_data,"$whichline.CODE_ignore_dup",'1');
    }
    &scan_data($scan_data,"$whichline.useCODE",'1');
    if ($args->{'CODE'} ne 'use_unfound') {
       if (length($args->{'CODE'}) > $$scantron_config{'CODElength'}) {
    return ($line,1,'New CODE value too large');
       }
       if (length($args->{'CODE'}) < $$scantron_config{'CODElength'}) {
    $args->{'CODE'}=sprintf('%-'.$$scantron_config{'CODElength'}.'s',$args->{'CODE'});
       }
       substr($line,$$scantron_config{'CODEstart'}-1,
      $$scantron_config{'CODElength'})=$args->{'CODE'};
    }
       } elsif ($field eq 'answer') {
    &scantron_get_maxbubble(); # Need the bubble counter info.
    my $length =$scantron_config->{'Qlength'};
    my $off=$scantron_config->{'Qoff'};
    my $on=$scantron_config->{'Qon'};
           my $question_number = $args->{'question'} -1;
           my $first_position  = $first_bubble_line{$question_number};
    my $bubble_count    = $bubble_lines_per_response{$question_number};
           my $bubbles_per_line= $$scantron_config{'Qlength'};
    my $answer=${off}x($bubbles_per_line*$bubble_count);
           my $final_answer;
           if ($$scantron_config{'Qon'} eq 'letter'  ||
       $$scantron_config{'Qon'} eq 'number') { 
       $bubbles_per_line = 10;
    }
    if (defined $args->{'response'}) {
       
       if ($args->{'response'} eq 'none') {
    &scan_data($scan_data,
      "$whichline.no_bubble.".$args->{'question'},'1');
       } else {
    my ($bubble_line, $bubble_number) = split(/:/,$args->{'response'});
    if ($on eq 'letter') {
       my @alphabet=('A'..'Z');
       $answer=$alphabet[$bubble_number];
    } elsif ($on eq 'number') {
       $answer= $bubble_number+1;
       if ($answer == 10) { $answer = '0'; }
    } else {
       substr($answer,$bubble_number+$bubble_line*$bubbles_per_line,1)=$on;
       $final_answer = $answer;
    }
    &scan_data($scan_data,
      "$whichline.no_bubble.".$args->{'question'},undef,'1');
   
    # Positional notation already has the right final answer length..
   
    if (($on eq 'letter') || ($on eq 'number')) {
       for (my $l = 0; $l < $bubble_count; $l++) {
    if ($l eq $bubble_line) {
       $final_answer .= $answer;
    } else {
       $final_answer .= ' ';
    }
       }
    }
       }
       # $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'};
       #substr($line,$where-1,$length)=$answer;
       substr($line, 
      $scantron_config->{'Qstart'}+$first_position-1,
      $bubbles_per_line*$length) = $final_answer;
    }
       }
       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 {
       my ($scan_data,$key,$value,$delete)=@_;
       my $filename=$env{'form.scantron_selectfile'};
       if (defined($value)) {
    $scan_data->{$filename.'_'.$key} = $value;
       }
       if ($delete) { delete($scan_data->{$filename.'_'.$key}); }
       return $scan_data->{$filename.'_'.$key};
   }
   
   =pod 
   
   =item scantron_parse_scanline
   
     Decodes a scanline from the selected scantron file
   
    Arguments:
       line             - The text of the scantron file line to process
       whichline        - Line number
       scantron_config  - Hash describing the format of the scantron lines.
       scan_data        - Hash of extra information about the scanline
                          (see scantron_getfile for more information)
       just_header      - True if should not process question answers but only
                          the stuff to the left of the answers.
    Returns:
      Hash containing the result of parsing the scanline
   
      Keys are all proceeded by the string 'scantron.'
   
          CODE    - the CODE in use for this scanline
          useCODE - 1 if the CODE is invalid but it usage has been forced
                    by the operator
          CODE_ignore_dup - 1 if the CODE is a duplicated use when unique
                               CODEs were selected, but the usage has been
                               forced by the operator
          ID  - student ID
          PaperID - if used, the ID number printed on the sheet when the 
                    paper was scanned
          FirstName - first name from the sheet
          LastName  - last name from the sheet
   
        if just_header was not true these key may also exist
   
          missingerror - a list of bubble ranges that are considered to be answers
                         to a single question that don't have any bubbles filled in.
                         Of the form questionnumber:firstbubblenumber:count.
          doubleerror  - a list of bubble ranges that are considered to be answers
                         to a single question that have more than one bubble filled in.
                         Of the form questionnumber::firstbubblenumber:count
      
                   In the above, count is the number of bubble responses in the
                   input line needed to represent the possible answers to the question.
                   e.g. a radioresponse with 15 choices in an answer sheet with 10 choices
                   per line would have count = 2.
   
          maxquest     - the number of the last bubble line that was parsed
   
          (<number> starts at 1)
          <number>.answer - zero or more letters representing the selected
                            letters from the scanline for the bubble line 
                            <number>.
                            if blank there was either no bubble or there where
                            multiple bubbles, (consult the keys missingerror and
                            doubleerror if this is an error condition)
   
   =cut
   
   sub scantron_parse_scanline {
       my ($line,$whichline,$scantron_config,$scan_data,$just_header)=@_;
   
       my %record;
       my $questions=substr($line,$$scantron_config{'Qstart'}-1);  # Answers
       my $data=substr($line,0,$$scantron_config{'Qstart'}-1);     # earlier stuff
       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,
       $$scantron_config{'CODEstart'}-1,
       $$scantron_config{'CODElength'});
       if (&scan_data($scan_data,"$whichline.useCODE")) {
    $record{'scantron.useCODE'}=1;
       }
       if (&scan_data($scan_data,"$whichline.CODE_ignore_dup")) {
    $record{'scantron.CODE_ignore_dup'}=1;
       }
    } else {
       #FIXME interpret first N questions
    }
       }
       $record{'scantron.ID'}=substr($data,$$scantron_config{'IDstart'}-1,
     $$scantron_config{'IDlength'});
       $record{'scantron.PaperID'}=
    substr($data,$$scantron_config{'PaperID'}-1,
          $$scantron_config{'PaperIDlength'});
       $record{'scantron.FirstName'}=
    substr($data,$$scantron_config{'FirstName'}-1,
          $$scantron_config{'FirstNamelength'});
       $record{'scantron.LastName'}=
    substr($data,$$scantron_config{'LastName'}-1,
          $$scantron_config{'LastNamelength'});
       if ($just_header) { return \%record; }
   
       my @alphabet=('A'..'Z');
       my $questnum=0;
       my $ansnum  =1; # Multiple 'answer lines'/question.
   
       chomp($questions); # Get rid of any trailing \n.
       $questions =~ s/\r$//;      # Get rid of trailing \r too (MAC or Win uploads).
       while (length($questions)) {
    my $answers_needed = $bubble_lines_per_response{$questnum};
    my $answer_length  = $$scantron_config{'Qlength'} * $answers_needed;
   
   
   
    $questnum++;
    my $currentquest = substr($questions,0,$answer_length);
    $questions       = substr($questions,0,$answer_length)='';
    if (length($currentquest) < $answer_length) { next; }
   
    # Qon letter implies for each slot in currentquest we have:
    #    ? or * for doubles a letter in A-Z for a bubble and
           #    about anything else (esp. a value of Qoff for missing
    #    bubbles.
   
   
    if ($$scantron_config{'Qon'} eq 'letter') {
   
       if ($currentquest =~ /\?/
    || $currentquest =~ /\*/
    || (&occurence_count($currentquest, "[A-Z]") > 1)) {
    push(@{$record{'scantron.doubleerror'}},$questnum);
    for (my $ans = 0; $ans < $answers_needed; $ans++) { 
       my $bubble = substr($currentquest, $ans, 1);
       if ($bubble =~ /[A-Z]/ ) {
    $record{"scantron.$ansnum.answer"} = $bubble;
       } else {
    $record{"scantron.$ansnum.answer"}='';
       }
       $ansnum++;
    }
   
       } elsif (!defined($currentquest)
        || (&occurence_count($currentquest, $$scantron_config{'Qoff'}) == length($currentquest))
        || (&occurence_count($currentquest, "[A-Z]") == 0)) {
    for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
       $record{"scantron.$ansnum.answer"}='';
       $ansnum++;
   
    }
    if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
       push(@{$record{"scantron.missingerror"}},$questnum);
      #  $ansnum += $answers_needed;
    }
       } else {
    for (my $ans = 0; $ans < $answers_needed; $ans++) {
       $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);
       $ansnum++;
    }
       }
   
    # Qon 'number' implies each slot gives a digit that indexes the
    #    the bubbles filled or Qoff or a non number for unbubbled lines.
           #    and *? for double bubbles on a line.
    #    these answers are also stored as letters.
   
    } elsif ($$scantron_config{'Qon'} eq 'number') {
       if ($currentquest =~ /\?/
    || $currentquest =~ /\*/
    || (&occurence_count($currentquest, '\d') > 1)) {
    push(@{$record{'scantron.doubleerror'}},$questnum);
    for (my $ans = 0; $ans < $answers_needed; $ans++) {
       my $bubble = substr($currentquest, $ans, 1);
       if ($bubble =~ /\d/) {
    $record{"scantron.$ansnum.answer"} = $alphabet[$bubble];
       } else {
    $record{"scantron.$ansnum.answer"}=' ';
       }
       $ansnum++;
    }
   
       } elsif (!defined($currentquest)
        || (&occurence_count($currentquest,$$scantron_config{'Qoff'}) == length($currentquest)) 
        || (&occurence_count($currentquest, '\d') == 0)) {
    for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
       $record{"scantron.$ansnum.answer"}='';
       $ansnum++;
   
    }
    if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
       push(@{$record{"scantron.missingerror"}},$questnum);
       $ansnum += $answers_needed;
    }
   
       } else {
    $currentquest = &digits_to_letters($currentquest);
    for (my $ans =0; $ans < $answers_needed; $ans++) {
       $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);
       $ansnum++;
    }
       }
    } else {
   
       # Otherwise there's a positional notation;
       # each bubble line requires Qlength items, and there are filled in
       # bubbles for each case where there 'Qon' characters.
       #
   
       my @array=split($$scantron_config{'Qon'},$currentquest,-1);
   
       # If the split only  giveas us one element.. the full length of the
       # answser string, no bubbles are filled in:
   
       if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {
    for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
       $record{"scantron.$ansnum.answer"}='';
       $ansnum++;
   
    }
    if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
       push(@{$record{"scantron.missingerror"}},$questnum);
    }
   
    #  If the bubble is not the last position, there will be
    # 2 elements.  If it is the last position, there will be 1 element.
   
       } elsif (scalar(@array) le 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++;
    }
       }
       #  If there's more than one instance of a bubble character
       #  That's a double bubble; with positional notation we can
       #  record all the bubbles filled in as well as the 
       #  fact this response consists of multiple bubbles.
       #
       else {
    push(@{$record{'scantron.doubleerror'}},$questnum);
   
    my $first_answer = $ansnum;
    for (my $ans =0; $ans < $answers_needed; $ans++) {
       my $item = $first_answer+$ans;
       $record{"scantron.$item.answer"} = '';
    }
   
    my @ans=@array;
    my $i=0;
    my $increment = 0;
    while ($#ans) {
       $i+=length($ans[0]) + $increment;
       my $line   = int($i/$$scantron_config{'Qlength'} + $first_answer);
       my $bubble = $i%$$scantron_config{'Qlength'};
       $record{"scantron.$line.answer"}.=$alphabet[$bubble];
       shift(@ans);
       $increment = 1;
    }
    $ansnum += $answers_needed;
       }
    }
       }
       $record{'scantron.maxquest'}=$questnum;
       return \%record;
 }  }
   
 #--- Menu for grading a section or the whole class ---  =pod
 sub view_edit_entire_class_form {  
     my ($symb,$url)=@_;  =item scantron_add_delay
     my ($classlist,$sections,undef) = &getclasslist('all','0');  
     my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";     Adds an error message that occurred during the grading phase to a
     $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";     queue of messages to be shown after grading pass is complete
     $result.='&nbsp;<b>Grade Entire Section or Class</b></td></tr>'."\n";  
     $result.='<tr bgcolor=#ffffe6><td>'."\n";   Arguments:
     $result.='<form action="/adm/grades" method="post">'."\n".     $delayqueue  - arrary ref of hash ref of error messages
  '<input type="hidden" name="symb" value="'.$symb.'" />'."\n".     $scanline    - the scanline that caused the error
  '<input type="hidden" name="url" value="'.$url.'" />'."\n".     $errormesage - the error message
  '<input type="hidden" name="command" value="viewgrades" />'."\n";     $errorcode   - a numeric code for the error
     $result.='&nbsp;<b>Select section:</b> <select name="section">'."\n";  
     if (ref($sections)) {   Side Effects:
  foreach (sort (@$sections)) {     updates the $delayqueue to have a new hash ref of the error
     $result.= '<option>'.$_.'</option>'."\n";  
   =cut
   
   sub scantron_add_delay {
       my ($delayqueue,$scanline,$errormessage,$errorcode)=@_;
       push(@$delayqueue,
    {'line' => $scanline, 'emsg' => $errormessage,
     'ecode' => $errorcode }
    );
   }
   
   =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 {
       my ($scantron_record,$scan_data,$idmap,$line)=@_;
       my $scanID=$$scantron_record{'scantron.ID'};
       if ($scanID =~ /^\s*$/) {
     return &scan_data($scan_data,"$line.user");
       }
       foreach my $id (keys(%$idmap)) {
     if (lc($id) eq lc($scanID)) {
        return $$idmap{$id};
     }
       }
       return undef;
   }
   
   =pod
   
   =item scantron_filter
   
      Filter sub for lonnavmaps, filters out hidden resources if ignore
      hidden resources was selected
   
   =cut
   
   sub scantron_filter {
       my ($curres)=@_;
   
       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 0;
   }
   
   =pod
   
   =item scantron_process_corrections
   
      Gets correction information out of submitted form data and corrects
      the scanline
   
   =cut
   
   sub scantron_process_corrections {
       my ($r) = @_;
       my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
       my ($scanlines,$scan_data)=&scantron_getfile();
       my $classlist=&Apache::loncoursedata::get_classlist();
       my $which=$env{'form.scantron_line'};
       my $line=&scantron_get_line($scanlines,$scan_data,$which);
       my ($skip,$err,$errmsg);
       if ($env{'form.scantron_skip_record'}) {
    $skip=1;
       } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) {
    my $newstudent=$env{'form.scantron_username'}.':'.
       $env{'form.scantron_domain'};
    my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID];
    ($line,$err,$errmsg)=
       &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
        'ID',{'newid'=>$newid,
       'username'=>$env{'form.scantron_username'},
       'domain'=>$env{'form.scantron_domain'}});
       } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)CODE$/) {
    my $resolution=$env{'form.scantron_CODE_resolution'};
    my $newCODE;
    my %args;
    if      ($resolution eq 'use_unfound') {
       $newCODE='use_unfound';
    } elsif ($resolution eq 'use_found') {
       $newCODE=$env{'form.scantron_CODE_selectedvalue'};
    } elsif ($resolution eq 'use_typed') {
       $newCODE=$env{'form.scantron_CODE_newvalue'};
    } elsif ($resolution =~ /^use_closest_(\d+)/) {
       $newCODE=$env{"form.scantron_CODE_closest_$1"};
    }
    if ($env{'form.scantron_corrections'} eq 'duplicateCODE') {
       $args{'CODE_ignore_dup'}=1;
    }
    $args{'CODE'}=$newCODE;
    ($line,$err,$errmsg)=
       &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
        'CODE',\%args);
       } elsif ($env{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) {
    foreach my $question (split(',',$env{'form.scantron_questions'})) {
       ($line,$err,$errmsg)=
    &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,
    $which,'answer',
    { 'question'=>$question,
          'response'=>$env{"form.scantron_correct_Q_$question"}});
       if ($err) { last; }
    }
       }
       if ($err) {
    $r->print("<span class=\"LC_warning\">Unable to accept last correction, an error occurred :$errmsg:</span>");
       } else {
    &scantron_put_line($scanlines,$scan_data,$which,$line,$skip);
    &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 {
       my ($scanlines,$scan_data)=&scantron_getfile();
       &scan_data($scan_data,'remember_skipping',undef,1);
       &scantron_putfile(undef,$scan_data);
   }
   
   =pod
   
   =item start_skipping
   
      Marks a scanline to be skipped. 
   
   =cut
   
   sub start_skipping {
       my ($scan_data,$i)=@_;
       my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
       if ($env{'form.scantron_options_redo'} =~ /^redo_/) {
    $remembered{$i}=2;
       } else {
    $remembered{$i}=1;
       }
       &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 {
       my ($scanlines,$scan_data,$i)=@_;
       if ($env{'form.scantron_options_redo'} !~ /^redo_/) {
    # not redoing old skips
    if ($scanlines->{'skipped'}[$i]) { return 1; }
    return 0;
       }
       my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
   
       if (exists($remembered{$i}) && $remembered{$i} != 2 ) {
    return 0;
       }
       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 {
       my ($scanlines,$scan_data)=&scantron_getfile();
       my %to_remember;
       for (my $i=0;$i<=$scanlines->{'count'};$i++) {
    if ($scanlines->{'skipped'}[$i]) {
       $to_remember{$i}=1;
  }   }
     }      }
     $result.='<option selected="on">all</select>'."<br />\n";  
     $result.='&nbsp;<input type="button" onClick="submit();" value="Grade" /></form>'."\n";      &scan_data($scan_data,'remember_skipping',join(':',%to_remember));
     $result.='</td></tr></table>'."\n";      &scantron_putfile(undef,$scan_data);
     $result.='</td></tr></table>'."\n";  }
   
   =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 {
       my ($r,$result)=@_;
       if ($result ne 'ok' && $result ne 'not_found' ) {
    $r->print("An error occurred ($result) when trying to Remove the existing corrections.");
       }
   }
   
   =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 {
       my ($button_text)=@_;
       my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
       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>List of CODES to validate against:</b></td><td><tt>'.
       $env{'form.scantron_CODElist'}.'</tt></td></tr>';
       }
       return (<<STUFF);
   <p>
   <span class="LC_warning">Please double check the information
                    below before clicking on '$button_text'</span>
   </p>
   <table>
   <tr><td><b>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>
   $CODElist
   </table>
   <br />
   <p> If this information is correct, please click on '$button_text'.</p>
   <p> If something is incorrect, please click the 'Grading Menu' button to start over.</p>
   
   <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 {
       my ($r)=@_;
       my ($symb)=&get_symb($r);
       if (!$symb) {return '';}
       my $default_form_data=&defaultFormData($symb);
       $r->print(&scantron_form_start().$default_form_data);
       if ( $env{'form.selectpage'} eq '' ||
    $env{'form.scantron_selectfile'} eq '' ||
    $env{'form.scantron_format'} eq '' ) {
    $r->print("<p>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">You have not selected a Sequence to grade</span></p>');
    } 
    if ( $env{'form.scantron_selectfile'} eq '') {
       $r->print('<p><span class="LC_error">You have not selected a file that contains the student\'s response data.</span></p>');
    } 
    if ( $env{'form.scantron_format'} eq '') {
       $r->print('<p><span class="LC_error">You have not selected a the format of the student\'s response data.</span></p>');
    } 
       } else {
    my $warning=&scantron_warning_screen('Grading: Validate Records');
    $r->print(<<STUFF);
   $warning
   <input type="submit" name="submit" value="Grading: Validate Records" />
   <input type="hidden" name="command" value="scantron_validate" />
   STUFF
       }
       $r->print("</form><br />".&show_grading_menu_form($symb));
       return '';
   }
   
   =pod
   
   =item scantron_form_start
   
       html hidden input for remembering all selected grading options
   
   =cut
   
   sub scantron_form_start {
       my ($max_bubble)=@_;
       my $result= <<SCANTRONFORM;
   <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="scantron_format" value="$env{'form.scantron_format'}" />
     <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_CODElist" value="$env{'form.scantron_CODElist'}" />
     <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_ignore" value="$env{'form.scantron_options_ignore'}" />
     <input type="hidden" name="scantron_options_hidden" value="$env{'form.scantron_options_hidden'}" />
   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";
          $result .= $chunk;
          $line++;
      }
     return $result;      return $result;
 }  }
   
 #--- Menu to upload a csv scores ---  =pod
 sub upcsvScores_form {  
     my ($symb,$url) = @_;  =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 {
       my ($r) = @_;
       my ($symb)=&get_symb($r);
     if (!$symb) {return '';}      if (!$symb) {return '';}
     my $result = '<script type="text/javascript" language="javascript">'."\n".      my $default_form_data=&defaultFormData($symb);
  '  function checkUpload(formname) {'."\n".      
  '    if (formname.upfile.value == "") {'."\n".      # do the detection of only doing skipped records first befroe we delete
  '       alert("Please use the browse button to select a file from your local directory.");'."\n".      # them when doing the corrections reset
  '       return false;'."\n".      if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') {
  '    }'."\n".   &reset_skipping_status();
  '    formname.submit();'."\n".      }
  '  }'."\n".      if ($env{'form.scantron_options_redo'} eq 'redo_skipped') {
  '</script>'."\n";   &remember_current_skipped();
    $env{'form.scantron_options_redo'}='redo_skipped_ready';
       }
   
       if ($env{'form.scantron_options_ignore'} eq 'ignore_corrections') {
    &check_for_error($r,&scantron_remove_file('corrected'));
    &check_for_error($r,&scantron_remove_file('skipped'));
    &check_for_error($r,&scantron_remove_scan_data());
    $env{'form.scantron_options_ignore'}='done';
       }
   
       if ($env{'form.scantron_corrections'}) {
    &scantron_process_corrections($r);
       }
       $r->print("<p>Gathering necessary info.</p>");$r->rflush();
       #get the student pick code ready
       $r->print(&Apache::loncommon::studentbrowser_javascript());
       my $max_bubble=&scantron_get_maxbubble();
       my $result=&scantron_form_start($max_bubble).$default_form_data;
       $r->print($result);
       
       my @validate_phases=( 'sequence',
     'ID',
     'CODE',
     'doublebubble',
     'missingbubbles');
       if (!$env{'form.validatepass'}) {
    $env{'form.validatepass'} = 0;
       }
       my $currentphase=$env{'form.validatepass'};
   
   
       my $stop=0;
       while (!$stop && $currentphase < scalar(@validate_phases)) {
    $r->print("<p> Validating ".$validate_phases[$currentphase]."</p>");
    $r->rflush();
    my $which="scantron_validate_".$validate_phases[$currentphase];
    {
       no strict 'refs';
       ($stop,$currentphase)=&$which($r,$currentphase);
    }
       }
       if (!$stop) {
    my $warning=&scantron_warning_screen('Start Grading');
    $r->print(<<STUFF);
   Validation process complete.<br />
   $warning
   <input type="submit" name="submit" value="Start Grading" />
   <input type="hidden" name="command" value="scantron_process" />
   STUFF
   
     $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";      } else {
     $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";   $r->print('<input type="hidden" name="command" value="scantron_validate" />');
     $result.='&nbsp;<b>Specify a file containing the class scores for above resource</b></td></tr>'."\n";   $r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />");
     $result.='<tr bgcolor=#ffffe6><td>'."\n";      }
     my $upfile_select=&Apache::loncommon::upfile_select_html();      if ($stop) {
   $result.=<<ENDUPFORM;   if ($validate_phases[$currentphase] eq 'sequence') {
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">      $r->print('<input type="submit" name="submit" value="Ignore -> " />');
 <input type="hidden" name="symb" value="$symb" />      $r->print(' this error <br />');
 <input type="hidden" name="url" value="$url" />  
 <input type="hidden" name="command" value="csvuploadmap" />      $r->print(" <p>Or click the 'Grading Menu' button to start over.</p>");
 $upfile_select   } else {
 <br />&nbsp;<input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Grades" />      $r->print('<input type="submit" name="submit" value="Continue ->" />');
 </form>      $r->print(' using corrected info <br />');
 ENDUPFORM      $r->print("<input type='submit' value='Skip' name='scantron_skip_record' />");
     $result.='</td></tr></table>'."\n";      $r->print(" this scanline saving it for later.");
     $result.='</td></tr></table>'."\n";   }
       }
       $r->print(" </form><br />".&show_grading_menu_form($symb));
       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 {
       my ($which)=@_;
       my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       my $file='scantron_';
       if ($which eq 'corrected' || $which eq 'skipped') {
    $file.=$which.'_';
       } else {
    return 'refused';
       }
       $file.=$env{'form.scantron_selectfile'};
       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 {
       my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       my @keys=&Apache::lonnet::getkeys('nohist_scantrondata',$cdom,$cname);
       my @todelete;
       my $filename=$env{'form.scantron_selectfile'};
       foreach my $key (@keys) {
    if ($key=~/^\Q$filename\E_/) {
       if ($env{'form.scantron_options_redo'} eq 'redo_skipped_ready' &&
    $key=~/remember_skipping/) {
    next;
       }
       push(@todelete,$key);
    }
       }
       my $result;
       if (@todelete) {
    $result=&Apache::lonnet::del('nohist_scantrondata',\@todelete,$cdom,$cname);
       }
     return $result;      return $result;
 }  }
   
 #--- Handgrading problems ---  
 sub viewGradeaStu_form {  =pod
     my ($symb,$url,$response,$handgrade) = @_;  
     my ($classlist,$sections) = &getclasslist('all','0');  =item scantron_getfile
     my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";  
     $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";      Fetches the requested bubble sheet data file (all 3 versions), and
     $result.='&nbsp;<b>';      the scan_data hash
     if ($handgrade eq 'yes') {    
  $result.="View/Grade ";    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 {
       #FIXME really would prefer a scantron directory
       my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       my $lines;
       $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
          'scantron_orig_'.$env{'form.scantron_selectfile'});
       my %scanlines;
       $scanlines{'orig'}=[(split("\n",$lines,-1))];
       my $temp=$scanlines{'orig'};
       $scanlines{'count'}=$#$temp;
   
       $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
          'scantron_corrected_'.$env{'form.scantron_selectfile'});
       if ($lines eq '-1') {
    $scanlines{'corrected'}=[];
     } else {      } else {
  $result.="View ";   $scanlines{'corrected'}=[(split("\n",$lines,-1))];
     }      }
     $result.='an Individual Student\'s Submission</b></td></tr>'."\n";      $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
     $result.='<tr bgcolor=#ffffe6><td>'."\n";         'scantron_skipped_'.$env{'form.scantron_selectfile'});
     $result.='<form action="/adm/grades" method="post">'."\n".      if ($lines eq '-1') {
  '<input type="hidden" name="symb" value="'.$symb.'" />'."\n".   $scanlines{'skipped'}=[];
  '<input type="hidden" name="url" value="'.$url.'" />'."\n".      } else {
  '<input type="hidden" name="response" value="'.$response.'" />'."\n".   $scanlines{'skipped'}=[(split("\n",$lines,-1))];
  '<input type="hidden" name="handgrade" value="'.$handgrade.'" />'."\n".      }
  '<input type="hidden" name="showgrading" value="yes" />'."\n".      my @tmp=&Apache::lonnet::dump('nohist_scantrondata',$cdom,$cname);
  '<input type="hidden" name="command" value="submission" />'."\n";      if ($tmp[0] =~ /^(error:|no_such_host)/) { @tmp=(); }
       my %scan_data = @tmp;
       return (\%scanlines,\%scan_data);
   }
   
     $result.='&nbsp;<b>Select section:</b> <select name="section">'."\n";  =pod
     if (ref($sections)) {  
  foreach (sort (@$sections)) {$result.='<option>'.$_.'</option>'."\n";}  =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 {
       my ($contents,$filename)=@_;
       my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       $env{'form.sillywaytopassafilearound'}=$contents;
       &Apache::lonnet::finishuserfileupload($docuname,$docudom,'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 {
       my ($scanlines,$scan_data) = @_;
       #FIXME really would prefer a scantron directory
       my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       if ($scanlines) {
    my $prefix='scantron_';
   # no need to update orig, shouldn't change
   #   &lonnet_putfile(join("\n",@{$scanlines->{'orig'}}),$prefix.'orig_'.
   #    $env{'form.scantron_selectfile'});
    &lonnet_putfile(join("\n",@{$scanlines->{'corrected'}}),
    $prefix.'corrected_'.
    $env{'form.scantron_selectfile'});
    &lonnet_putfile(join("\n",@{$scanlines->{'skipped'}}),
    $prefix.'skipped_'.
    $env{'form.scantron_selectfile'});
     }      }
     $result.= '<option selected="on">all</select>'."\n";      &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname);
     $result.='&nbsp;&nbsp;<b>Display students who has: </b>'.  }
  '<input type="radio" name="submitonly" value="yes" checked> submitted'.  
  '<input type="radio" name="submitonly" value="all"> everybody <br />';  =pod
     if (ref($sections)) {  
  $result.='&nbsp;(Section "no" implies the students were not assigned a section.)<br />'   =item scantron_get_line
     if (grep /no/,@$sections);  
      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 {
       my ($scanlines,$scan_data,$i)=@_;
       if (&should_be_skipped($scanlines,$scan_data,$i)) { return undef; }
       #if ($scanlines->{'skipped'}[$i]) { return undef; }
       if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$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 {
       my ($scanlines,$scan_data)=@_;
       my $count=0;
       for (my $i=0;$i<=$scanlines->{'count'};$i++) {
    my $line=&scantron_get_line($scanlines,$scan_data,$i);
    if ($line=~/^[\s\cz]*$/) { next; }
    $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 {
       my ($scanlines,$scan_data,$i,$newline,$skip)=@_;
       if ($skip) {
    $scanlines->{'skipped'}[$i]=$newline;
    &start_skipping($scan_data,$i);
    return;
       }
       $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;
   }
   
     $result.='<br />&nbsp;<input type="button" onClick="submit();" value="';  =pod
     if ($handgrade eq 'yes') {  
  $result.="View/Grade";  =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();
       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);
   }
   
   =pod
   
   =item scantron_validate_ID
   
      Validates all scanlines in the selected file to not have any
      invalid or underspecified student IDs
   
   =cut
   
   sub scantron_validate_ID {
       my ($r,$currentphase) = @_;
       
       #get student info
       my $classlist=&Apache::loncoursedata::get_classlist();
       my %idmap=&username_to_idmap($classlist);
   
       #get scantron line setup
       my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
       my ($scanlines,$scan_data)=&scantron_getfile();
       
       &scantron_get_maxbubble(); # parse needs the bubble_lines.. array.
   
       my %found=('ids'=>{},'usernames'=>{});
       for (my $i=0;$i<=$scanlines->{'count'};$i++) {
    my $line=&scantron_get_line($scanlines,$scan_data,$i);
    if ($line=~/^[\s\cz]*$/) { next; }
    my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
    $scan_data);
    my $id=$$scan_record{'scantron.ID'};
    my $found;
    foreach my $checkid (keys(%idmap)) {
       if (lc($checkid) eq lc($id)) { $found=$checkid;last; }
    }
    if ($found) {
       my $username=$idmap{$found};
       if ($found{'ids'}{$found}) {
    &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
    $line,'duplicateID',$found);
    return(1,$currentphase);
       } elsif ($found{'usernames'}{$username}) {
    &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
    $line,'duplicateID',$username);
    return(1,$currentphase);
       }
       #FIXME store away line we previously saw the ID on to use above
       $found{'ids'}{$found}++;
       $found{'usernames'}{$username}++;
    } else {
       if ($id =~ /^\s*$/) {
    my $username=&scan_data($scan_data,"$i.user");
    if (defined($username) && $found{'usernames'}{$username}) {
       &scantron_get_correction($r,$i,$scan_record,
        \%scantron_config,
        $line,'duplicateID',$username);
       return(1,$currentphase);
    } elsif (!defined($username)) {
       &scantron_get_correction($r,$i,$scan_record,
        \%scantron_config,
        $line,'incorrectID');
       return(1,$currentphase);
    }
    $found{'usernames'}{$username}++;
       } else {
    &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
    $line,'incorrectID');
    return(1,$currentphase);
       }
    }
       }
   
       return (0,$currentphase+1);
   }
   
   =pod
   
   =item scantron_get_correction
   
      Builds the interface screen to interact with the operator to fix a
      specific error condition in a specific scanline
   
    Arguments:
       $r           - Apache request object
       $i           - number of the current scanline
       $scan_record - hash ref as returned from &scantron_parse_scanline()
       $scan_config - hash ref as returned from &get_scantron_config()
       $line        - full contents of the current scanline
       $error       - error condition, valid values are
                      'incorrectCODE', 'duplicateCODE',
                      'doublebubble', 'missingbubble',
                      'duplicateID', 'incorrectID'
       $arg         - extra information needed
          For errors:
            - duplicateID   - paper number that this studentID was seen before on
            - duplicateCODE - array ref of the paper numbers this CODE was
                              seen on before
            - incorrectCODE - current incorrect CODE 
            - doublebubble  - array ref of the bubble lines that have double
                              bubble errors
            - missingbubble - array ref of the bubble lines that have missing
                              bubble errors
   
   =cut
   
   sub scantron_get_correction {
       my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;
   
   #FIXME in the case of a duplicated ID the previous line, probably need
   #to show both the current line and the previous one and allow skipping
   #the previous one or the current one
   
       $r->print("<p><b>An error was detected ($error)</b>");
       if ( $$scan_record{'scantron.PaperID'} =~ /\S/) {
    $r->print(" for PaperID <tt>".
     $$scan_record{'scantron.PaperID'}."</tt> \n");
       } else {
    $r->print(" in scanline $i <pre>".
     $line."</pre> \n");
       }
       my $message="<p>The ID on the form is  <tt>".
    $$scan_record{'scantron.ID'}."</tt><br />\n".
    "The name on the paper is ".
    $$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_line" value="'.$i.'" />'."\n");
       if ($error =~ /ID$/) {
    if ($error eq 'incorrectID') {
       $r->print("The encoded ID is not in the classlist</p>\n");
    } elsif ($error eq 'duplicateID') {
       $r->print("The encoded ID has also been used by a previous paper $arg</p>\n");
    }
    $r->print($message);
    $r->print("<p>How should I handle this? <br /> \n");
    $r->print("\n<ul><li> ");
    #FIXME it would be nice if this sent back the user ID and
    #could do partial userID matches
    $r->print(&Apache::loncommon::selectstudent_link('scantronupload',
          'scantron_username','scantron_domain'));
    $r->print(": <input type='text' name='scantron_username' value='' />");
    $r->print("\n@".
    &Apache::loncommon::select_dom_form($env{'request.role.domain'},'scantron_domain'));
   
    $r->print('</li>');
       } elsif ($error =~ /CODE$/) {
    if ($error eq 'incorrectCODE') {
       $r->print("</p><p>The encoded CODE is not in the list of possible CODEs</p>\n");
    } 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>The CODE on the form is  <tt>'".
     $$scan_record{'scantron.CODE'}."'</tt><br />\n");
    $r->print($message);
    $r->print("<p>How should I handle this? <br /> \n");
    $r->print("\n<br /> ");
    my $i=0;
    if ($error eq 'incorrectCODE' 
       && $$scan_record{'scantron.CODE'}=~/\S/ ) {
       my ($max,$closest)=&scantron_get_closely_matching_CODEs($arg,$$scan_record{'scantron.CODE'});
       if ($closest > 0) {
    foreach my $testcode (@{$closest}) {
       my $checked='';
       if (!$i) { $checked=' checked="checked" '; }
       $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_closest_$i' $checked /> Use the similar CODE <b><tt>".$testcode."</tt></b> instead.</label><input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />");
       $r->print("\n<br />");
       $i++;
    }
       }
    }
    if ($$scan_record{'scantron.CODE'}=~/\S/ ) {
       my $checked; if (!$i) { $checked=' checked="checked" '; }
       $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_unfound' $checked /> Use the CODE <b><tt>".$$scan_record{'scantron.CODE'}."</tt></b> that is was on the paper, ignoring the error.</label>");
       $r->print("\n<br />");
    }
   
    $r->print(<<ENDSCRIPT);
   <script type="text/javascript">
   function change_radio(field) {
       var slct=document.scantronupload.scantron_CODE_resolution;
       var i;
       for (i=0;i<slct.length;i++) {
           if (slct[i].value==field) { slct[i].checked=true; }
       }
   }
   </script>
   ENDSCRIPT
    my $href="/adm/pickcode?".
      "form=".&escape("scantronupload").
      "&scantron_format=".&escape($env{'form.scantron_format'}).
      "&scantron_CODElist=".&escape($env{'form.scantron_CODElist'}).
      "&curCODE=".&escape($$scan_record{'scantron.CODE'}).
      "&scantron_selectfile=".&escape($env{'form.scantron_selectfile'});
    if ($env{'form.scantron_CODElist'} =~ /\S/) { 
       $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_found' /> <a target='_blank' href='$href'>Select</a> a CODE from the list of all CODEs and use it.</label> Selected CODE is <input readonly='true' type='text' size='8' name='scantron_CODE_selectedvalue' onfocus=\"javascript:change_radio('use_found')\" onchange=\"javascript:change_radio('use_found')\" />");
       $r->print("\n<br />");
    }
    $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_typed' /> Use </label><input type='text' size='8' name='scantron_CODE_newvalue' onfocus=\"javascript:change_radio('use_typed')\" onkeypress=\"javascript:change_radio('use_typed')\" /> as the CODE.");
    $r->print("\n<br /><br />");
       } elsif ($error eq 'doublebubble') {
    $r->print("<p>There have been multiple bubbles scanned for a some question(s)</p>\n");
    $r->print('<input type="hidden" name="scantron_questions" value="'.
     join(',',@{$arg}).'" />');
    $r->print($message);
    $r->print("<p>Please indicate which bubble should be used for grading</p>");
    foreach my $question (@{$arg}) {
       my $selected  = &get_response_bubbles($scan_record, $question);
       my @select_array = split(/:/,$selected);
       &scantron_bubble_selector($r,$scan_config,$question,
         @select_array);
    }
       } elsif ($error eq 'missingbubble') {
    $r->print("<p>There have been <b>no</b> bubbles scanned for some question(s)</p>\n");
    $r->print($message);
    $r->print("<p>Please indicate which bubble should be used for grading</p>");
    $r->print("Some questions have no scanned bubbles\n");
    $r->print('<input type="hidden" name="scantron_questions" value="'.
     join(',',@{$arg}).'" />');
    foreach my $question (@{$arg}) {
       my $selected = &get_response_bubbles($scan_record, $question);
       my @select_array = split(/:/,$selected); # ought to be an array of empties.
       &scantron_bubble_selector($r,$scan_config,$question, @select_array);
    }
       } else {
    $r->print("\n<ul>");
       }
       $r->print("\n</li></ul>");
   
   }
   
   =pod
   
   =item scantron_bubble_selector
     
      Generates the html radiobuttons to correct a single bubble line
      possibly showing the existing the selected bubbles if known
   
    Arguments:
       $r           - Apache request object
       $scan_config - hash from &get_scantron_config()
       $quest       - number of the bubble line to make a corrector for
       @lines       - array of answer lines.
   
   =cut
   
   sub scantron_bubble_selector {
       my ($r,$scan_config,$quest,@lines)=@_;
       my $max=$$scan_config{'Qlength'};
   
   
       my $scmode=$$scan_config{'Qon'};
   
       my $bubble_length = scalar(@lines);
   
   
       if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }     
   
       my $response = $quest-1;
       my $lines = $bubble_lines_per_response{$response};
   
       my $total_lines = $lines*2;
       my @alphabet=('A'..'Z');
   
       $r->print("<table border='1'><tr><td rowspan='".$total_lines."'>$quest</td>");
   
       for (my $l = 0; $l < $lines; $l++) {
    if ($l != 0) {
       $r->print('<tr>');
    }
    my @selected = split(//,$lines[$l]);
    for (my $i=0;$i<$max;$i++) {
       $r->print("\n".'<td align="center">');
       if ($selected[0] eq $alphabet[$i]) { 
    $r->print('X'); 
    shift(@selected) ;
       } else { 
    $r->print('&nbsp;'); 
       }
       $r->print('</td>');
       
    }
   
    if ($l == 0) {
       my $lspan = $total_lines * 2;   #  2 table rows per bubble line.
   
       $r->print('<td rowspan='.$lspan.'><label><input type="radio" name="scantron_correct_Q_'.
         $quest.'" value="none" /> No bubble </label></td>');
   
    }
   
    $r->print('</tr><tr>');
   
    # FIXME: This may have to be a bit more clever for
    #        multiline questions (different values e.g..).
   
    for (my $i=0;$i<$max;$i++) {
       my $value = "$l:$i"; # Relative bubble line #: Bubble in line.
       $r->print("\n".
         '<td><label><input type="radio" name="scantron_correct_Q_'.
         $quest.'" value="'.$value.'" />'.$alphabet[$i]."</label></td>");
    }
    $r->print('</tr>');
   
       
       }
       $r->print('</table>');
   }
   
   =pod
   
   =item num_matches
   
      Counts the number of characters that are the same between the two arguments.
   
    Arguments:
      $orig - CODE from the scanline
      $code - CODE to match against
   
    Returns:
      $count - integer count of the number of same characters between the
               two arguments
   
   =cut
   
   sub num_matches {
       my ($orig,$code) = @_;
       my @code=split(//,$code);
       my @orig=split(//,$orig);
       my $same=0;
       for (my $i=0;$i<scalar(@code);$i++) {
    if ($code[$i] eq $orig[$i]) { $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 {
       my ($allcodes,$CODE)=@_;
       my @CODEs;
       foreach my $testcode (sort(keys(%{$allcodes}))) {
    push(@{$CODEs[&num_matches($CODE,$testcode)]},$testcode);
       }
   
       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 {
       my ($old_name, $cdom, $cnum) = @_;
       if (!$old_name) {
    $old_name=$env{'form.scantron_CODElist'};
       }
       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 {      } else {
  $result.="View";   %allcodes=map {(&Apache::lonprintout::num_to_letters($_),1)} split(',',$result{$old_name});
       }
       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 {
       my ($r,$currentphase) = @_;
       my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
       if ($scantron_config{'CODElocation'} &&
    $scantron_config{'CODEstart'} &&
    $scantron_config{'CODElength'}) {
    if (!defined($env{'form.scantron_CODElist'})) {
       &FIXME_blow_up()
    }
       } else {
    return (0,$currentphase+1);
       }
       
       my %usedCODEs;
   
       my %allcodes=&get_codes();
   
       &scantron_get_maxbubble(); # parse needs the lines per response array.
   
       my ($scanlines,$scan_data)=&scantron_getfile();
       for (my $i=0;$i<=$scanlines->{'count'};$i++) {
    my $line=&scantron_get_line($scanlines,$scan_data,$i);
    if ($line=~/^[\s\cz]*$/) { next; }
    my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
    $scan_data);
    my $CODE=$$scan_record{'scantron.CODE'};
    my $error=0;
    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_config,
        $line,'incorrectCODE',\%allcodes);
       return(1,$currentphase);
    }
    if (exists($usedCODEs{$CODE}) 
       && $env{'form.scantron_CODEunique'} eq 'yes'
       && !$$scan_record{'scantron.CODE_ignore_dup'}) {
       &scantron_get_correction($r,$i,$scan_record,
        \%scantron_config,
        $line,'duplicateCODE',$usedCODEs{$CODE});
       return(1,$currentphase);
    }
    push (@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});
       }
       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 {
       my ($r,$currentphase) = @_;
       #get student info
       my $classlist=&Apache::loncoursedata::get_classlist();
       my %idmap=&username_to_idmap($classlist);
   
       #get scantron line setup
       my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
       my ($scanlines,$scan_data)=&scantron_getfile();
   
       &scantron_get_maxbubble(); # parse needs the bubble line array.
   
       for (my $i=0;$i<=$scanlines->{'count'};$i++) {
    my $line=&scantron_get_line($scanlines,$scan_data,$i);
    if ($line=~/^[\s\cz]*$/) { next; }
    my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
    $scan_data);
    if (!defined($$scan_record{'scantron.doubleerror'})) { next; }
    &scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line,
    'doublebubble',
    $$scan_record{'scantron.doubleerror'});
       return (1,$currentphase);
       }
       return (0,$currentphase+1);
   }
   
   =pod
   
   =item scantron_get_maxbubble
   
      Returns the maximum number of bubble lines that are expected to
      occur. Does this by walking the selected sequence rendering the
      resource and then checking &Apache::lonxml::get_problem_counter()
      for what the current value of the problem counter is.
   
      Caches the results to $env{'form.scantron_maxbubble'},
      $env{'form.scantron.bubble_lines.n'} and 
      $env{'form.scantron.first_bubble_line.n'}
      which are the total number of bubble, lines, the number of bubble
      lines for reponse n and number of the first bubble line for response n.
   
   =cut
   
   sub scantron_get_maxbubble {    
       if (defined($env{'form.scantron_maxbubble'}) &&
    $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 $map=$navmap->getResourceByUrl($sequence);
       my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
   
       &Apache::lonxml::clear_problem_counter();
   
       my $uname       = $env{'form.student'};
       my $udom        = $env{'form.userdom'};
       my $cid         = $env{'request.course.id'};
       my $total_lines = 0;
       %bubble_lines_per_response = ();
       %first_bubble_line         = ();
   
     
       my $response_number = 0;
       my $bubble_line     = 0;
       foreach my $resource (@resources) {
    my $symb = $resource->symb();
    &Apache::lonxml::clear_bubble_lines_for_part();
    my $result=&Apache::lonnet::ssi($resource->src(),
    ('symb' => $resource->symb()),
    ('grade_target' => 'analyze'),
    ('grade_courseid' => $cid),
    ('grade_domain' => $udom),
    ('grade_username' => $uname));
    my (undef, $an) =
       split(/_HASH_REF__/,$result, 2);
   
    my %analysis = &Apache::lonnet::str2hash($an);
   
   
   
    foreach my $part_id (@{$analysis{'parts'}}) {
   
   
       my $lines = $analysis{"$part_id.bubble_lines"};;
   
       # TODO - make this a persistent hash not an array.
   
   
       $first_bubble_line{$response_number}           = $bubble_line;
       $bubble_lines_per_response{$response_number}   = $lines;
       $response_number++;
   
       $bubble_line +=  $lines;
       $total_lines +=  $lines;
    }
   
     }      }
     $result.='" />'."\n".'</form>'."\n";      &Apache::lonnet::delenv('scantron\.');
     $result.='</td></tr></table>'."\n";  
     $result.='</td></tr></table>'."\n";      &save_bubble_lines();
       $env{'form.scantron_maxbubble'} =
    $total_lines;
       return $env{'form.scantron_maxbubble'};
   }
   
   =pod
   
   =item scantron_validate_missingbubbles
   
      Validates all scanlines in the selected file to not have any
       answers that don't have bubbles that have not been verified
       to be bubble free.
   
   =cut
   
   sub scantron_validate_missingbubbles {
       my ($r,$currentphase) = @_;
       #get student info
       my $classlist=&Apache::loncoursedata::get_classlist();
       my %idmap=&username_to_idmap($classlist);
   
       #get scantron line setup
       my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
       my ($scanlines,$scan_data)=&scantron_getfile();
       my $max_bubble=&scantron_get_maxbubble();
       if (!$max_bubble) { $max_bubble=2**31; }
       for (my $i=0;$i<=$scanlines->{'count'};$i++) {
    my $line=&scantron_get_line($scanlines,$scan_data,$i);
    if ($line=~/^[\s\cz]*$/) { next; }
    my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
    $scan_data);
    if (!defined($$scan_record{'scantron.missingerror'})) { next; }
    my @to_correct;
   
    # Probably here's where the error is...
   
    foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {
       if ($missing > $max_bubble) { next; }
       push(@to_correct,$missing);
    }
    if (@to_correct) {
       &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
        $line,'missingbubble',\@to_correct);
       return (1,$currentphase);
    }
   
       }
       return (0,$currentphase+1);
   }
   
   =pod
   
   =item scantron_process_students
   
      Routine that does the actual grading of the bubble sheet information.
   
      The parsed scanline hash is added to %env 
   
      Then foreach unskipped scanline it does an &Apache::lonnet::ssi()
      foreach resource , with the form data of
   
    'submitted'     =>'scantron' 
    'grade_target'  =>'grade',
    'grade_username'=> username of student
    'grade_domain'  => domain of student
    'grade_courseid'=> of course
    'grade_symb'    => symb of resource to grade
   
       This triggers a grading pass. The problem grading code takes care
       of converting the bubbled letter information (now in %env) into a
       valid submission.
   
   =cut
   
   sub scantron_process_students {
       my ($r) = @_;
       my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
       my ($symb)=&get_symb($r);
       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 $classlist=&Apache::loncoursedata::get_classlist();
       my %idmap=&username_to_idmap($classlist);
       my $navmap=Apache::lonnavmaps::navmap->new();
       my $map=$navmap->getResourceByUrl($sequence);
       my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
   #    $r->print("geto ".scalar(@resources)."<br />");
       my $result= <<SCANTRONFORM;
   <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
     <input type="hidden" name="command" value="scantron_configphase" />
     $default_form_data
   SCANTRONFORM
       $r->print($result);
   
       my @delayqueue;
       my %completedstudents;
       
       my $count=&get_todo_count($scanlines,$scan_data);
       my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron Status',
        'Scantron Progress',$count,
       'inline',undef,'scantronupload');
       &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
     'Processing first student');
       my $start=&Time::HiRes::time();
       my $i=-1;
       my ($uname,$udom,$started);
   
       &scantron_get_maxbubble(); # Need the bubble lines array to parse.
   
       while ($i<$scanlines->{'count'}) {
     ($uname,$udom)=('','');
     $i++;
     my $line=&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=&scantron_parse_scanline($line,$i,\%scantron_config,
     $scan_data);
     unless ($uname=&scantron_find_student($scan_record,$scan_data,
          \%idmap,$i)) {
        &scantron_add_delay(\@delayqueue,$line,
     'Unable to find a student that matches',1);
        next;
     }
     if (exists $completedstudents{$uname}) {
        &scantron_add_delay(\@delayqueue,$line,
     'Student '.$uname.' has multiple sheets',2);
        next;
     }
     ($uname,$udom)=split(/:/,$uname);
   
    &Apache::lonxml::clear_problem_counter();
     &Apache::lonnet::appenv(%$scan_record);
   
    if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
       &scantron_putfile($scanlines,$scan_data);
    }
   
    my $i=0;
    foreach my $resource (@resources) {
       $i++;
       my %form=('submitted'     =>'scantron',
         'grade_target'  =>'grade',
         'grade_username'=>$uname,
         'grade_domain'  =>$udom,
         'grade_courseid'=>$env{'request.course.id'},
         'grade_symb'    =>$resource->symb());
       if (exists($scan_record->{'scantron.CODE'})
    && 
    &Apache::lonnet::validCODE($scan_record->{'scantron.CODE'})) {
    $form{'CODE'}=$scan_record->{'scantron.CODE'};
       } else {
    $form{'CODE'}='';
       }
       my $result=&Apache::lonnet::ssi($resource->src(),%form);
       if ($result ne '') {
       }
       if (&Apache::loncommon::connection_aborted($r)) { last; }
    }
    $completedstudents{$uname}={'line'=>$line};
    if (&Apache::loncommon::connection_aborted($r)) { last; }
       } continue {
    &Apache::lonxml::clear_problem_counter();
    &Apache::lonnet::delenv('scantron\.');
       }
       &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
   #    my $lasttime = &Time::HiRes::time()-$start;
   #    $r->print("<p>took $lasttime</p>");
   
       $r->print("</form>");
       $r->print(&show_grading_menu_form($symb));
       return '';
   }
   
   =pod
   
   =item scantron_upload_scantron_data
   
       Creates the screen for adding a new bubble sheet data file to a course.
   
   =cut
   
   sub scantron_upload_scantron_data {
       my ($r)=@_;
       $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}));
       my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
     'domainid',
     'coursename');
       my $domsel=&Apache::loncommon::select_dom_form($env{'request.role.domain'},
      'domainid');
       my $default_form_data=&defaultFormData(&get_symb($r,1));
       $r->print(<<UPLOAD);
   <script type="text/javascript" language="javascript">
       function checkUpload(formname) {
    if (formname.upfile.value == "") {
       alert("Please use the browse button to select a file from your local directory.");
       return false;
    }
    formname.submit();
       }
   </script>
   
   <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'>
   $default_form_data
   <table>
   <tr><td>$select_link </td></tr>
   <tr><td>Course ID:   </td><td><input name='courseid' type='text' />  </td></tr>
   <tr><td>Course Name: </td><td><input name='coursename' type='text' /></td></tr>
   <tr><td>Domain:      </td><td>$domsel                                </td></tr>
   <tr><td>File to upload:</td><td><input type="file" name="upfile" size="50" /></td></tr>
   </table>
   <input name='command' value='scantronupload_save' type='hidden' />
   <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" />
   </form>
   UPLOAD
       return '';
   }
   
   =pod
   
   =item scantron_upload_scantron_data_save
   
      Adds a provided bubble information data file to the course if user
      has the correct privileges to do so.  
   
   =cut
   
   sub scantron_upload_scantron_data_save {
       my($r)=@_;
       my ($symb)=&get_symb($r,1);
       my $doanotherupload=
    '<br /><form action="/adm/grades" method="post">'."\n".
    '<input type="hidden" name="command" value="scantronupload" />'."\n".
    '<input type="submit" name="submit" value="Do Another Upload" />'."\n".
    '</form>'."\n";
       if (!&Apache::lonnet::allowed('usc',$env{'form.domainid'}) &&
    !&Apache::lonnet::allowed('usc',
       $env{'form.domainid'}.'_'.$env{'form.courseid'})) {
    $r->print("You are not allowed to upload Scantron data to the requested course.<br />");
    if ($symb) {
       $r->print(&show_grading_menu_form($symb));
    } else {
       $r->print($doanotherupload);
    }
    return '';
       }
       my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
       $r->print("Doing upload to ".$coursedata{'description'}." <br />");
       my $fname=$env{'form.upfile.filename'};
       #FIXME
       #copied from lonnet::userfileupload()
       #make that function able to target a specified course
       # Replace Windows backslashes by forward slashes
       $fname=~s/\\/\//g;
       # Get rid of everything but the actual filename
       $fname=~s/^.*\/([^\/]+)$/$1/;
       # Replace spaces by underscores
       $fname=~s/\s+/\_/g;
       # Replace all other weird characters by nothing
       $fname=~s/[^\w\.\-]//g;
       # See if there is anything left
       unless ($fname) { return 'error: no uploaded file'; }
       my $uploadedfile=$fname;
       $fname='scantron_orig_'.$fname;
       if (length($env{'form.upfile'}) < 2) {
    $r->print("<span class=\"LC_error\">Error:</span> The file you attempted to upload, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>, contained no information. Please check that you entered the correct filename.");
       } else {
    my $result=&Apache::lonnet::finishuserfileupload($env{'form.courseid'},$env{'form.domainid'},'upfile',$fname);
    if ($result =~ m|^/uploaded/|) {
       $r->print("<span class=\"LC_success\">Success:</span> Successfully uploaded ".(length($env{'form.upfile'})-1)." bytes of data into location <tt>".$result."</tt>");
    } else {
       $r->print("<span class=\"LC_error\">Error:</span> An error (".$result.") occurred when attempting to upload the file, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>");
    }
       }
       if ($symb) {
    $r->print(&scantron_selectphase($r,$uploadedfile));
       } else {
    $r->print($doanotherupload);
       }
       return '';
   }
   
   =pod
   
   =item valid_file
   
      Validates that the requested bubble data file exists in the course.
   
   =cut
   
   sub valid_file {
       my ($requested_file)=@_;
       foreach my $filename (sort(&scantron_filenames())) {
    if ($requested_file eq $filename) { return 1; }
       }
       return 0;
   }
   
   =pod
   
   =item scantron_download_scantron_data
   
      Shows a list of the three internal files (original, corrected,
      skipped) for a specific bubble sheet data file that exists in the
      course.
   
   =cut
   
   sub scantron_download_scantron_data {
       my ($r)=@_;
       my $default_form_data=&defaultFormData(&get_symb($r,1));
       my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       my $file=$env{'form.scantron_selectfile'};
       if (! &valid_file($file)) {
    $r->print(<<ERROR);
    <p>
       The requested file name was invalid.
           </p>
   ERROR
    $r->print(&show_grading_menu_form(&get_symb($r,1)));
    return;
       }
       my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file;
       my $corrected='/uploaded/'.$cdom.'/'.$cname.'/scantron_corrected_'.$file;
       my $skipped='/uploaded/'.$cdom.'/'.$cname.'/scantron_skipped_'.$file;
       &Apache::lonnet::allowuploaded('/adm/grades',$orig);
       &Apache::lonnet::allowuploaded('/adm/grades',$corrected);
       &Apache::lonnet::allowuploaded('/adm/grades',$skipped);
       $r->print(<<DOWNLOAD);
       <p>
    <a href="$orig">Original</a> file as uploaded by the scantron office.
       </p>
       <p>
    <a href="$corrected">Corrections</a>, a file of corrected records that were used in grading.
       </p>
       <p>
    <a href="$skipped">Skipped</a>, a file of records that were skipped.
       </p>
   DOWNLOAD
       $r->print(&show_grading_menu_form(&get_symb($r,1)));
       return '';
   }
   
   =pod
   
   =back
   
   =cut
   
   #-------- end of section for handling grading scantron forms -------
   #
   #-------------------------------------------------------------------
   
   #-------------------------- Menu interface -------------------------
   #
   #--- Show a Grading Menu button - Calls the next routine ---
   sub show_grading_menu_form {
       my ($symb)=@_;
       my $result.='<br /><form action="/adm/grades" method="post">'."\n".
    '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
    '<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
    '<input type="hidden" name="command" value="gradingmenu" />'."\n".
    '<input type="submit" name="submit" value="'.&mt('Grading Menu').'" />'."\n".
    '</form>'."\n";
     return $result;      return $result;
 }  }
   
 #--- Form to input a receipt number ---  # -- Retrieve choices for grading form
 sub verifyReceipt_form {  sub savedState {
     my ($symb,$url) = @_;      my %savedState = ();
     my $result = '<script type="text/javascript" language="javascript">'."\n".      if ($env{'form.saveState'}) {
  '  function checkEntry(formname) {'."\n".   foreach (split(/:/,$env{'form.saveState'})) {
  '    var receipt = formname.receipt.value;'."\n".      my ($key,$value) = split(/=/,$_,2);
  '    if (isNaN(receipt) || receipt == "") {'."\n".      $savedState{$key} = $value;
  '       alert("Please enter a receipt number given by a student in the box.");'."\n".   }
  '       return false;'."\n".      }
  '    }'."\n".      return \%savedState;
  '    formname.submit();'."\n".  }
  '  }'."\n".  
  '</script>'."\n";  sub grading_menu {
       my ($request) = @_;
       my ($symb)=&get_symb($request);
       if (!$symb) {return '';}
       my $probTitle = &Apache::lonnet::gettitle($symb);
       my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
   
       $request->print($table);
       my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb),
                     'handgrade'=>$hdgrade,
                     'probTitle'=>$probTitle,
                     'command'=>'submit_options',
                     'saveState'=>"",
                     'gradingMenu'=>1,
                     'showgrading'=>"yes");
       my $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       my @menu = ({ url => $url,
                        name => &mt('Manual Grading/View Submissions'),
                        short_description => 
       &mt('Start the process of hand grading submissions.'),
                    });
       $fields{'command'} = 'csvform';
       $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       push (@menu, { url => $url,
                      name => &mt('Upload Scores'),
                      short_description => 
               &mt('Specify a file containing the class scores for current resource.')});
       $fields{'command'} = 'processclicker';
       $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       push (@menu, { url => $url,
                      name => &mt('Process Clicker'),
                      short_description => 
               &mt('Specify a file containing the clicker information for this resource.')});
       $fields{'command'} = 'scantron_selectphase';
       $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       push (@menu, { url => $url,
                      name => &mt('Grade/Manage Scantron Forms'),
                      short_description => 
               &mt('')});
       $fields{'command'} = 'verify';
       $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
       push (@menu, { url => "",
                      name => &mt('Verify Receipt'),
                      short_description => 
               &mt('')});
       #
       # Create the menu
       my $Str;
       # $Str .= '<h2>'.&mt('Please select a grading task').'</h2>';
       $Str .= '<form method="post" action="" name="gradingMenu">';
       $Str .= '<input type="hidden" name="command" value="" />'.
       '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
    '<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
    '<input type="hidden" name="probTitle"   value="'.$probTitle.'" />'."\n".
    '<input type="hidden" name="saveState"   value="" />'."\n".
    '<input type="hidden" name="gradingMenu" value="1" />'."\n".
    '<input type="hidden" name="showgrading" value="yes" />'."\n";
   
       foreach my $menudata (@menu) {
           if ($menudata->{'name'} ne &mt('Verify Receipt')) {
               $Str .='    <h3><a '.
                   $menudata->{'jscript'}.
                   ' href="'.
                   $menudata->{'url'}.'" >'.
                   $menudata->{'name'}."</a></h3>\n";
           } else {
               $Str .='    <h3><input type="button" value="'.&mt('Verify Receipt').'" '.
                   $menudata->{'jscript'}.
                   ' onClick="javascript:checkChoice(document.forms.gradingMenu,\'5\',\'verify\')" '.
                   ' /></h3>';
               $Str .= ('&nbsp;'x8).
    &mt(' receipt: [_1]',
       &Apache::lonnet::recprefix($env{'request.course.id'}).
                       '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />');
           }
           $Str .= '    '.('&nbsp;'x8).$menudata->{'short_description'}.
               "\n";
       }
       $Str .="</form>\n";
       $request->print(<<GRADINGMENUJS);
   <script type="text/javascript" language="javascript">
       function checkChoice(formname,val,cmdx) {
    if (val <= 2) {
       var cmd = radioSelection(formname.radioChoice);
       var cmdsave = cmd;
    } else {
       cmd = cmdx;
       cmdsave = 'submission';
    }
    formname.command.value = cmd;
    if (val < 5) formname.submit();
    if (val == 5) {
       if (!checkReceiptNo(formname,'notOK')) { 
           return false;
       } else {
           formname.submit();
       }
    }
       }
   
     my $hostver=unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'});      function checkReceiptNo(formname,nospace) {
    var receiptNo = formname.receipt.value;
    var checkOpt = false;
    if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
    if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
    if (checkOpt) {
       alert("Please enter a receipt number given by a student in the receipt box.");
       formname.receipt.value = "";
       formname.receipt.focus();
       return false;
    }
    return true;
       }
   </script>
   GRADINGMENUJS
       &commonJSfunctions($request);
       return $Str;    
   }
   
   
   #--- Displays the submissions first page -------
   sub submit_options {
       my ($request) = @_;
       my ($symb)=&get_symb($request);
       if (!$symb) {return '';}
       my $probTitle = &Apache::lonnet::gettitle($symb);
   
     $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";      $request->print(<<GRADINGMENUJS);
     $result.='<table width=100% border=0><tr><td bgcolor=#e6ffff>'."\n";  <script type="text/javascript" language="javascript">
     $result.='&nbsp;<b>Verify a Submission Receipt Issued by this Server</td></tr>'."\n";      function checkChoice(formname,val,cmdx) {
    if (val <= 2) {
       var cmd = radioSelection(formname.radioChoice);
       var cmdsave = cmd;
    } else {
       cmd = cmdx;
       cmdsave = 'submission';
    }
    formname.command.value = cmd;
    formname.saveState.value = "saveCmd="+cmdsave+":saveSec="+pullDownSelection(formname.section)+
       ":saveSub="+pullDownSelection(formname.submitonly)+":saveStatus="+pullDownSelection(formname.Status);
    if (val < 5) formname.submit();
    if (val == 5) {
       if (!checkReceiptNo(formname,'notOK')) { return false;}
       formname.submit();
    }
    if (val < 7) formname.submit();
       }
   
       function checkReceiptNo(formname,nospace) {
    var receiptNo = formname.receipt.value;
    var checkOpt = false;
    if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
    if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
    if (checkOpt) {
       alert("Please enter a receipt number given by a student in the receipt box.");
       formname.receipt.value = "";
       formname.receipt.focus();
       return false;
    }
    return true;
       }
   </script>
   GRADINGMENUJS
       &commonJSfunctions($request);
       my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
       my $result;
       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".
    '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
    '<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
    '<input type="hidden" name="probTitle"   value="'.$probTitle.'" />'."\n".
    '<input type="hidden" name="command"     value="" />'."\n".
    '<input type="hidden" name="saveState"   value="" />'."\n".
    '<input type="hidden" name="gradingMenu" value="1" />'."\n".
    '<input type="hidden" name="showgrading" value="yes" />'."\n";
   
       $result.='
       <div class="LC_grade_select_mode">
         <div class="LC_grade_select_mode_current">
           <h2>
             '.&mt('Grade Current Resource').'
           </h2>
           <div class="LC_grade_select_mode_body">
             <div class="LC_grades_resource_info">
              '.$table.'
             </div>
             <div class="LC_grade_select_mode_selector">
                <div class="LC_grade_select_mode_selector_header">
                   '.&mt('Sections').'
                </div>
                <div class="LC_grade_select_mode_selector_body">
          <select name="section" multiple="multiple" size="5">'."\n";
       if (ref($sections)) {
    foreach my $section (sort (@$sections)) {
       $result.='<option value="'.$section.'" '.
    ($saveSec eq $section ? 'selected="selected"':'').'>'.$section.'</option>'."\n";
    }
       }
       $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> &nbsp; ';
       $result.='
                </div>
             </div>
             <div class="LC_grade_select_mode_selector">
                <div class="LC_grade_select_mode_selector_header">
                   '.&mt('Groups').'
                </div>
                <div class="LC_grade_select_mode_selector_body">
                   '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'
                </div>
             </div>
             <div class="LC_grade_select_mode_selector">
                <div class="LC_grade_select_mode_selector_header">
                   '.&mt('Access Status').'
                </div>
                <div class="LC_grade_select_mode_selector_body">
                   '.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,5,undef,'mult').'
                </div>
             </div>
             <div class="LC_grade_select_mode_selector">
                <div class="LC_grade_select_mode_selector_header">
                   '.&mt('Submission Status').'
                </div>
                <div class="LC_grade_select_mode_selector_body">
                  <select name="submitonly" size="5">
            <option value="yes" '.      ($saveSub eq 'yes'       ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>
            <option value="queued" '.   ($saveSub eq 'queued'    ? 'selected="selected"' : '').'>'.&mt('in grading queue').'</option>
            <option value="graded" '.   ($saveSub eq 'graded'    ? 'selected="selected"' : '').'>'.&mt('with ungraded submissions').'</option>
            <option value="incorrect" '.($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>
                    <option value="all" '.      ($saveSub eq 'all'       ? 'selected="selected"' : '').'>'.&mt('with any status').'</option>
                  </select>
                </div>
             </div>
             <div class="LC_grade_select_mode_type_body">
               <div class="LC_grade_select_mode_type">
                 <label>
                   <input type="radio" name="radioChoice" value="submission" '.
                     ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.
                &mt('Select individual students to grade and view submissions.').'
         </label> 
               </div>
               <div class="LC_grade_select_mode_type">
         <label>
                   <input type="radio" name="radioChoice" value="viewgrades" '.
                     ($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '.
                       &mt('Grade all selected students in a grading table.').'
                 </label>
               </div>
               <div class="LC_grade_select_mode_type">
         <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next-&gt;').'" />
               </div>
             </div>
           </div>
         </div>
         <div class="LC_grade_select_mode_page">
           <h2>
            '.&mt('Grade Complete Folder for One Student').'
           </h2>
           <div class="LC_grades_select_mode_body">
             <div class="LC_grade_select_mode_type_body">
               <div class="LC_grade_select_mode_type">
                 <label>
                   <input type="radio" name="radioChoice" value="pickStudentPage" '.
     ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '.
     &mt('The <b>complete</b> page/sequence/folder: For one student').'
                 </label>
               </div>
               <div class="LC_grade_select_mode_type">
         <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next-&gt;').'" />
               </div>
             </div>
           </div>
         </div>
       </div>
     </form>';
       return $result;
   }
   
   sub reset_perm {
       undef(%perm);
   }
   
   sub init_perm {
       &reset_perm();
       foreach my $test_perm ('vgr','mgr','opa') {
   
    my $scope = $env{'request.course.id'};
    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});
       }
    }
       }
   }
   
   sub gather_clicker_ids {
       my %clicker_ids;
   
       my $classlist = &Apache::loncoursedata::get_classlist();
   
       # Set up a couple variables.
       my $username_idx = &Apache::loncoursedata::CL_SNAME();
       my $domain_idx   = &Apache::loncoursedata::CL_SDOM();
       my $status_idx   = &Apache::loncoursedata::CL_STATUS();
   
       foreach my $student (keys(%$classlist)) {
           if ($classlist->{$student}->[$status_idx] ne 'Active') { next; }
           my $username = $classlist->{$student}->[$username_idx];
           my $domain   = $classlist->{$student}->[$domain_idx];
           my $clickers =
       (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1];
           foreach my $id (split(/\,/,$clickers)) {
               $id=~s/^[\#0]+//;
               $id=~s/[\-\:]//g;
               if (exists($clicker_ids{$id})) {
    $clicker_ids{$id}.=','.$username.':'.$domain;
               } else {
    $clicker_ids{$id}=$username.':'.$domain;
               }
           }
       }
       return %clicker_ids;
   }
   
   sub gather_adv_clicker_ids {
       my %clicker_ids;
       my $cnum=$env{'course.'.$env{'request.course.id'}.'.num'};
       my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
       my %coursepersonnel=&Apache::lonnet::get_course_adv_roles($cdom.'/'.$cnum);
       foreach my $element (sort(keys(%coursepersonnel))) {
           foreach my $person (split(/\,/,$coursepersonnel{$element})) {
               my ($puname,$pudom)=split(/\:/,$person);
               my $clickers =
    (&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1];
               foreach my $id (split(/\,/,$clickers)) {
    $id=~s/^[\#0]+//;
                   $id=~s/[\-\:]//g;
    if (exists($clicker_ids{$id})) {
       $clicker_ids{$id}.=','.$puname.':'.$pudom;
    } else {
       $clicker_ids{$id}=$puname.':'.$pudom;
    }
               }
           }
       }
       return %clicker_ids;
   }
   
   sub clicker_grading_parameters {
       return ('gradingmechanism' => 'scalar',
               'upfiletype' => 'scalar',
               'specificid' => 'scalar',
               'pcorrect' => 'scalar',
               'pincorrect' => 'scalar');
   }
   
   sub process_clicker {
       my ($r)=@_;
       my ($symb)=&get_symb($r);
       if (!$symb) {return '';}
       my $result=&checkforfile_js();
       $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
       my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});
       $result.=$table;
       $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";      $result.='<tr bgcolor=#ffffe6><td>'."\n";
     $result.='<form action="/adm/grades" method="post" name="verifyform">'."\n";  # Attempt to restore parameters from last session, set defaults if not present
     $result.='&nbsp;<tt>'.$hostver.'-<input type="text" name="receipt" size="4"></tt><br />'."\n";      my %Saveable_Parameters=&clicker_grading_parameters();
     $result.='&nbsp;<input type="button" onClick="javascript:checkEntry(this.form);"'.      &Apache::loncommon::restore_course_settings('grades_clicker',
  ' value="Verify Receipt">'."\n";                                                   \%Saveable_Parameters);
     $result.='<input type="hidden" name="command" value="verify">'."\n";      if (!$env{'form.pcorrect'}) { $env{'form.pcorrect'}=100; }
     if ($ENV{'form.url'}) {      if (!$env{'form.pincorrect'}) { $env{'form.pincorrect'}=100; }
  $result.='<input type="hidden" name="url" value="'.$ENV{'form.url'}.'" />';      if (!$env{'form.gradingmechanism'}) { $env{'form.gradingmechanism'}='attendance'; }
     }      if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; }
     if ($ENV{'form.symb'}) {  
  $result.='<input type="hidden" name="symb" value="'.$ENV{'form.symb'}.'" />';      my %checked;
     }      foreach my $gradingmechanism ('attendance','personnel','specific') {
     $result.='</form>';         if ($env{'form.gradingmechanism'} eq $gradingmechanism) {
     $result.='</td></tr></table>'."\n";            $checked{$gradingmechanism}="checked='checked'";
     $result.='</td></tr></table>'."\n";         }
       }
   
       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 $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.=<<ENDUPFORM;
   <script type="text/javascript">
   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;
   }
   </script>
   <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="hidden" name="probTitle" value="$env{'form.probTitle'}" />
   <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
   <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" />
   <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";
       $result.=&show_grading_menu_form($symb);
     return $result;      return $result;
 }  }
   
   sub process_clicker_file {
       my ($r)=@_;
       my ($symb)=&get_symb($r);
       if (!$symb) {return '';}
   
       my %Saveable_Parameters=&clicker_grading_parameters();
       &Apache::loncommon::store_course_settings('grades_clicker',
                                                 \%Saveable_Parameters);
   
       my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
       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.&show_grading_menu_form($symb);
       }
       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');
       } 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.&show_grading_menu_form($symb);
    }
       }
       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.&show_grading_menu_form($symb);
       }
   
   # 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="probTitle" value="$env{'form.probTitle'}" />
   <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
   <input type="hidden" name="gradingmechanism" value="$env{'form.gradingmechanism'}" />
   <input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" />
   <input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" />
   ENDHEADER
       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 corrion(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 />';
   # 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') {
          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".
                '</td></tr></table><br /><br />'."\n";
       return $result.&show_grading_menu_form($symb);
   }
   
   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)=@_;
       my ($symb)=&get_symb($r);
       if (!$symb) {return '';}
   # See which part we are saving to
       my ($partlist,$handgrade,$responseType) = &response_type($symb);
   # FIXME: This should probably look for the first handgradeable part
       my $part=$$partlist[0];
   # Start screen output
       my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
   
       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;
             for (my $i=0;$i<$number;$i++) {
                if ($answer[$i]) {
                   if ($gradingmechanism eq 'attendance') {
                      $sum+=$pcorrect;
                   } else {
                      if ($answer[$i] eq $correct[$i]) {
                         $sum+=$pcorrect;
                      } else {
                         $sum+=$pincorrect;
                      }
                   }
                }
             }
             my $ave=$sum/(100*$number);
   # 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 [_1] student(s).',$storecount).
                '</td></tr></table>'."\n".
                '</td></tr></table><br /><br />'."\n";
       return $result.&show_grading_menu_form($symb);
   }
   
 sub handler {  sub handler {
     my $request=$_[0];      my $request=$_[0];
           &reset_caches();
     if ($ENV{'browser.mathml'}) {      if ($env{'browser.mathml'}) {
  $request->content_type('text/xml');   &Apache::loncommon::content_type($request,'text/xml');
     } else {      } else {
  $request->content_type('text/html');   &Apache::loncommon::content_type($request,'text/html');
     }      }
     $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=&get_symb($request,1);
     my $symb=$ENV{'form.symb'};      my @commands=&Apache::loncommon::get_env_multiple('form.command');
     my $command=$ENV{'form.command'};      my $command=$commands[0];
     if (!$url) {  
  my ($temp1,$temp2);      if ($#commands > 0) {
  ($temp1,$temp2,$ENV{'form.url'})=split(/___/,$symb);   &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
  $url = $ENV{'form.url'};      }
     }  
     &send_header($request);  
     if ($url eq '' && $symb eq '') {      $request->print(&Apache::loncommon::start_page('Grading'));
  if ($ENV{'user.adv'}) {      if ($symb eq '' && $command eq '') {
     if (($ENV{'form.codeone'}) && ($ENV{'form.codetwo'}) &&   if ($env{'user.adv'}) {
  ($ENV{'form.codethree'})) {      if (($env{'form.codeone'}) && ($env{'form.codetwo'}) &&
  my $token=$ENV{'form.codeone'}.'*'.$ENV{'form.codetwo'}.'*'.   ($env{'form.codethree'})) {
     $ENV{'form.codethree'};   my $token=$env{'form.codeone'}.'*'.$env{'form.codetwo'}.'*'.
       $env{'form.codethree'};
  my ($tsymb,$tuname,$tudom,$tcrsid)=   my ($tsymb,$tuname,$tudom,$tcrsid)=
     &Apache::lonnet::checkin($token);      &Apache::lonnet::checkin($token);
  if ($tsymb) {   if ($tsymb) {
     my ($map,$id,$url)=split(/\_\_\_/,$tsymb);      my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb);
     if (&Apache::lonnet::allowed('mgr',$tcrsid)) {      if (&Apache::lonnet::allowed('mgr',$tcrsid)) {
  $request->print(   $request->print(&Apache::lonnet::ssi_body('/res/'.$url,
  &Apache::lonnet::ssi('/res/'.$url,    ('grade_username' => $tuname,
      ('grade_username' => $tuname,     'grade_domain' => $tudom,
       'grade_domain' => $tudom,     'grade_courseid' => $tcrsid,
       'grade_courseid' => $tcrsid,     'grade_symb' => $tsymb)));
       'grade_symb' => $tsymb)));  
     } else {      } else {
  $request->print('<h3>Not authorized: '.$token.'</h3>');   $request->print('<h3>Not authorized: '.$token.'</h3>');
     }                 }
  } else {   } else {
     $request->print('<h3>Not a valid DocID: '.$token.'</h3>');      $request->print('<h3>Not a valid DocID: '.$token.'</h3>');
  }   }
Line 2390  sub handler { Line 8190  sub handler {
     }      }
  }   }
     } else {      } else {
  $Apache::grades::viewgrades=&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'});   &init_perm();
  if ($command eq 'submission') {   if ($command eq 'submission' && $perm{'vgr'}) {
     &listStudents($request) if ($ENV{'form.student'} eq '');      ($env{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0));
     &submission($request,0,0) if ($ENV{'form.student'} ne '');   } elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) {
  } elsif ($command eq 'processGroup') {      &pickStudentPage($request);
    } elsif ($command eq 'displayPage' && $perm{'vgr'}) {
       &displayPage($request);
    } elsif ($command eq 'gradeByPage' && $perm{'mgr'}) {
       &updateGradeByPage($request);
    } elsif ($command eq 'processGroup' && $perm{'vgr'}) {
     &processGroup($request);      &processGroup($request);
  } elsif ($command eq 'gradingmenu') {   } elsif ($command eq 'gradingmenu' && $perm{'vgr'}) {
     $request->print(&gradingmenu($request));      $request->print(&grading_menu($request));
  } elsif ($command eq 'viewgrades') {   } elsif ($command eq 'submit_options' && $perm{'vgr'}) {
       $request->print(&submit_options($request));
    } elsif ($command eq 'viewgrades' && $perm{'vgr'}) {
     $request->print(&viewgrades($request));      $request->print(&viewgrades($request));
  } elsif ($command eq 'handgrade') {   } elsif ($command eq 'handgrade' && $perm{'mgr'}) {
     $request->print(&processHandGrade($request));      $request->print(&processHandGrade($request));
  } elsif ($command eq 'editgrades') {   } elsif ($command eq 'editgrades' && $perm{'mgr'}) {
     $request->print(&editgrades($request));      $request->print(&editgrades($request));
  } elsif ($command eq 'verify') {   } elsif ($command eq 'verify' && $perm{'vgr'}) {
     $request->print(&verifyreceipt($request));      $request->print(&verifyreceipt($request));
  } elsif ($command eq 'csvupload') {          } elsif ($command eq 'processclicker' && $perm{'mgr'}) {
               $request->print(&process_clicker($request));
           } elsif ($command eq 'processclickerfile' && $perm{'mgr'}) {
               $request->print(&process_clicker_file($request));
           } elsif ($command eq 'assignclickergrades' && $perm{'mgr'}) {
               $request->print(&assign_clicker_grades($request));
    } elsif ($command eq 'csvform' && $perm{'mgr'}) {
       $request->print(&upcsvScores_form($request));
    } elsif ($command eq 'csvupload' && $perm{'mgr'}) {
     $request->print(&csvupload($request));      $request->print(&csvupload($request));
  } elsif ($command eq 'viewclasslist') {   } elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) {
     $request->print(&viewclasslist($request));  
  } elsif ($command eq 'csvuploadmap') {  
     $request->print(&csvuploadmap($request));      $request->print(&csvuploadmap($request));
  } elsif ($command eq 'csvuploadassign') {   } elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) {
     if ($ENV{'form.associate'} ne 'Reverse Association') {      if ($env{'form.associate'} ne 'Reverse Association') {
  $request->print(&csvuploadassign($request));   $request->print(&csvuploadoptions($request));
     } 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));   $request->print(&csvuploadmap($request));
     }      }
  } else {   } elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {
     $request->print("Unknown action: $command:");      $request->print(&csvuploadassign($request));
    } elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
       $request->print(&scantron_selectphase($request));
     } elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {
        $request->print(&scantron_do_warning($request));
    } elsif ($command eq 'scantron_validate' && $perm{'mgr'}) {
       $request->print(&scantron_validate_file($request));
    } elsif ($command eq 'scantron_process' && $perm{'mgr'}) {
       $request->print(&scantron_process_students($request));
     } elsif ($command eq 'scantronupload' && 
     (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
     &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
        $request->print(&scantron_upload_scantron_data($request)); 
     } elsif ($command eq 'scantronupload_save' &&
     (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
     &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
        $request->print(&scantron_upload_scantron_data_save($request));
     } elsif ($command eq 'scantron_download' &&
    &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
        $request->print(&scantron_download_scantron_data($request));
    } elsif ($command) {
       $request->print("Access Denied ($command)");
  }   }
     }      }
     &send_footer($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'));  
 }  
   
 sub send_footer {  
     my ($request)= @_;  
     $request->print('</body>');  
     $request->print(&Apache::lontexconvert::footer());  
 }  
   
 1;  1;
   
 __END__;  __END__;

Removed from v.1.62  
changed lines
  Added in v.1.489


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