File:  [LON-CAPA] / loncom / homework / grades.pm
Revision 1.269: download - view: text, annotated - select for diffs
Fri Jun 3 18:23:19 2005 UTC (19 years ago) by raeburn
Branches: MAIN
CVS tags: HEAD
Unescape value passed to lond's increment function so that negative increments can be passed.  Include functionality in grades.pm so that iuse of "reset status" causes aggregate totals to be decremented appropriately.

    1: # The LearningOnline Network with CAPA
    2: # The LON-CAPA Grading handler
    3: #
    4: # $Id: grades.pm,v 1.269 2005/06/03 18:23:19 raeburn Exp $
    5: #
    6: # Copyright Michigan State University Board of Trustees
    7: #
    8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    9: #
   10: # LON-CAPA is free software; you can redistribute it and/or modify
   11: # it under the terms of the GNU General Public License as published by
   12: # the Free Software Foundation; either version 2 of the License, or
   13: # (at your option) any later version.
   14: #
   15: # LON-CAPA is distributed in the hope that it will be useful,
   16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18: # GNU General Public License for more details.
   19: #
   20: # You should have received a copy of the GNU General Public License
   21: # along with LON-CAPA; if not, write to the Free Software
   22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   23: #
   24: # /home/httpd/html/adm/gpl.txt
   25: #
   26: # http://www.lon-capa.org/
   27: #
   28: 
   29: package Apache::grades;
   30: use strict;
   31: use Apache::style;
   32: use Apache::lonxml;
   33: use Apache::lonnet;
   34: use Apache::loncommon;
   35: use Apache::lonhtmlcommon;
   36: use Apache::lonnavmaps;
   37: use Apache::lonhomework;
   38: use Apache::loncoursedata;
   39: use Apache::lonmsg qw(:user_normal_msg);
   40: use Apache::Constants qw(:common);
   41: use Apache::lonlocal;
   42: use String::Similarity;
   43: 
   44: my %oldessays=();
   45: my %perm=();
   46: 
   47: # ----- These first few routines are general use routines.----
   48: #
   49: # --- Retrieve the parts from the metadata file.---
   50: sub getpartlist {
   51:     my ($url,$symb) = @_;
   52:     my $partorder = &Apache::lonnet::metadata($url, 'partorder');
   53:     my @parts;
   54:     if ($partorder) {
   55: 	for my $part (split (/,/,$partorder)) {
   56: 	    if (!&Apache::loncommon::check_if_partid_hidden($part,$symb)) {
   57: 		push(@parts, $part);
   58: 	    }
   59: 	}	    
   60:     } else {
   61: 	my $metadata = &Apache::lonnet::metadata($url, 'packages');
   62: 	foreach (split(/\,/,$metadata)) {
   63: 	    if ($_ =~ /^part_(.*)$/) {
   64: 		if (!&Apache::loncommon::check_if_partid_hidden($1,$symb)) {
   65: 		    push(@parts, $1);
   66: 		}
   67: 	    }
   68: 	}
   69:     }
   70:     my @stores;
   71:     foreach my $part (@parts) {
   72: 	my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys'));
   73: 	foreach my $key (@metakeys) {
   74: 	    if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }
   75: 	}
   76:     }
   77:     return @stores;
   78: }
   79: 
   80: # --- Get the symbolic name of a problem and the url
   81: sub get_symb_and_url {
   82:     my ($request,$silent) = @_;
   83:     (my $url=$env{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
   84:     my $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url)));
   85:     if ($symb eq '') { 
   86: 	if (!$silent) {
   87: 	    $request->print("Unable to handle ambiguous references:$url:.");
   88: 	    return ();
   89: 	}
   90:     }
   91:     return ($symb,$url);
   92: }
   93: 
   94: #--- Format fullname, username:domain if different for display
   95: #--- Use anywhere where the student names are listed
   96: sub nameUserString {
   97:     my ($type,$fullname,$uname,$udom) = @_;
   98:     if ($type eq 'header') {
   99: 	return '<b>&nbsp;Fullname&nbsp;</b><font color="#999999">(Username)</font>';
  100:     } else {
  101: 	return '&nbsp;'.$fullname.'<font color="#999999">&nbsp;('.$uname.
  102: 	    ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</font>';
  103:     }
  104: }
  105: 
  106: #--- Get the partlist and the response type for a given problem. ---
  107: #--- Indicate if a response type is coded handgraded or not. ---
  108: sub response_type {
  109:     my ($url,$symb) = shift;
  110:     $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url))) if ($symb eq '');
  111:     my $allkeys = &Apache::lonnet::metadata($url,'keys');
  112:     my %vPart;
  113:     foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) {
  114: 	$vPart{$partid}=1;
  115:     }
  116:     my %seen = ();
  117:     my (@partlist,%handgrade,%responseType);
  118:     foreach (split(/,/,&Apache::lonnet::metadata($url,'packages'))) {
  119: 	if (/^\w+response_.*/) {
  120: 	    my ($responsetype,$part) = split(/_/,$_,2);
  121: 	    my ($partid,$respid) = split(/_/,$part);
  122: 	    if (&Apache::loncommon::check_if_partid_hidden($partid,$symb)) {
  123: 		next;
  124: 	    }
  125: 	    if (%vPart && !exists($vPart{$partid})) {
  126: 		next;
  127: 	    }
  128: 	    $responsetype =~ s/response$//; # make it compatible w/ navmaps - should move to that!!
  129: 	    my ($value) = &Apache::lonnet::EXT('resource.'.$part.'.handgrade',$symb);
  130: 	    $handgrade{$part} = ($value eq 'yes' ? 'yes' : 'no'); 
  131: 	    if (!exists($responseType{$partid})) { $responseType{$partid}={}; }
  132: 	    $responseType{$partid}->{$respid}=$responsetype;
  133: 	    next if ($seen{$partid} > 0);
  134: 	    $seen{$partid}++;
  135: 	    push @partlist,$partid;
  136: 	}
  137:     }
  138:     return \@partlist,\%handgrade,\%responseType;
  139: }
  140: 
  141: sub get_display_part {
  142:     my ($partID,$url,$symb)=@_;
  143:     if (!defined($symb) || $symb eq '') {
  144: 	$symb=$env{'form.symb'};
  145: 	if ($symb eq '') { $symb=&Apache::lonnet::symbread($url) }
  146:     }
  147:     my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);
  148:     if (defined($display) and $display ne '') {
  149: 	$display.= " (<font color=\"#999900\">id $partID</font>)";
  150:     } else {
  151: 	$display=$partID;
  152:     }
  153:     return $display;
  154: }
  155: 
  156: #--- Show resource title
  157: #--- and parts and response type
  158: sub showResourceInfo {
  159:     my ($url,$probTitle,$checkboxes) = @_;
  160:     my $col=3;
  161:     if ($checkboxes) { $col=4; }
  162:     my $result ='<table border="0">'.
  163: 	'<tr><td colspan="'.$col.'"><font size="+1"><b>'.&mt('Current Resource').': </b>'.
  164: 	$probTitle.'</font></td></tr>'."\n";
  165:     my ($partlist,$handgrade,$responseType) = &response_type($url);
  166:     my %resptype = ();
  167:     my $hdgrade='no';
  168:     my %partsseen;
  169:     for my $part_resID (sort keys(%$handgrade)) {
  170: 	my $handgrade=$$handgrade{$part_resID};
  171: 	my ($partID,$resID) = split(/_/,$part_resID);
  172: 	my $responsetype = $responseType->{$partID}->{$resID};
  173: 	$hdgrade = $handgrade if ($handgrade eq 'yes');
  174: 	$result.='<tr>';
  175: 	if ($checkboxes) {
  176: 	    if (exists($partsseen{$partID})) {
  177: 		$result.="<td>&nbsp;</td>";
  178: 	    } else {
  179: 		$result.="<td><input type='checkbox' name='vPart' value='$partID' checked='on' /></td>";
  180: 	    }
  181: 	    $partsseen{$partID}=1;
  182: 	}
  183: 	my $display_part=&get_display_part($partID,$url);
  184: 	$result.='<td><b>Part: </b>'.$display_part.' <font color="#999999">'.
  185: 	    $resID.'</font></td>'.
  186: 	    '<td><b>Type: </b>'.$responsetype.'</td></tr>';
  187: #	    '<td><b>Handgrade: </b>'.$handgrade.'</td></tr>';
  188:     }
  189:     $result.='</table>'."\n";
  190:     return $result,$responseType,$hdgrade,$partlist,$handgrade;
  191: }
  192: 
  193: 
  194: sub get_order {
  195:     my ($partid,$respid,$symb,$uname,$udom)=@_;
  196:     my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
  197:     $url=&Apache::lonnet::clutter($url);
  198:     my $subresult=&Apache::lonnet::ssi($url,
  199: 				       ('grade_target' => 'analyze'),
  200: 				       ('grade_domain' => $udom),
  201: 				       ('grade_symb' => $symb),
  202: 				       ('grade_courseid' => 
  203: 					        $env{'request.course.id'}),
  204: 				       ('grade_username' => $uname));
  205:     (undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
  206:     my %analyze=&Apache::lonnet::str2hash($subresult);
  207:     return ($analyze{"$partid.$respid.shown"});
  208: }
  209: #--- Clean response type for display
  210: #--- Currently filters option/rank/radiobutton/match/essay response types only.
  211: sub cleanRecord {
  212:     my ($answer,$response,$symb,$partid,$respid,$record,$order,$version) = @_;
  213:     my $grayFont = '<font color="#999999">';
  214:     if ($response =~ /^(option|rank)$/) {
  215: 	my %answer=&Apache::lonnet::str2hash($answer);
  216: 	my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
  217: 	my ($toprow,$bottomrow);
  218: 	foreach my $foil (@$order) {
  219: 	    if ($grading{$foil} == 1) {
  220: 		$toprow.='<td><b>'.$answer{$foil}.'&nbsp;</b></td>';
  221: 	    } else {
  222: 		$toprow.='<td><i>'.$answer{$foil}.'&nbsp;</i></td>';
  223: 	    }
  224: 	    $bottomrow.='<td>'.$grayFont.$foil.'</font>&nbsp;</td>';
  225: 	}
  226: 	return '<blockquote><table border="1">'.
  227: 	    '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'.
  228: 	    '<tr valign="top"><td>'.$grayFont.'Option ID</font></td>'.
  229: 	    $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';
  230:     } elsif ($response eq 'match') {
  231: 	my %answer=&Apache::lonnet::str2hash($answer);
  232: 	my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
  233: 	my @items=&Apache::lonnet::str2array($record->{$version."resource.$partid.$respid.submissionitems"});
  234: 	my ($toprow,$middlerow,$bottomrow);
  235: 	foreach my $foil (@$order) {
  236: 	    my $item=shift(@items);
  237: 	    if ($grading{$foil} == 1) {
  238: 		$toprow.='<td><b>'.$item.'&nbsp;</b></td>';
  239: 		$middlerow.='<td><b>'.$grayFont.$answer{$foil}.'&nbsp;</font></b></td>';
  240: 	    } else {
  241: 		$toprow.='<td><i>'.$item.'&nbsp;</i></td>';
  242: 		$middlerow.='<td><i>'.$grayFont.$answer{$foil}.'&nbsp;</font></i></td>';
  243: 	    }
  244: 	    $bottomrow.='<td>'.$grayFont.$foil.'</font>&nbsp;</td>';
  245: 	}
  246: 	return '<blockquote><table border="1">'.
  247: 	    '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'.
  248: 	    '<tr valign="top"><td>'.$grayFont.'Item ID</font></td>'.
  249: 	    $middlerow.'</tr>'.
  250: 	    '<tr valign="top"><td>'.$grayFont.'Option ID</font></td>'.
  251: 	    $bottomrow.'</tr>'.'</table></blockquote>';
  252:     } elsif ($response eq 'radiobutton') {
  253: 	my %answer=&Apache::lonnet::str2hash($answer);
  254: 	my ($toprow,$bottomrow);
  255: 	my $correct=($order->[0])+1;
  256: 	for (my $i=1;$i<=$#$order;$i++) {
  257: 	    my $foil=$order->[$i];
  258: 	    if (exists($answer{$foil})) {
  259: 		if ($i == $correct) {
  260: 		    $toprow.='<td><b>true</b></td>';
  261: 		} else {
  262: 		    $toprow.='<td><i>true</i></td>';
  263: 		}
  264: 	    } else {
  265: 		$toprow.='<td>false</td>';
  266: 	    }
  267: 	    $bottomrow.='<td>'.$grayFont.$foil.'</font>&nbsp;</td>';
  268: 	}
  269: 	return '<blockquote><table border="1">'.
  270: 	    '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'.
  271: 	    '<tr valign="top"><td>'.$grayFont.'Option ID</font></td>'.
  272: 	    $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';
  273:     } elsif ($response eq 'essay') {
  274: 	if (! exists ($env{'form.'.$symb})) {
  275: 	    my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade',
  276: 						  $env{'course.'.$env{'request.course.id'}.'.domain'},
  277: 						  $env{'course.'.$env{'request.course.id'}.'.num'});
  278: 
  279: 	    my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
  280: 	    $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
  281: 	    $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
  282: 	    $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
  283: 	    $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
  284: 	    $env{'form.'.$symb} = 1; # so that we don't have to read it from disk for multiple sub of the same prob.
  285: 	}
  286: 	$answer =~ s-\n-<br />-g;
  287: 	return '<br /><br /><blockquote><tt>'.&keywords_highlight($answer).'</tt></blockquote>';
  288:     } elsif ( $response eq 'organic') {
  289: 	my $result='Smile representation: "<tt>'.$answer.'</tt>"';
  290: 	my $jme=$record->{$version."resource.$partid.$respid.molecule"};
  291: 	$result.=&Apache::chemresponse::jme_img($jme,$answer,400);
  292: 	return $result;
  293:     }
  294:     return $answer;
  295: }
  296: 
  297: #-- A couple of common js functions
  298: sub commonJSfunctions {
  299:     my $request = shift;
  300:     $request->print(<<COMMONJSFUNCTIONS);
  301: <script type="text/javascript" language="javascript">
  302:     function radioSelection(radioButton) {
  303: 	var selection=null;
  304: 	if (radioButton.length > 1) {
  305: 	    for (var i=0; i<radioButton.length; i++) {
  306: 		if (radioButton[i].checked) {
  307: 		    return radioButton[i].value;
  308: 		}
  309: 	    }
  310: 	} else {
  311: 	    if (radioButton.checked) return radioButton.value;
  312: 	}
  313: 	return selection;
  314:     }
  315: 
  316:     function pullDownSelection(selectOne) {
  317: 	var selection="";
  318: 	if (selectOne.length > 1) {
  319: 	    for (var i=0; i<selectOne.length; i++) {
  320: 		if (selectOne[i].selected) {
  321: 		    return selectOne[i].value;
  322: 		}
  323: 	    }
  324: 	} else {
  325:             // only one value it must be the selected one
  326: 	    return selectOne.value;
  327: 	}
  328:     }
  329: </script>
  330: COMMONJSFUNCTIONS
  331: }
  332: 
  333: #--- Dumps the class list with usernames,list of sections,
  334: #--- section, ids and fullnames for each user.
  335: sub getclasslist {
  336:     my ($getsec,$filterlist) = @_;
  337:     $getsec = $getsec eq '' ? 'all' : $getsec;
  338:     my $classlist=&Apache::loncoursedata::get_classlist();
  339:     # Bail out if we were unable to get the classlist
  340:     return if (! defined($classlist));
  341:     #
  342:     my %sections;
  343:     my %fullnames;
  344:     foreach my $student (keys(%$classlist)) {
  345:         my $end      = 
  346:             $classlist->{$student}->[&Apache::loncoursedata::CL_END()];
  347:         my $start    = 
  348:             $classlist->{$student}->[&Apache::loncoursedata::CL_START()];
  349:         my $id       = 
  350:             $classlist->{$student}->[&Apache::loncoursedata::CL_ID()];
  351:         my $section  = 
  352:             $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
  353:         my $fullname = 
  354:             $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()];
  355:         my $status   = 
  356:             $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()];
  357: 	# filter students according to status selected
  358: 	if ($filterlist && $env{'form.Status'} ne 'Any') {
  359: 	    if ($env{'form.Status'} ne $status) {
  360: 		delete ($classlist->{$student});
  361: 		next;
  362: 	    }
  363: 	}
  364: 	$section = ($section ne '' ? $section : 'none');
  365: 	if (&canview($section)) {
  366: 	    if ($getsec eq 'all' || $getsec eq $section) {
  367: 		$sections{$section}++;
  368: 		$fullnames{$student}=$fullname;
  369: 	    } else {
  370: 		delete($classlist->{$student});
  371: 	    }
  372: 	} else {
  373: 	    delete($classlist->{$student});
  374: 	}
  375:     }
  376:     my %seen = ();
  377:     my @sections = sort(keys(%sections));
  378:     return ($classlist,\@sections,\%fullnames);
  379: }
  380: 
  381: sub canmodify {
  382:     my ($sec)=@_;
  383:     if ($perm{'mgr'}) {
  384: 	if (!defined($perm{'mgr_section'})) {
  385: 	    # can modify whole class
  386: 	    return 1;
  387: 	} else {
  388: 	    if ($sec eq $perm{'mgr_section'}) {
  389: 		#can modify the requested section
  390: 		return 1;
  391: 	    } else {
  392: 		# can't modify the request section
  393: 		return 0;
  394: 	    }
  395: 	}
  396:     }
  397:     #can't modify
  398:     return 0;
  399: }
  400: 
  401: sub canview {
  402:     my ($sec)=@_;
  403:     if ($perm{'vgr'}) {
  404: 	if (!defined($perm{'vgr_section'})) {
  405: 	    # can modify whole class
  406: 	    return 1;
  407: 	} else {
  408: 	    if ($sec eq $perm{'vgr_section'}) {
  409: 		#can modify the requested section
  410: 		return 1;
  411: 	    } else {
  412: 		# can't modify the request section
  413: 		return 0;
  414: 	    }
  415: 	}
  416:     }
  417:     #can't modify
  418:     return 0;
  419: }
  420: 
  421: #--- Retrieve the grade status of a student for all the parts
  422: sub student_gradeStatus {
  423:     my ($url,$symb,$udom,$uname,$partlist) = @_;
  424:     my %record     = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
  425:     my %partstatus = ();
  426:     foreach (@$partlist) {
  427: 	my ($status,undef)   = split(/_/,$record{"resource.$_.solved"},2);
  428: 	$status              = 'nothing' if ($status eq '');
  429: 	$partstatus{$_}      = $status;
  430: 	my $subkey           = "resource.$_.submitted_by";
  431: 	$partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
  432:     }
  433:     return %partstatus;
  434: }
  435: 
  436: # hidden form and javascript that calls the form
  437: # Use by verifyscript and viewgrades
  438: # Shows a student's view of problem and submission
  439: sub jscriptNform {
  440:     my ($url,$symb) = @_;
  441:     my $jscript='<script type="text/javascript" language="javascript">'."\n".
  442: 	'    function viewOneStudent(user,domain) {'."\n".
  443: 	'	document.onestudent.student.value = user;'."\n".
  444: 	'	document.onestudent.userdom.value = domain;'."\n".
  445: 	'	document.onestudent.submit();'."\n".
  446: 	'    }'."\n".
  447: 	'</script>'."\n";
  448:     $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\n".
  449: 	'<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
  450: 	'<input type="hidden" name="url"     value="'.$url.'" />'."\n".
  451: 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
  452: 	'<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n".
  453: 	'<input type="hidden" name="Status"  value="'.$env{'form.Status'}.'" />'."\n".
  454: 	'<input type="hidden" name="command" value="submission" />'."\n".
  455: 	'<input type="hidden" name="student" value="" />'."\n".
  456: 	'<input type="hidden" name="userdom" value="" />'."\n".
  457: 	'</form>'."\n";
  458:     return $jscript;
  459: }
  460: 
  461: #------------------ End of general use routines --------------------
  462: 
  463: #
  464: # Find most similar essay
  465: #
  466: 
  467: sub most_similar {
  468:     my ($uname,$udom,$uessay)=@_;
  469: 
  470: # ignore spaces and punctuation
  471: 
  472:     $uessay=~s/\W+/ /gs;
  473: 
  474: # these will be returned. Do not care if not at least 50 percent similar
  475:     my $limit=0.6;
  476:     my $sname='';
  477:     my $sdom='';
  478:     my $scrsid='';
  479:     my $sessay='';
  480: # go through all essays ...
  481:     foreach my $tkey (keys %oldessays) {
  482: 	my ($tname,$tdom,$tcrsid)=split(/\./,$tkey);
  483: # ... except the same student
  484:         if (($tname ne $uname) || ($tdom ne $udom)) {
  485: 	    my $tessay=$oldessays{$tkey};
  486:             $tessay=~s/\W+/ /gs;
  487: # String similarity gives up if not even limit
  488:             my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit);
  489: # Found one
  490:             if ($tsimilar>$limit) {
  491: 		$limit=$tsimilar;
  492:                 $sname=$tname;
  493:                 $sdom=$tdom;
  494:                 $scrsid=$tcrsid;
  495:                 $sessay=$oldessays{$tkey};
  496:             }
  497:         } 
  498:     }
  499:     if ($limit>0.6) {
  500:        return ($sname,$sdom,$scrsid,$sessay,$limit);
  501:     } else {
  502:        return ('','','','',0);
  503:     }
  504: }
  505: 
  506: #-------------------------------------------------------------------
  507: 
  508: #------------------------------------ Receipt Verification Routines
  509: #
  510: #--- Check whether a receipt number is valid.---
  511: sub verifyreceipt {
  512:     my $request  = shift;
  513: 
  514:     my $courseid = $env{'request.course.id'};
  515:     my $receipt  = &Apache::lonnet::recprefix($courseid).'-'.
  516: 	$env{'form.receipt'};
  517:     $receipt     =~ s/[^\-\d]//g;
  518:     my $url      = $env{'form.url'};
  519:     my $symb     = $env{'form.symb'};
  520:     unless ($symb) {
  521: 	$symb    = &Apache::lonnet::symbread($url);
  522:     }
  523: 
  524:     my $title.='<h3><font color="#339933">Verifying Submission Receipt '.
  525: 	$receipt.'</h3></font>'."\n".
  526: 	'<font size=+1><b>Resource: </b>'.$env{'form.probTitle'}.'</font><br><br>'."\n";
  527: 
  528:     my ($string,$contents,$matches) = ('','',0);
  529:     my (undef,undef,$fullname) = &getclasslist('all','0');
  530:     
  531:     my $receiptparts=0;
  532:     if ($env{"course.$courseid.receiptalg"} eq 'receipt2') { $receiptparts=1; }
  533:     my $parts=['0'];
  534:     if ($receiptparts) { ($parts)=&response_type($url,$symb); }
  535:     foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {
  536: 	my ($uname,$udom)=split(/\:/);
  537: 	foreach my $part (@$parts) {
  538: 	    if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) {
  539: 		$contents.='<tr bgcolor="#ffffe6"><td>&nbsp;'."\n".
  540: 		    '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
  541: 		    '\')"; TARGET=_self>'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".
  542: 		    '<td>&nbsp;'.$uname.'&nbsp;</td>'.
  543: 		    '<td>&nbsp;'.$udom.'&nbsp;</td>';
  544: 		if ($receiptparts) {
  545: 		    $contents.='<td>&nbsp;'.$part.'&nbsp;</td>';
  546: 		}
  547: 		$contents.='</tr>'."\n";
  548: 		
  549: 		$matches++;
  550: 	    }
  551: 	}
  552:     }
  553:     if ($matches == 0) {
  554: 	$string = $title.'No match found for the above receipt.';
  555:     } else {
  556: 	$string = &jscriptNform($url,$symb).$title.
  557: 	    'The above receipt matches the following student'.
  558: 	    ($matches <= 1 ? '.' : 's.')."\n".
  559: 	    '<table border="0"><tr><td bgcolor="#777777">'."\n".
  560: 	    '<table border="0"><tr bgcolor="#e6ffff">'."\n".
  561: 	    '<td><b>&nbsp;Fullname&nbsp;</b></td>'."\n".
  562: 	    '<td><b>&nbsp;Username&nbsp;</b></td>'."\n".
  563: 	    '<td><b>&nbsp;Domain&nbsp;</b></td>';
  564: 	if ($receiptparts) {
  565: 	    $string.='<td>&nbsp;Problem Part&nbsp;</td>';
  566: 	}
  567: 	$string.='</tr>'."\n".$contents.
  568: 	    '</table></td></tr></table>'."\n";
  569:     }
  570:     return $string.&show_grading_menu_form($symb,$url);
  571: }
  572: 
  573: #--- This is called by a number of programs.
  574: #--- Called from the Grading Menu - View/Grade an individual student
  575: #--- Also called directly when one clicks on the subm button 
  576: #    on the problem page.
  577: sub listStudents {
  578:     my ($request) = shift;
  579: 
  580:     my ($symb,$url) = &get_symb_and_url($request);
  581:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
  582:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
  583:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
  584:     my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
  585: 
  586:     my $viewgrade = $env{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View';
  587:     $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 
  588: 	&Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
  589: 
  590:     my $result='<h3><font color="#339933">&nbsp;'.$viewgrade.
  591: 	' Submissions for a Student or a Group of Students</font></h3>';
  592: 
  593:     my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($url,$env{'form.probTitle'},($env{'form.showgrading'} eq 'yes'));
  594: 
  595:     $request->print(<<LISTJAVASCRIPT);
  596: <script type="text/javascript" language="javascript">
  597:     function checkSelect(checkBox) {
  598: 	var ctr=0;
  599: 	var sense="";
  600: 	if (checkBox.length > 1) {
  601: 	    for (var i=0; i<checkBox.length; i++) {
  602: 		if (checkBox[i].checked) {
  603: 		    ctr++;
  604: 		}
  605: 	    }
  606: 	    sense = "a student or group of students";
  607: 	} else {
  608: 	    if (checkBox.checked) {
  609: 		ctr = 1;
  610: 	    }
  611: 	    sense = "the student";
  612: 	}
  613: 	if (ctr == 0) {
  614: 	    alert("Please select "+sense+" before clicking on the Next button.");
  615: 	    return false;
  616: 	}
  617: 	document.gradesub.submit();
  618:     }
  619: 
  620:     function reLoadList(formname) {
  621: 	if (formname.saveStatusOld.value == pullDownSelection(formname.Status)) {return;}
  622: 	formname.command.value = 'submission';
  623: 	formname.submit();
  624:     }
  625: </script>
  626: LISTJAVASCRIPT
  627: 
  628:     &commonJSfunctions($request);
  629:     $request->print($result);
  630: 
  631:     my $checkhdgrade = ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1 ) ? 'checked' : '';
  632:     my $checklastsub = $checkhdgrade eq '' ? 'checked' : '';
  633:     my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.
  634: 	"\n".$table.
  635: 	'&nbsp;<b>View Problem Text: </b><label><input type="radio" name="vProb" value="no" checked="on" /> no </label>'."\n".
  636: 	'<label><input type="radio" name="vProb" value="yes" /> one student </label>'."\n".
  637: 	'<label><input type="radio" name="vProb" value="all" /> all students </label><br />'."\n".
  638: 	'&nbsp;<b>View Answer: </b><label><input type="radio" name="vAns" value="no"  /> no </label>'."\n".
  639: 	'<label><input type="radio" name="vAns" value="yes" /> one student </label>'."\n".
  640: 	'<label><input type="radio" name="vAns" value="all" checked="on" /> all students </label><br />'."\n".
  641: 	'&nbsp;<b>Submissions: </b>'."\n";
  642:     if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {
  643: 	$gradeTable.='<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> essay part only </label>'."\n";
  644:     }
  645: 
  646:     my $saveStatus = $env{'form.Status'} eq '' ? 'Active' : $env{'form.Status'};
  647:     $env{'form.Status'} = $saveStatus;
  648: 
  649:     $gradeTable.='<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> last submission only </label>'."\n".
  650: 	'<label><input type="radio" name="lastSub" value="last" /> last submission & parts info </label>'."\n".
  651: 	'<label><input type="radio" name="lastSub" value="datesub" /> by dates and submissions </label>'."\n".
  652: 	'<label><input type="radio" name="lastSub" value="all" /> all details</label>'."\n".
  653: 	'<input type="hidden" name="section"     value="'.$getsec.'" />'."\n".
  654: 	'<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".
  655: 	'<input type="hidden" name="handgrade"   value="'.$env{'form.handgrade'}.'" /><br />'."\n".
  656: 	'<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" /><br />'."\n".
  657: 	'<input type="hidden" name="saveState"   value="'.$env{'form.saveState'}.'" />'."\n".
  658: 	'<input type="hidden" name="probTitle"   value="'.$env{'form.probTitle'}.'" />'."\n".
  659: 	'<input type="hidden" name="url"  value="'.$url.'" />'."\n".
  660: 	'<input type="hidden" name="symb" value="'.$symb.'" />'."\n".
  661: 	'<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";
  662: 
  663:     if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) {
  664: 	$gradeTable.='<input type="hidden" name="Status"   value="'.$env{'form.Status'}.'" />'."\n";
  665:     } else {
  666: 	$gradeTable.='<b>Student Status:</b> '.
  667: 	    &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);').'<br />';
  668:     }
  669: 
  670:     $gradeTable.='To '.lc($viewgrade).' a submission or a group of submissions, click on the check box(es) '.
  671: 	'next to the student\'s name(s). Then click on the Next button.<br />'."\n".
  672: 	'<input type="hidden" name="command" value="processGroup" />'."\n";
  673: 
  674: # checkall buttons
  675:     $gradeTable.=&check_script('gradesub', 'stuinfo');
  676:     $gradeTable.='<input type="button" '."\n".
  677: 	'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n".
  678: 	'value="Next->" /> <br />'."\n";
  679:     $gradeTable.=&check_buttons();
  680:     $gradeTable.='<label><input type="checkbox" name="checkPlag" checked="on" />Check For Plagiarism</label>';
  681:     my ($classlist, undef, $fullname) = &getclasslist($getsec,'1');
  682:     $gradeTable.='<table border="0"><tr><td bgcolor="#777777">'.
  683: 	'<table border="0"><tr bgcolor="#e6ffff">';
  684:     my $loop = 0;
  685:     while ($loop < 2) {
  686: 	$gradeTable.='<td><b>&nbsp;No.</b>&nbsp;</td><td><b>&nbsp;Select&nbsp;</b></td>'.
  687: 	    '<td>'.&nameUserString('header').'&nbsp;Section/Group</td>';
  688: 	if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {
  689: 	    foreach (sort(@$partlist)) {
  690: 		my $display_part=&get_display_part((split(/_/))[0],$url,$symb);
  691: 		$gradeTable.='<td><b>&nbsp;Part: '.$display_part.
  692: 		    ' Status&nbsp;</b></td>';
  693: 	    }
  694: 	}
  695: 	$loop++;
  696: #	$gradeTable.='<td></td>' if ($loop%2 ==1);
  697:     }
  698:     $gradeTable.='</tr>'."\n";
  699: 
  700:     my $ctr = 0;
  701:     foreach my $student (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {
  702: 	my ($uname,$udom) = split(/:/,$student);
  703: 	my %status = ();
  704: 	if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {
  705: 	    (%status) =&student_gradeStatus($url,$symb,$udom,$uname,$partlist);
  706: 	    my $submitted = 0;
  707: 	    my $graded = 0;
  708: 	    my $incorrect = 0;
  709: 	    foreach (keys(%status)) {
  710: 		$submitted = 1 if ($status{$_} ne 'nothing');
  711: 		$graded = 1 if ($status{$_} =~ /^ungraded/);
  712: 		$incorrect = 1 if ($status{$_} =~ /^incorrect/);
  713: 		
  714: 		my ($foo,$partid,$foo1) = split(/\./,$_);
  715: 		if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
  716: 		    $submitted = 0;
  717: 		    my ($part)=split(/\./,$partid);
  718: 		    $gradeTable.='<input type="hidden" name="'.
  719: 			$student.':'.$part.':submitted_by" value="'.
  720: 			$status{'resource.'.$partid.'.submitted_by'}.'" />';
  721: 		}
  722: 	    }
  723: 	    
  724: 	    next if (!$submitted && ($submitonly eq 'yes' ||
  725: 				     $submitonly eq 'incorrect' ||
  726: 				     $submitonly eq 'graded'));
  727: 	    next if (!$graded && ($submitonly eq 'graded'));
  728: 	    next if (!$incorrect && $submitonly eq 'incorrect');
  729: 	}
  730: 
  731: 	$ctr++;
  732: 	my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
  733: 
  734: 	if ( $perm{'vgr'} eq 'F' ) {
  735: 	    $gradeTable.='<tr bgcolor="#ffffe6">' if ($ctr%2 ==1);
  736: 	    $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.
  737:                '<td align="center"><label><input type=checkbox name="stuinfo" value="'.
  738:                $student.':'.$$fullname{$student}.':::SECTION'.$section.
  739: 	       ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.
  740: 	       &nameUserString(undef,$$fullname{$student},$uname,$udom).
  741: 	       '&nbsp;'.$section.'</td>'."\n";
  742: 
  743: 	    if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {
  744: 		foreach (sort keys(%status)) {
  745: 		    next if (/^resource.*?submitted_by$/);
  746: 		    $gradeTable.='<td align="middle">&nbsp;'.$status{$_}.'&nbsp;</td>'."\n";
  747: 		}
  748: 	    }
  749: #	    $gradeTable.='<td></td>' if ($ctr%2 ==1);
  750: 	    $gradeTable.='</tr>'."\n" if ($ctr%2 ==0);
  751: 	}
  752:     }
  753:     if ($ctr%2 ==1) {
  754: 	$gradeTable.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>';
  755: 	    if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {
  756: 		foreach (@$partlist) {
  757: 		    $gradeTable.='<td>&nbsp;</td>';
  758: 		}
  759: 	    }
  760: 	$gradeTable.='</tr>';
  761:     }
  762: 
  763:     $gradeTable.='</table></td></tr></table>'."\n".
  764: 	'<input type="button" '.
  765: 	'onClick="javascript:checkSelect(this.form.stuinfo);" '.
  766: 	'value="Next->" /></form>'."\n";
  767:     if ($ctr == 0) {
  768: 	my $num_students=(scalar(keys(%$fullname)));
  769: 	if ($num_students eq 0) {
  770: 	    $gradeTable='<br />&nbsp;<font color="red">There are no students currently enrolled.</font>';
  771: 	} else {
  772: 	    my $submissions='submissions';
  773: 	    if ($submitonly eq 'incorrect') { $submissions = 'incorrect submissions'; }
  774: 	    if ($submitonly eq 'graded'   ) { $submissions = 'ungraded submissions'; }
  775: 	    $gradeTable='<br />&nbsp;<font color="red">'.
  776: 		'No '.$submissions.' found for this resource for any students. ('.$num_students.
  777: 		' students checked for '.$submissions.')</font><br />';
  778: 	}
  779:     } elsif ($ctr == 1) {
  780: 	$gradeTable =~ s/type=checkbox/type=checkbox checked/;
  781:     }
  782:     $gradeTable.=&show_grading_menu_form($symb,$url);
  783:     $request->print($gradeTable);
  784:     return '';
  785: }
  786: 
  787: #---- Called from the listStudents routine
  788: 
  789: sub check_script {
  790:     my ($form, $type)=@_;
  791:     my $chkallscript='<script type="text/javascript">
  792:     function checkall() {
  793:         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
  794:             ele = document.forms.'.$form.'.elements[i];
  795:             if (ele.name == "'.$type.'") {
  796:             document.forms.'.$form.'.elements[i].checked=true;
  797:                                        }
  798:         }
  799:     }
  800: 
  801:     function checksec() {
  802:         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
  803:             ele = document.forms.'.$form.'.elements[i];
  804:            string = document.forms.'.$form.'.chksec.value;
  805:            if
  806:           (ele.value.indexOf(":::SECTION"+string)>0) {
  807:               document.forms.'.$form.'.elements[i].checked=true;
  808:             }
  809:         }
  810:     }
  811: 
  812: 
  813:     function uncheckall() {
  814:         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
  815:             ele = document.forms.'.$form.'.elements[i];
  816:             if (ele.name == "'.$type.'") {
  817:             document.forms.'.$form.'.elements[i].checked=false;
  818:                                        }
  819:         }
  820:     }
  821: 
  822: </script>'."\n";
  823:     return $chkallscript;
  824: }
  825: 
  826: sub check_buttons {
  827:     my $buttons.='<input type="button" onclick="checkall()" value="Check All" />';
  828:     $buttons.='<input type="button" onclick="uncheckall()" value="Uncheck All" />&nbsp;';
  829:     $buttons.='<input type="button" onclick="checksec()" value="Check Section/Group" />';
  830:     $buttons.='<input type="text" size="5" name="chksec" />&nbsp;';
  831:     return $buttons;
  832: }
  833: 
  834: #     Displays the submissions for one student or a group of students
  835: sub processGroup {
  836:     my ($request)  = shift;
  837:     my $ctr        = 0;
  838:     my @stuchecked = &Apache::loncommon::get_env_multiple('form.stuinfo');
  839:     my $total      = scalar(@stuchecked)-1;
  840: 
  841:     foreach (@stuchecked) {
  842: 	my ($uname,$udom,$fullname) = split(/:/);
  843: 	$env{'form.student'}        = $uname;
  844: 	$env{'form.userdom'}        = $udom;
  845: 	$env{'form.fullname'}       = $fullname;
  846: 	&submission($request,$ctr,$total);
  847: 	$ctr++;
  848:     }
  849:     return '';
  850: }
  851: 
  852: #------------------------------------------------------------------------------------
  853: #
  854: #-------------------------- Next few routines handles grading by student, essentially
  855: #                           handles essay response type problem/part
  856: #
  857: #--- Javascript to handle the submission page functionality ---
  858: sub sub_page_js {
  859:     my $request = shift;
  860:     $request->print(<<SUBJAVASCRIPT);
  861: <script type="text/javascript" language="javascript">
  862:     function updateRadio(formname,id,weight) {
  863: 	var gradeBox = formname["GD_BOX"+id];
  864: 	var radioButton = formname["RADVAL"+id];
  865: 	var oldpts = formname["oldpts"+id].value;
  866: 	var pts = checkSolved(formname,id) == 'update' ? gradeBox.value : oldpts;
  867: 	gradeBox.value = pts;
  868: 	var resetbox = false;
  869: 	if (isNaN(pts) || pts < 0) {
  870: 	    alert("A number equal or greater than 0 is expected. Entered value = "+pts);
  871: 	    for (var i=0; i<radioButton.length; i++) {
  872: 		if (radioButton[i].checked) {
  873: 		    gradeBox.value = i;
  874: 		    resetbox = true;
  875: 		}
  876: 	    }
  877: 	    if (!resetbox) {
  878: 		formtextbox.value = "";
  879: 	    }
  880: 	    return;
  881: 	}
  882: 
  883: 	if (pts > weight) {
  884: 	    var resp = confirm("You entered a value ("+pts+
  885: 			       ") greater than the weight for the part. Accept?");
  886: 	    if (resp == false) {
  887: 		gradeBox.value = oldpts;
  888: 		return;
  889: 	    }
  890: 	}
  891: 
  892: 	for (var i=0; i<radioButton.length; i++) {
  893: 	    radioButton[i].checked=false;
  894: 	    if (pts == i && pts != "") {
  895: 		radioButton[i].checked=true;
  896: 	    }
  897: 	}
  898: 	updateSelect(formname,id);
  899: 	formname["stores"+id].value = "0";
  900:     }
  901: 
  902:     function writeBox(formname,id,pts) {
  903: 	var gradeBox = formname["GD_BOX"+id];
  904: 	if (checkSolved(formname,id) == 'update') {
  905: 	    gradeBox.value = pts;
  906: 	} else {
  907: 	    var oldpts = formname["oldpts"+id].value;
  908: 	    gradeBox.value = oldpts;
  909: 	    var radioButton = formname["RADVAL"+id];
  910: 	    for (var i=0; i<radioButton.length; i++) {
  911: 		radioButton[i].checked=false;
  912: 		if (i == oldpts) {
  913: 		    radioButton[i].checked=true;
  914: 		}
  915: 	    }
  916: 	}
  917: 	formname["stores"+id].value = "0";
  918: 	updateSelect(formname,id);
  919: 	return;
  920:     }
  921: 
  922:     function clearRadBox(formname,id) {
  923: 	if (checkSolved(formname,id) == 'noupdate') {
  924: 	    updateSelect(formname,id);
  925: 	    return;
  926: 	}
  927: 	gradeSelect = formname["GD_SEL"+id];
  928: 	for (var i=0; i<gradeSelect.length; i++) {
  929: 	    if (gradeSelect[i].selected) {
  930: 		var selectx=i;
  931: 	    }
  932: 	}
  933: 	var stores = formname["stores"+id];
  934: 	if (selectx == stores.value) { return };
  935: 	var gradeBox = formname["GD_BOX"+id];
  936: 	gradeBox.value = "";
  937: 	var radioButton = formname["RADVAL"+id];
  938: 	for (var i=0; i<radioButton.length; i++) {
  939: 	    radioButton[i].checked=false;
  940: 	}
  941: 	stores.value = selectx;
  942:     }
  943: 
  944:     function checkSolved(formname,id) {
  945: 	if (formname["solved"+id].value == "correct_by_student" && formname.overRideScore.value == 'no') {
  946: 	    var reply = confirm("This problem has been graded correct by the computer. Do you want to change the score?");
  947: 	    if (!reply) {return "noupdate";}
  948: 	    formname.overRideScore.value = 'yes';
  949: 	}
  950: 	return "update";
  951:     }
  952: 
  953:     function updateSelect(formname,id) {
  954: 	formname["GD_SEL"+id][0].selected = true;
  955: 	return;
  956:     }
  957: 
  958: //=========== Check that a point is assigned for all the parts  ============
  959:     function checksubmit(formname,val,total,parttot) {
  960: 	formname.gradeOpt.value = val;
  961: 	if (val == "Save & Next") {
  962: 	    for (i=0;i<=total;i++) {
  963: 		for (j=0;j<parttot;j++) {
  964: 		    var partid = formname["partid"+i+"_"+j].value;
  965: 		    if (formname["GD_SEL"+i+"_"+partid][0].selected) {
  966: 			var points = formname["GD_BOX"+i+"_"+partid].value;
  967: 			if (points == "") {
  968: 			    var name = formname["name"+i].value;
  969: 			    var studentID = (name != '' ? name : formname["unamedom"+i].value);
  970: 			    var resp = confirm("You did not assign a score for "+studentID+
  971: 					       ", part "+partid+". Continue?");
  972: 			    if (resp == false) {
  973: 				formname["GD_BOX"+i+"_"+partid].focus();
  974: 				return false;
  975: 			    }
  976: 			}
  977: 		    }
  978: 		    
  979: 		}
  980: 	    }
  981: 	    
  982: 	}
  983: 	if (val == "Grade Student") {
  984: 	    formname.showgrading.value = "yes";
  985: 	    if (formname.Status.value == "") {
  986: 		formname.Status.value = "Active";
  987: 	    }
  988: 	    formname.studentNo.value = total;
  989: 	}
  990: 	formname.submit();
  991:     }
  992: 
  993: //======= Check that a score is assigned for all the problems (page/sequence grading only) =========
  994:     function checkSubmitPage(formname,total) {
  995: 	noscore = new Array(100);
  996: 	var ptr = 0;
  997: 	for (i=1;i<total;i++) {
  998: 	    var partid = formname["q_"+i].value;
  999: 	    if (formname["GD_SEL"+i+"_"+partid][0].selected) {
 1000: 		var points = formname["GD_BOX"+i+"_"+partid].value;
 1001: 		var status = formname["solved"+i+"_"+partid].value;
 1002: 		if (points == "" && status != "correct_by_student") {
 1003: 		    noscore[ptr] = i;
 1004: 		    ptr++;
 1005: 		}
 1006: 	    }
 1007: 	}
 1008: 	if (ptr != 0) {
 1009: 	    var sense = ptr == 1 ? ": " : "s: ";
 1010: 	    var prolist = "";
 1011: 	    if (ptr == 1) {
 1012: 		prolist = noscore[0];
 1013: 	    } else {
 1014: 		var i = 0;
 1015: 		while (i < ptr-1) {
 1016: 		    prolist += noscore[i]+", ";
 1017: 		    i++;
 1018: 		}
 1019: 		prolist += "and "+noscore[i];
 1020: 	    }
 1021: 	    var resp = confirm("You did not assign any score for the following problem"+sense+prolist+". Continue?");
 1022: 	    if (resp == false) {
 1023: 		return false;
 1024: 	    }
 1025: 	}
 1026: 
 1027: 	formname.submit();
 1028:     }
 1029: </script>
 1030: SUBJAVASCRIPT
 1031: }
 1032: 
 1033: #--- javascript for essay type problem --
 1034: sub sub_page_kw_js {
 1035:     my $request = shift;
 1036:     my $iconpath = $request->dir_config('lonIconsURL');
 1037:     &commonJSfunctions($request);
 1038:     my $docopen=&Apache::lonhtmlcommon::javascript_docopen();
 1039:     $docopen=~s/^document\.//;
 1040:     $request->print(<<SUBJAVASCRIPT);
 1041: <script type="text/javascript" language="javascript">
 1042: 
 1043: //===================== Show list of keywords ====================
 1044:   function keywords(formname) {
 1045:     var nret = prompt("Keywords list, separated by a space. Add/delete to list if desired.",formname.keywords.value);
 1046:     if (nret==null) return;
 1047:     formname.keywords.value = nret;
 1048: 
 1049:     if (formname.keywords.value != "") {
 1050: 	formname.refresh.value = "on";
 1051: 	formname.submit();
 1052:     }
 1053:     return;
 1054:   }
 1055: 
 1056: //===================== Script to view submitted by ==================
 1057:   function viewSubmitter(submitter) {
 1058:     document.SCORE.refresh.value = "on";
 1059:     document.SCORE.NCT.value = "1";
 1060:     document.SCORE.unamedom0.value = submitter;
 1061:     document.SCORE.submit();
 1062:     return;
 1063:   }
 1064: 
 1065: //===================== Script to add keyword(s) ==================
 1066:   function getSel() {
 1067:     if (document.getSelection) txt = document.getSelection();
 1068:     else if (document.selection) txt = document.selection.createRange().text;
 1069:     else return;
 1070:     var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");
 1071:     if (cleantxt=="") {
 1072: 	alert("Please select a word or group of words from document and then click this link.");
 1073: 	return;
 1074:     }
 1075:     var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);
 1076:     if (nret==null) return;
 1077:     document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret;
 1078:     if (document.SCORE.keywords.value != "") {
 1079: 	document.SCORE.refresh.value = "on";
 1080: 	document.SCORE.submit();
 1081:     }
 1082:     return;
 1083:   }
 1084: 
 1085: //====================== Script for composing message ==============
 1086:    // preload images
 1087:    img1 = new Image();
 1088:    img1.src = "$iconpath/mailbkgrd.gif";
 1089:    img2 = new Image();
 1090:    img2.src = "$iconpath/mailto.gif";
 1091: 
 1092:   function msgCenter(msgform,usrctr,fullname) {
 1093:     var Nmsg  = msgform.savemsgN.value;
 1094:     savedMsgHeader(Nmsg,usrctr,fullname);
 1095:     var subject = msgform.msgsub.value;
 1096:     var msgchk = document.SCORE["includemsg"+usrctr].value;
 1097:     re = /msgsub/;
 1098:     var shwsel = "";
 1099:     if (re.test(msgchk)) { shwsel = "checked" }
 1100:     subject = (document.SCORE.shownSub.value == 0 ? checkEntities(subject) : subject);
 1101:     displaySubject(checkEntities(subject),shwsel);
 1102:     for (var i=1; i<=Nmsg; i++) {
 1103: 	var testmsg = "savemsg"+i+",";
 1104: 	re = new RegExp(testmsg,"g");
 1105: 	shwsel = "";
 1106: 	if (re.test(msgchk)) { shwsel = "checked" }
 1107: 	var message = document.SCORE["savemsg"+i].value;
 1108: 	message = (document.SCORE["shownOnce"+i].value == 0 ? checkEntities(message) : message);
 1109: 	displaySavedMsg(i,message,shwsel); //I do not get it. w/o checkEntities on saved messages,
 1110: 	                                   //any &lt; is already converted to <, etc. However, only once!!
 1111:     }
 1112:     newmsg = document.SCORE["newmsg"+usrctr].value;
 1113:     shwsel = "";
 1114:     re = /newmsg/;
 1115:     if (re.test(msgchk)) { shwsel = "checked" }
 1116:     newMsg(newmsg,shwsel);
 1117:     msgTail(); 
 1118:     return;
 1119:   }
 1120: 
 1121:   function checkEntities(strx) {
 1122:     if (strx.length == 0) return strx;
 1123:     var orgStr = ["&", "<", ">", '"']; 
 1124:     var newStr = ["&amp;", "&lt;", "&gt;", "&quot;"];
 1125:     var counter = 0;
 1126:     while (counter < 4) {
 1127: 	strx = strReplace(strx,orgStr[counter],newStr[counter]);
 1128: 	counter++;
 1129:     }
 1130:     return strx;
 1131:   }
 1132: 
 1133:   function strReplace(strx, orgStr, newStr) {
 1134:     return strx.split(orgStr).join(newStr);
 1135:   }
 1136: 
 1137:   function savedMsgHeader(Nmsg,usrctr,fullname) {
 1138:     var height = 70*Nmsg+250;
 1139:     var scrollbar = "no";
 1140:     if (height > 600) {
 1141: 	height = 600;
 1142: 	scrollbar = "yes";
 1143:     }
 1144:     var xpos = (screen.width-600)/2;
 1145:     xpos = (xpos < 0) ? '0' : xpos;
 1146:     var ypos = (screen.height-height)/2-30;
 1147:     ypos = (ypos < 0) ? '0' : ypos;
 1148: 
 1149:     pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars='+scrollbar+',screenx='+xpos+',screeny='+ypos+',width=600,height='+height);
 1150:     pWin.focus();
 1151:     pDoc = pWin.document;
 1152:     pDoc.$docopen;
 1153:     pDoc.write("<html><head>");
 1154:     pDoc.write("<title>Message Central</title>");
 1155: 
 1156:     pDoc.write("<script language=javascript>");
 1157:     pDoc.write("function checkInput() {");
 1158:     pDoc.write("  opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value);");
 1159:     pDoc.write("  var nmsg   = opener.document.SCORE.savemsgN.value;");
 1160:     pDoc.write("  var usrctr = document.msgcenter.usrctr.value;");
 1161:     pDoc.write("  var newval = opener.document.SCORE[\\"newmsg\\"+usrctr];");
 1162:     pDoc.write("  newval.value = opener.checkEntities(document.msgcenter.newmsg.value);");
 1163: 
 1164:     pDoc.write("  var msgchk = \\"\\";");
 1165:     pDoc.write("  if (document.msgcenter.subchk.checked) {");
 1166:     pDoc.write("     msgchk = \\"msgsub,\\";");
 1167:     pDoc.write("  }");
 1168:     pDoc.write("  var includemsg = 0;");
 1169:     pDoc.write("  for (var i=1; i<=nmsg; i++) {");
 1170:     pDoc.write("      var opnmsg = opener.document.SCORE[\\"savemsg\\"+i];");
 1171:     pDoc.write("      var frmmsg = document.msgcenter[\\"msg\\"+i];");
 1172:     pDoc.write("      opnmsg.value = opener.checkEntities(frmmsg.value);");
 1173:     pDoc.write("      var showflg = opener.document.SCORE[\\"shownOnce\\"+i];");
 1174:     pDoc.write("      showflg.value = \\"1\\";");
 1175:     pDoc.write("      var chkbox = document.msgcenter[\\"msgn\\"+i];");
 1176:     pDoc.write("      if (chkbox.checked) {");
 1177:     pDoc.write("         msgchk += \\"savemsg\\"+i+\\",\\";");
 1178:     pDoc.write("         includemsg = 1;");
 1179:     pDoc.write("      }");
 1180:     pDoc.write("  }");
 1181:     pDoc.write("  if (document.msgcenter.newmsgchk.checked) {");
 1182:     pDoc.write("     msgchk += \\"newmsg\\"+usrctr;");
 1183:     pDoc.write("     includemsg = 1;");
 1184:     pDoc.write("  }");
 1185:     pDoc.write("  imgformname = opener.document.SCORE[\\"mailicon\\"+usrctr];");
 1186:     pDoc.write("  imgformname.src = \\"$iconpath/\\"+((includemsg) ? \\"mailto.gif\\" : \\"mailbkgrd.gif\\");");
 1187:     pDoc.write("  var includemsg = opener.document.SCORE[\\"includemsg\\"+usrctr];");
 1188:     pDoc.write("  includemsg.value = msgchk;");
 1189: 
 1190:     pDoc.write("  self.close()");
 1191: 
 1192:     pDoc.write("}");
 1193: 
 1194:     pDoc.write("<");
 1195:     pDoc.write("/script>");
 1196: 
 1197:     pDoc.write("</head><body bgcolor=white>");
 1198: 
 1199:     pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");
 1200:     pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");
 1201:     pDoc.write("<font color=\\"green\\" size=+1>&nbsp;Compose Message for \"+fullname+\"</font><br><br>");
 1202: 
 1203:     pDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
 1204:     pDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
 1205:     pDoc.write("<td><b>Type</b></td><td><b>Include</b></td><td><b>Message</td></tr>");
 1206: }
 1207:     function displaySubject(msg,shwsel) {
 1208:     pDoc = pWin.document;
 1209:     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
 1210:     pDoc.write("<td>Subject</td>");
 1211:     pDoc.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"></td>");
 1212:     pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"60\\" maxlength=\\"80\\"></td></tr>");
 1213: }
 1214: 
 1215:   function displaySavedMsg(ctr,msg,shwsel) {
 1216:     pDoc = pWin.document;
 1217:     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
 1218:     pDoc.write("<td align=\\"center\\">"+ctr+"</td>");
 1219:     pDoc.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"></td>");
 1220:     pDoc.write("<td><textarea name=\\"msg"+ctr+"\\" cols=\\"60\\" rows=\\"3\\">"+msg+"</textarea></td></tr>");
 1221: }
 1222: 
 1223:   function newMsg(newmsg,shwsel) {
 1224:     pDoc = pWin.document;
 1225:     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
 1226:     pDoc.write("<td align=\\"center\\">New</td>");
 1227:     pDoc.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"></td>");
 1228:     pDoc.write("<td><textarea name=\\"newmsg\\" cols=\\"60\\" rows=\\"3\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" >"+newmsg+"</textarea></td></tr>");
 1229: }
 1230: 
 1231:   function msgTail() {
 1232:     pDoc = pWin.document;
 1233:     pDoc.write("</table>");
 1234:     pDoc.write("</td></tr></table>&nbsp;");
 1235:     pDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");
 1236:     pDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br><br>");
 1237:     pDoc.write("</form>");
 1238:     pDoc.write("</body></html>");
 1239:     pDoc.close();
 1240: }
 1241: 
 1242: //====================== Script for keyword highlight options ==============
 1243:   function kwhighlight() {
 1244:     var kwclr    = document.SCORE.kwclr.value;
 1245:     var kwsize   = document.SCORE.kwsize.value;
 1246:     var kwstyle  = document.SCORE.kwstyle.value;
 1247:     var redsel = "";
 1248:     var grnsel = "";
 1249:     var blusel = "";
 1250:     if (kwclr=="red")   {var redsel="checked"};
 1251:     if (kwclr=="green") {var grnsel="checked"};
 1252:     if (kwclr=="blue")  {var blusel="checked"};
 1253:     var sznsel = "";
 1254:     var sz1sel = "";
 1255:     var sz2sel = "";
 1256:     if (kwsize=="0")  {var sznsel="checked"};
 1257:     if (kwsize=="+1") {var sz1sel="checked"};
 1258:     if (kwsize=="+2") {var sz2sel="checked"};
 1259:     var synsel = "";
 1260:     var syisel = "";
 1261:     var sybsel = "";
 1262:     if (kwstyle=="")    {var synsel="checked"};
 1263:     if (kwstyle=="<i>") {var syisel="checked"};
 1264:     if (kwstyle=="<b>") {var sybsel="checked"};
 1265:     highlightCentral();
 1266:     highlightbody('red','red',redsel,'0','normal',sznsel,'','normal',synsel);
 1267:     highlightbody('green','green',grnsel,'+1','+1',sz1sel,'<i>','italic',syisel);
 1268:     highlightbody('blue','blue',blusel,'+2','+2',sz2sel,'<b>','bold',sybsel);
 1269:     highlightend();
 1270:     return;
 1271:   }
 1272: 
 1273:   function highlightCentral() {
 1274: //    if (window.hwdWin) window.hwdWin.close();
 1275:     var xpos = (screen.width-400)/2;
 1276:     xpos = (xpos < 0) ? '0' : xpos;
 1277:     var ypos = (screen.height-330)/2-30;
 1278:     ypos = (ypos < 0) ? '0' : ypos;
 1279: 
 1280:     hwdWin = window.open('', 'KeywordHighlightCentral', 'resizeable=yes,toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx='+xpos+',screeny='+ypos);
 1281:     hwdWin.focus();
 1282:     var hDoc = hwdWin.document;
 1283:     hDoc.$docopen;
 1284:     hDoc.write("<html><head>");
 1285:     hDoc.write("<title>Highlight Central</title>");
 1286: 
 1287:     hDoc.write("<script language=javascript>");
 1288:     hDoc.write("function updateChoice(flag) {");
 1289:     hDoc.write("  opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr);");
 1290:     hDoc.write("  opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize);");
 1291:     hDoc.write("  opener.document.SCORE.kwstyle.value = opener.radioSelection(document.hlCenter.kwdstyle);");
 1292:     hDoc.write("  opener.document.SCORE.refresh.value = \\"on\\";");
 1293:     hDoc.write("  if (opener.document.SCORE.keywords.value!=\\"\\"){");
 1294:     hDoc.write("     opener.document.SCORE.submit();");
 1295:     hDoc.write("  }");
 1296:     hDoc.write("  self.close()");
 1297:     hDoc.write("}");
 1298: 
 1299:     hDoc.write("<");
 1300:     hDoc.write("/script>");
 1301: 
 1302:     hDoc.write("</head><body bgcolor=white>");
 1303: 
 1304:     hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");
 1305:     hDoc.write("<font color=\\"green\\" size=+1>&nbsp;Keyword Highlight Options</font><br><br>");
 1306: 
 1307:     hDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
 1308:     hDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
 1309:     hDoc.write("<td><b>Text Color</b></td><td><b>Font Size</b></td><td><b>Font Style</td></tr>");
 1310:   }
 1311: 
 1312:   function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) { 
 1313:     var hDoc = hwdWin.document;
 1314:     hDoc.write("<tr bgcolor=\\"#ffffdd\\">");
 1315:     hDoc.write("<td align=\\"left\\">");
 1316:     hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+">&nbsp;"+clrtxt+"</td>");
 1317:     hDoc.write("<td align=\\"left\\">");
 1318:     hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+">&nbsp;"+sztxt+"</td>");
 1319:     hDoc.write("<td align=\\"left\\">");
 1320:     hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+">&nbsp;"+sytxt+"</td>");
 1321:     hDoc.write("</tr>");
 1322:   }
 1323: 
 1324:   function highlightend() { 
 1325:     var hDoc = hwdWin.document;
 1326:     hDoc.write("</table>");
 1327:     hDoc.write("</td></tr></table>&nbsp;");
 1328:     hDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:updateChoice(1)\\">&nbsp;&nbsp;");
 1329:     hDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br><br>");
 1330:     hDoc.write("</form>");
 1331:     hDoc.write("</body></html>");
 1332:     hDoc.close();
 1333:   }
 1334: 
 1335: </script>
 1336: SUBJAVASCRIPT
 1337: }
 1338: 
 1339: #--- displays the grading box, used in essay type problem and grading by page/sequence
 1340: sub gradeBox {
 1341:     my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;
 1342: 
 1343:     my $checkIcon = '<img src="'.$request->dir_config('lonIconsURL').
 1344: 	'/check.gif" height="16" border="0" />';
 1345: 
 1346:     my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);
 1347:     my $wgtmsg = ($wgt > 0 ? '(problem weight)' : 
 1348: 		  '<font color="red">problem weight assigned by computer</font>');
 1349:     $wgt       = ($wgt > 0 ? $wgt : '1');
 1350:     my $score  = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
 1351: 		  '' : $$record{'resource.'.$partid.'.awarded'}*$wgt);
 1352:     my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
 1353: 
 1354:     my $display_part=&get_display_part($partid,undef,$symb);
 1355:     my @partids = ("$partid");
 1356:     my %last_resets = ();
 1357:     &get_last_resets($symb,$env{'request.course.id'},\%last_resets,\@partids);
 1358:     my $aggtries;
 1359:     if ($last_resets{$partid}) {
 1360:         $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
 1361:     } else {
 1362:         $aggtries = $$record{'resource.'.$partid.'.tries'};
 1363:     }
 1364:     $result.='<table border="0"><tr><td>'.
 1365: 	'<b>Part: </b>'.$display_part.' <b>Points: </b></td><td>'."\n";
 1366: 
 1367:     my $ctr = 0;
 1368:     $result.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across
 1369:     while ($ctr<=$wgt) {
 1370: 	$result.= '<td><nobr><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
 1371: 	    'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.
 1372: 	    $ctr.')" value="'.$ctr.'" '.
 1373: 	    ($score eq $ctr ? 'checked':'').' /> '.$ctr."</nobr></td>\n";
 1374: 	$result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
 1375: 	$ctr++;
 1376:     }
 1377:     $result.='</tr></table>';
 1378: 
 1379:     $result.='</td><td>&nbsp;<b>or</b>&nbsp;</td>'."\n";
 1380:     $result.='<td><input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
 1381: 	($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
 1382: 	'onChange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','.
 1383: 	$wgt.')" /></td>'."\n";
 1384:     $result.='<td>/'.$wgt.' '.$wgtmsg.
 1385: 	($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? '&nbsp;'.$checkIcon : '').
 1386: 	' </td><td>'."\n";
 1387: 
 1388:     $result.='<select name="GD_SEL'.$counter.'_'.$partid.'" '.
 1389: 	'onChange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";
 1390:     if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
 1391: 	$result.='<option> </option>'.
 1392: 	    '<option selected="on">excused</option>';
 1393:     } else {
 1394: 	$result.='<option selected="on"> </option>'.
 1395: 	    '<option>excused</option>';
 1396:     }
 1397:     $result.='<option>reset status</option></select>'."\n";
 1398:     $result.="&nbsp&nbsp\n";
 1399:     $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n".
 1400: 	'<input type="hidden" name="oldpts'.$counter.'_'.$partid.'" value="'.$score.'" />'."\n".
 1401: 	'<input type="hidden" name="solved'.$counter.'_'.$partid.'" value="'.
 1402: 	$$record{'resource.'.$partid.'.solved'}.'" />'."\n".
 1403:         '<input type="hidden" name="totaltries'.$counter.'_'.$partid.'" value="'.
 1404:         $$record{'resource.'.$partid.'.tries'}.'" />'."\n".
 1405:         '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.
 1406:         $aggtries.'" />'."\n";
 1407:     $result.='</td></tr></table>'."\n";
 1408:     return $result;
 1409: }
 1410: 
 1411: sub show_problem {
 1412:     my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode) = @_;
 1413:     my $rendered;
 1414:     if ($mode eq 'both' or $mode eq 'text') {
 1415: 	$rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,
 1416: 					     $env{'request.course.id'});
 1417:     }
 1418:     if ($removeform) {
 1419: 	$rendered=~s|<form(.*?)>||g;
 1420: 	$rendered=~s|</form>||g;
 1421: 	$rendered=~s|name="submit"|name="would_have_been_submit"|g;
 1422:     }
 1423:     my $companswer;
 1424:     if ($mode eq 'both' or $mode eq 'answer') {
 1425: 	$companswer=&Apache::loncommon::get_student_answers($symb,$uname,$udom,
 1426: 						    $env{'request.course.id'});
 1427:     }
 1428:     if ($removeform) {
 1429: 	$companswer=~s|<form(.*?)>||g;
 1430: 	$companswer=~s|</form>||g;
 1431: 	$companswer=~s|name="submit"|name="would_have_been_submit"|g;
 1432:     }
 1433:     my $result.='<table border="0" width="100%"><tr><td bgcolor="#777777">';
 1434:     $result.='<table border="0" width="100%">';
 1435:     if ($viewon) {
 1436: 	$result.='<tr><td bgcolor="#e6ffff"><b> ';
 1437: 	if ($mode eq 'both' or $mode eq 'text') {
 1438: 	    $result.='View of the problem - ';
 1439: 	} else {
 1440: 	    $result.='Correct answer: ';
 1441: 	}
 1442: 	$result.=$env{'form.fullname'}.'</b></td></tr>';
 1443:     }
 1444:     if ($mode eq 'both') {
 1445: 	$result.='<tr><td bgcolor="#ffffff">'.$rendered.'<br />';
 1446: 	$result.='<b>Correct answer:</b><br />'.$companswer;
 1447:     } elsif ($mode eq 'text') {
 1448: 	$result.='<tr><td bgcolor="#ffffff">'.$rendered;
 1449:     } elsif ($mode eq 'answer') {
 1450: 	$result.='<tr><td bgcolor="#ffffff">'.$companswer;
 1451:     }
 1452:     $result.='</td></tr></table>';
 1453:     $result.='</td></tr></table><br />';
 1454:     return $result;
 1455: }
 1456: 
 1457: # --------------------------- show submissions of a student, option to grade 
 1458: sub submission {
 1459:     my ($request,$counter,$total) = @_;
 1460: 
 1461:     (my $url=$env{'form.url'})=~s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
 1462:     my ($uname,$udom)     = ($env{'form.student'},$env{'form.userdom'});
 1463:     $udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student?
 1464:     my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
 1465:     $env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq '';
 1466: 
 1467:     my $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url)));
 1468:     if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; }
 1469: 
 1470:     if (!&canview($usec)) {
 1471: 	$request->print('<font color="red">Unable to view requested student.('.
 1472: 			$uname.'@'.$udom.' in section '.$usec.' in course id '.
 1473: 			$env{'request.course.id'}.')</font>');
 1474: 	$request->print(&show_grading_menu_form($symb,$url));
 1475: 	return;
 1476:     }
 1477: 
 1478:     if (!$env{'form.lastSub'}) { $env{'form.lastSub'} = 'datesub'; }
 1479:     if (!$env{'form.vProb'}) { $env{'form.vProb'} = 'yes'; }
 1480:     if (!$env{'form.vAns'}) { $env{'form.vAns'} = 'yes'; }
 1481:     my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
 1482:     my $checkIcon = '<img src="'.$request->dir_config('lonIconsURL').
 1483: 	'/check.gif" height="16" border="0" />';
 1484: 
 1485:     # header info
 1486:     if ($counter == 0) {
 1487: 	&sub_page_js($request);
 1488: 	&sub_page_kw_js($request) if ($env{'form.handgrade'} eq 'yes');
 1489: 	$env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 
 1490: 	    &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
 1491: 
 1492: 	$request->print('<h3>&nbsp;<font color="#339933">Submission Record</font></h3>'."\n".
 1493: 			'<font size=+1>&nbsp;<b>Resource: </b>'.$env{'form.probTitle'}.'</font>'."\n");
 1494: 
 1495: 	if ($env{'form.handgrade'} eq 'no') {
 1496: 	    my $checkMark='<br /><br />&nbsp;<b>Note:</b> Part(s) graded correct by the computer is marked with a '.
 1497: 		$checkIcon.' symbol.'."\n";
 1498: 	    $request->print($checkMark);
 1499: 	}
 1500: 
 1501: 	# option to display problem, only once else it cause problems 
 1502:         # with the form later since the problem has a form.
 1503: 	if ($env{'form.vProb'} eq 'yes' or $env{'form.vAns'} eq 'yes') {
 1504: 	    my $mode;
 1505: 	    if ($env{'form.vProb'} eq 'yes' && $env{'form.vAns'} eq 'yes') {
 1506: 		$mode='both';
 1507: 	    } elsif ($env{'form.vProb'} eq 'yes') {
 1508: 		$mode='text';
 1509: 	    } elsif ($env{'form.vAns'} eq 'yes') {
 1510: 		$mode='answer';
 1511: 	    }
 1512: 	    $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode));
 1513: 	}
 1514: 	
 1515: 	# kwclr is the only variable that is guaranteed to be non blank 
 1516:         # if this subroutine has been called once.
 1517: 	my %keyhash = ();
 1518: 	if ($env{'form.kwclr'} eq '' && $env{'form.handgrade'} eq 'yes') {
 1519: 	    %keyhash = &Apache::lonnet::dump('nohist_handgrade',
 1520: 					     $env{'course.'.$env{'request.course.id'}.'.domain'},
 1521: 					     $env{'course.'.$env{'request.course.id'}.'.num'});
 1522: 
 1523: 	    my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
 1524: 	    $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
 1525: 	    $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
 1526: 	    $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
 1527: 	    $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
 1528: 	    $env{'form.msgsub'}   = $keyhash{$symb.'_subject'} ne '' ? 
 1529: 		$keyhash{$symb.'_subject'} : $env{'form.probTitle'};
 1530: 	    $env{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0';
 1531: 	}
 1532: 	my $overRideScore = $env{'form.overRideScore'} eq '' ? 'no' : $env{'form.overRideScore'};
 1533: 
 1534: 	$request->print('<form action="/adm/grades" method="post" name="SCORE">'."\n".
 1535: 			'<input type="hidden" name="command"    value="handgrade" />'."\n".
 1536: 			'<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
 1537: 			'<input type="hidden" name="Status"     value="'.$env{'form.Status'}.'" />'."\n".
 1538: 			'<input type="hidden" name="overRideScore" value="'.$overRideScore.'" />'."\n".
 1539: 			'<input type="hidden" name="probTitle"  value="'.$env{'form.probTitle'}.'" />'."\n".
 1540: 			'<input type="hidden" name="refresh"    value="off" />'."\n".
 1541: 			'<input type="hidden" name="studentNo"  value="" />'."\n".
 1542: 			'<input type="hidden" name="gradeOpt"   value="" />'."\n".
 1543: 			'<input type="hidden" name="symb"       value="'.$symb.'" />'."\n".
 1544: 			'<input type="hidden" name="url"        value="'.$url.'" />'."\n".
 1545: 			'<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" />'."\n".
 1546: 			'<input type="hidden" name="vProb"      value="'.$env{'form.vProb'}.'" />'."\n".
 1547: 			'<input type="hidden" name="vAns"       value="'.$env{'form.vAns'}.'" />'."\n".
 1548: 			'<input type="hidden" name="lastSub"    value="'.$env{'form.lastSub'}.'" />'."\n".
 1549: 			'<input type="hidden" name="section"    value="'.$env{'form.section'}.'">'."\n".
 1550: 			'<input type="hidden" name="submitonly" value="'.$env{'form.submitonly'}.'">'."\n".
 1551: 			'<input type="hidden" name="handgrade"  value="'.$env{'form.handgrade'}.'">'."\n".
 1552: 			'<input type="hidden" name="NCT"'.
 1553: 			' value="'.($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : $total+1).'" />'."\n");
 1554: 	if ($env{'form.handgrade'} eq 'yes') {
 1555: 	    $request->print('<input type="hidden" name="keywords" value="'.$env{'form.keywords'}.'" />'."\n".
 1556: 			    '<input type="hidden" name="kwclr"    value="'.$env{'form.kwclr'}.'" />'."\n".
 1557: 			    '<input type="hidden" name="kwsize"   value="'.$env{'form.kwsize'}.'" />'."\n".
 1558: 			    '<input type="hidden" name="kwstyle"  value="'.$env{'form.kwstyle'}.'" />'."\n".
 1559: 			    '<input type="hidden" name="msgsub"   value="'.$env{'form.msgsub'}.'" />'."\n".
 1560: 			    '<input type="hidden" name="shownSub" value="0" />'."\n".
 1561: 			    '<input type="hidden" name="savemsgN" value="'.$env{'form.savemsgN'}.'" />'."\n");
 1562: 	    foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) {
 1563: 		$request->print('<input type="hidden" name="vPart" value="'.$partid.'" />'."\n");
 1564: 	    }
 1565: 	}
 1566: 	
 1567: 	my ($cts,$prnmsg) = (1,'');
 1568: 	while ($cts <= $env{'form.savemsgN'}) {
 1569: 	    $prnmsg.='<input type="hidden" name="savemsg'.$cts.'" value="'.
 1570: 		(!exists($keyhash{$symb.'_savemsg'.$cts}) ? 
 1571: 		 &Apache::lonfeedback::clear_out_html($env{'form.savemsg'.$cts}) :
 1572: 		 &Apache::lonfeedback::clear_out_html($keyhash{$symb.'_savemsg'.$cts})).
 1573: 		'" />'."\n".
 1574: 		'<input type="hidden" name="shownOnce'.$cts.'" value="0" />'."\n";
 1575: 	    $cts++;
 1576: 	}
 1577: 	$request->print($prnmsg);
 1578: 
 1579: 	if ($env{'form.handgrade'} eq 'yes' && $env{'form.showgrading'} eq 'yes') {
 1580: #
 1581: # Print out the keyword options line
 1582: #
 1583: 	    $request->print(<<KEYWORDS);
 1584: &nbsp;<b>Keyword Options:</b>&nbsp;
 1585: <a href="javascript:keywords(document.SCORE)"; TARGET=_self>List</a>&nbsp; &nbsp;
 1586: <a href="#" onMouseDown="javascript:getSel(); return false"
 1587:  CLASS="page">Paste Selection to List</a>&nbsp; &nbsp;
 1588: <a href="javascript:kwhighlight()"; TARGET=_self>Highlight Attribute</a><br /><br />
 1589: KEYWORDS
 1590: #
 1591: # Load the other essays for similarity check
 1592: #
 1593:             my $essayurl=&Apache::lonnet::declutter($url);
 1594: 	    my ($adom,$aname,$apath)=($essayurl=~/^(\w+)\/(\w+)\/(.*)$/);
 1595: 	    $apath=&Apache::lonnet::escape($apath);
 1596: 	    $apath=~s/\W/\_/gs;
 1597: 	    %oldessays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
 1598:         }
 1599:     }
 1600: 
 1601:     if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {
 1602: 	$request->print('<br /><br /><br />') if ($counter > 0);
 1603: 	my $mode;
 1604: 	if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {
 1605: 	    $mode='both';
 1606: 	} elsif ($env{'form.vProb'} eq 'all' ) {
 1607: 	    $mode='text';
 1608: 	} elsif ($env{'form.vAns'} eq 'all') {
 1609: 	    $mode='answer';
 1610: 	}
 1611: 	$request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode));
 1612:     }
 1613: 
 1614:     my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
 1615:     my ($partlist,$handgrade,$responseType) = &response_type($url,$symb);
 1616: 
 1617:     # Display student info
 1618:     $request->print(($counter == 0 ? '' : '<br />'));
 1619:     my $result='<table border="0" width=100%><tr><td bgcolor="#777777">'."\n".
 1620: 	'<table border="0" width=100%><tr bgcolor="#edffff"><td>'."\n";
 1621: 
 1622:     $result.='<b>Fullname: </b>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'<br />'."\n";
 1623:     $result.='<input type="hidden" name="name'.$counter.
 1624: 	'" value="'.$env{'form.fullname'}.'" />'."\n";
 1625: 
 1626:     # If any part of the problem is an essay-response (handgraded), then check for collaborators
 1627:     my @col_fullnames;
 1628:     my ($classlist,$fullname);
 1629:     if ($env{'form.handgrade'} eq 'yes') {
 1630: 	($classlist,undef,$fullname) = &getclasslist('all','0');
 1631: 	for (keys (%$handgrade)) {
 1632: 	    my $ncol = &Apache::lonnet::EXT('resource.'.$_.
 1633: 					    '.maxcollaborators',
 1634:                                             $symb,$udom,$uname);
 1635: 	    next if ($ncol <= 0);
 1636:             s/\_/\./g;
 1637:             next if ($record{'resource.'.$_.'.collaborators'} eq '');
 1638:             my @goodcollaborators = ();
 1639:             my @badcollaborators  = ();
 1640: 	    foreach (split(/,?\s+/,$record{'resource.'.$_.'.collaborators'})) { 
 1641: 		$_ =~ s/[\$\^\(\)]//g;
 1642: 		next if ($_ eq '');
 1643: 		my ($co_name,$co_dom) = split /\@|:/,$_;
 1644: 		$co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i);
 1645: 		next if ($co_name eq $uname && $co_dom eq $udom);
 1646: 		# Doing this grep allows 'fuzzy' specification
 1647: 		my @Matches = grep /^$co_name:$co_dom$/i,keys %$classlist;
 1648: 		if (! scalar(@Matches)) {
 1649: 		    push @badcollaborators,$_;
 1650: 		} else {
 1651: 		    push @goodcollaborators, @Matches;
 1652: 		}
 1653: 	    }
 1654:             if (scalar(@goodcollaborators) != 0) {
 1655:                 $result.='<b>Collaborators: </b>';
 1656:                 foreach (@goodcollaborators) {
 1657: 		    my ($lastname,$givenn) = split(/,/,$$fullname{$_});
 1658: 		    push @col_fullnames, $givenn.' '.$lastname;
 1659: 		    $result.=$$fullname{$_}.'&nbsp; &nbsp; &nbsp;';
 1660: 		}
 1661:                 $result.='<br />'."\n";
 1662: 		my ($part)=split(/\./,$_);
 1663: 		$result.='<input type="hidden" name="collaborator'.$counter.
 1664: 		    '" value="'.$part.':'.(join ':',@goodcollaborators).'" />'.
 1665: 		    "\n";
 1666: 	    }
 1667: 	    if (scalar(@badcollaborators) > 0) {
 1668: 		$result.='<table border="0"><tr bgcolor="#ffbbbb"><td>';
 1669: 		$result.='This student has submitted ';
 1670: 		$result.=(scalar(@badcollaborators) == 1) ? 'an invalid collaborator' : 'invalid collaborators';
 1671: 		$result .= ': '.join(', ',@badcollaborators);
 1672: 		$result .= '</td></tr></table>';
 1673: 	    }         
 1674: 	    if (scalar(@badcollaborators > $ncol)) {
 1675: 		$result .= '<table border="0"><tr bgcolor="#ffbbbb"><td>';
 1676: 		$result .= 'This student has submitted too many '.
 1677: 		    'collaborators.  Maximum is '.$ncol.'.';
 1678: 		$result .= '</td></tr></table>';
 1679: 	    }
 1680: 	}
 1681:     }
 1682:     $request->print($result."\n");
 1683: 
 1684:     # print student answer/submission
 1685:     # Options are (1) Handgaded submission only
 1686:     #             (2) Last submission, includes submission that is not handgraded 
 1687:     #                  (for multi-response type part)
 1688:     #             (3) Last submission plus the parts info
 1689:     #             (4) The whole record for this student
 1690:     if ($env{'form.lastSub'} =~ /^(lastonly|hdgrade)$/) {
 1691: 	my ($string,$timestamp)= &get_last_submission(\%record);
 1692: 	my $lastsubonly=''.
 1693: 	    ($$timestamp eq '' ? '' : '<b>Date Submitted:</b> '.
 1694: 	     $$timestamp)."</td></tr>\n";
 1695: 	if ($$timestamp eq '') {
 1696: 	    $lastsubonly.='<tr><td bgcolor="#ffffe6">'.$$string[0]; 
 1697: 	} else {
 1698: 	    my %seenparts;
 1699: 	    for my $part (sort keys(%$handgrade)) {
 1700: 		my ($partid,$respid) = split(/_/,$part);
 1701: 		my $display_part=&get_display_part($partid,$url,$symb);
 1702: 		if ($env{"form.$uname:$udom:$partid:submitted_by"}) {
 1703: 		    if (exists($seenparts{$partid})) { next; }
 1704: 		    $seenparts{$partid}=1;
 1705: 		    my $submitby='<b>Part:</b> '.$display_part.
 1706: 			' <b>Collaborative submission by:</b> '.
 1707: 			'<a href="javascript:viewSubmitter(\''.
 1708: 			$env{"form.$uname:$udom:$partid:submitted_by"}.
 1709: 			'\')"; TARGET=_self>'.
 1710: 			$$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a><br />';
 1711: 		    $request->print($submitby);
 1712: 		    next;
 1713: 		}
 1714: 		my $responsetype = $responseType->{$partid}->{$respid};
 1715: 		if (!exists($record{"resource.$partid.$respid.submission"})) {
 1716: 		    $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '.
 1717: 			$display_part.' <font color="#999999">( ID '.$respid.
 1718: 			' )</font>&nbsp; &nbsp;'.
 1719: 			'<font color="red">Nothing submitted - no attempts</font><br /><br />';
 1720: 		    next;
 1721: 		}
 1722: 		foreach (@$string) {
 1723: 		    my ($partid,$respid) = /^resource\.([^\.]*)\.([^\.]*)\.submission/;
 1724: 		    if ($part ne ($partid.'_'.$respid)) { next; }
 1725: 		    my ($ressub,$subval) = split(/:/,$_,2);
 1726: 		    # Similarity check
 1727: 		    my $similar='';
 1728: 		    if($env{'form.checkPlag'}){
 1729: 			my ($oname,$odom,$ocrsid,$oessay,$osim)=
 1730: 			    &most_similar($uname,$udom,$subval);
 1731: 			if ($osim) {
 1732: 			    $osim=int($osim*100.0);
 1733: 			    $similar="<hr /><h3><font color=\"#FF0000\">Essay".
 1734: 				" is $osim% similar to an essay by ".
 1735: 				&Apache::loncommon::plainname($oname,$odom).
 1736: 				'</font></h3><blockquote><i>'.
 1737: 				&keywords_highlight($oessay).
 1738: 				'</i></blockquote><hr />';
 1739: 			}
 1740: 		    }
 1741: 		    my $order=&get_order($partid,$respid,$symb,$uname,$udom);
 1742: 		    if ($env{'form.lastSub'} eq 'lastonly' || 
 1743: 			($env{'form.lastSub'} eq 'hdgrade' && 
 1744: 			 $$handgrade{$part} eq 'yes')) {
 1745: 			my $display_part=&get_display_part($partid,$url,$symb);
 1746: 			$lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '.
 1747: 			    $display_part.' <font color="#999999">( ID '.$respid.
 1748: 			    ' )</font>&nbsp; &nbsp;';
 1749: 			my @files;
 1750: 			if ($record{"resource.$partid.$respid.portfiles"}) {
 1751: 			    my $file_url = '/uploaded/'.$udom.'/'.$uname.'/portfolio';
 1752: 			    foreach my $file (split(',',$record{"resource.$partid.$respid.portfiles"})) {
 1753: 				push(@files,$file_url.$file);
 1754: 			    
 1755: 				&Apache::lonnet::logthis("found a portfolio file".$record{"resource.$partid.$respid.portfiles"});
 1756: 				&Apache::lonnet::logthis("uploaded URL file".$record{"resource.$partid.$respid.uploadedurl"});
 1757: 			    }
 1758: 			}
 1759: 			if ($record{"resource.$partid.$respid.uploadedurl"}) {
 1760: 			    push(@files,$record{"resource.$partid.$respid.uploadedurl"});
 1761: 			}
 1762: 			if (@files) {
 1763: 			    $lastsubonly.='<br /><font color="red" size="1">Like all files provided by users, this file may contain virusses</font><br />';
 1764: 			    foreach my $file (@files) {
 1765: 				&Apache::lonnet::allowuploaded('/adm/grades',$file);
 1766: 				$lastsubonly.='<br /><a href="'.$file.'" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border=0"> '.$file.'</a>';
 1767: 			    }
 1768: 			    $lastsubonly.='<br />';
 1769: 			}
 1770: 			$lastsubonly.='<b>Submitted Answer: </b>'.
 1771: 			    &cleanRecord($subval,$responsetype,$symb,$partid,
 1772: 					 $respid,\%record,$order);
 1773: 			if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
 1774: 		    }
 1775: 		}
 1776: 	    }
 1777: 	}
 1778: 	$lastsubonly.='</td></tr><tr bgcolor="#ffffff"><td>'."\n";
 1779: 	$request->print($lastsubonly);
 1780:     } elsif ($env{'form.lastSub'} eq 'datesub') {
 1781: 	my (undef,$responseType,undef,$parts) = &showResourceInfo($url);
 1782: 	$request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom));
 1783:     } elsif ($env{'form.lastSub'} =~ /^(last|all)$/) {
 1784: 	$request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,
 1785: 								 $env{'request.course.id'},
 1786: 								 $last,'.submission',
 1787: 								 'Apache::grades::keywords_highlight'));
 1788:     }
 1789: 
 1790:     $request->print('<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'
 1791: 	.$udom.'" />'."\n");
 1792:     
 1793:     # return if view submission with no grading option
 1794:     if ($env{'form.showgrading'} eq '' || (!&canmodify($usec))) {
 1795: 	my $toGrade.='<input type="button" value="Grade Student" '.
 1796: 	    'onClick="javascript:checksubmit(this.form,\'Grade Student\',\''
 1797: 	    .$counter.'\');" TARGET=_self> &nbsp;'."\n" if (&canmodify($usec));
 1798: 	$toGrade.='</td></tr></table></td></tr></table>'."\n";
 1799: 	if (($env{'form.command'} eq 'submission') || 
 1800: 	    ($env{'form.command'} eq 'processGroup' && $counter == $total)) {
 1801: 	    $toGrade.='</form>'.&show_grading_menu_form($symb,$url) 
 1802: 	}
 1803: 	$request->print($toGrade);
 1804: 	return;
 1805:     } else {
 1806: 	$request->print('</td></tr></table></td></tr></table>'."\n");
 1807:     }
 1808: 
 1809:     # essay grading message center
 1810:     if ($env{'form.handgrade'} eq 'yes') {
 1811: 	my ($lastname,$givenn) = split(/,/,$env{'form.fullname'});
 1812: 	my $msgfor = $givenn.' '.$lastname;
 1813: 	if (scalar(@col_fullnames) > 0) {
 1814: 	    my $lastone = pop @col_fullnames;
 1815: 	    $msgfor .= ', '.(join ', ',@col_fullnames).' and '.$lastone.'.';
 1816: 	}
 1817: 	$msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript
 1818: 	$result='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".
 1819: 	    '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n";
 1820: 	$result.='&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.
 1821: 	    ',\''.$msgfor.'\')"; TARGET=_self>'.
 1822: 	    'Compose Message to student'.(scalar(@col_fullnames) >= 1 ? 's' : '').'</a> &nbsp;'.
 1823: 	    '<img src="'.$request->dir_config('lonIconsURL').
 1824: 	    '/mailbkgrd.gif" width="14" height="10" name="mailicon'.$counter.'" />'."\n".
 1825: 	    '<br />&nbsp;(Message will be sent when you click on Save & Next below.)'."\n" 
 1826: 	    if ($env{'form.handgrade'} eq 'yes');
 1827: 	$request->print($result);
 1828:     }
 1829: 
 1830:     my %seen = ();
 1831:     my @partlist;
 1832:     my @gradePartRespid;
 1833:     for (sort keys(%$handgrade)) {
 1834: 	my ($partid,$respid) = split(/_/);
 1835: 	next if ($seen{$partid} > 0);
 1836: 	$seen{$partid}++;
 1837: 	next if ($$handgrade{$_} =~ /:no$/ && $env{'form.lastSub'} =~ /^(hdgrade)$/);
 1838: 	push @partlist,$partid;
 1839: 	push @gradePartRespid,$partid.'.'.$respid;
 1840: 
 1841: 	$request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
 1842:     }
 1843:     $result='<input type="hidden" name="partlist'.$counter.
 1844: 	'" value="'.(join ":",@partlist).'" />'."\n";
 1845:     $result.='<input type="hidden" name="gradePartRespid'.
 1846: 	'" value="'.(join ":",@gradePartRespid).'" />'."\n" if ($counter == 0);
 1847:     my $ctr = 0;
 1848:     while ($ctr < scalar(@partlist)) {
 1849: 	$result.='<input type="hidden" name="partid'.$counter.'_'.$ctr.'" value="'.
 1850: 	    $partlist[$ctr].'" />'."\n";
 1851: 	$ctr++;
 1852:     }
 1853:     $request->print($result.'</td></tr></table></td></tr></table>'."\n");
 1854: 
 1855:     # print end of form
 1856:     if ($counter == $total) {
 1857: 	my $endform='<table border="0"><tr><td>'."\n";
 1858: 	$endform.='<input type="button" value="Save & Next" '.
 1859: 	    'onClick="javascript:checksubmit(this.form,\'Save & Next\','.
 1860: 	    $total.','.scalar(@partlist).');" TARGET=_self> &nbsp;'."\n";
 1861: 	my $ntstu ='<select name="NTSTU">'.
 1862: 	    '<option>1</option><option>2</option>'.
 1863: 	    '<option>3</option><option>5</option>'.
 1864: 	    '<option>7</option><option>10</option></select>'."\n";
 1865: 	my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1');
 1866: 	$ntstu =~ s/<option>$nsel</<option selected="on">$nsel</;
 1867: 	$endform.=$ntstu.'student(s) &nbsp;&nbsp;';
 1868: 	$endform.='<input type="button" value="Previous" '.
 1869: 	    'onClick="javascript:checksubmit(this.form,\'Previous\');" TARGET=_self> &nbsp;'."\n".
 1870: 	    '<input type="button" value="Next" '.
 1871: 	    'onClick="javascript:checksubmit(this.form,\'Next\');" TARGET=_self> &nbsp;';
 1872: 	$endform.='(Next and Previous (student) do not save the scores.)'."\n" ;
 1873: 	$endform.='</td><tr></table></form>';
 1874: 	$endform.=&show_grading_menu_form($symb,$url);
 1875: 	$request->print($endform);
 1876:     }
 1877:     return '';
 1878: }
 1879: 
 1880: #--- Retrieve the last submission for all the parts
 1881: sub get_last_submission {
 1882:     my ($returnhash)=@_;
 1883:     my (@string,$timestamp);
 1884:     if ($$returnhash{'version'}) {
 1885: 	my %lasthash=();
 1886: 	my ($version);
 1887: 	for ($version=1;$version<=$$returnhash{'version'};$version++) {
 1888: 	    foreach (sort(split(/\:/,$$returnhash{$version.':keys'}))) {
 1889: 		$lasthash{$_}=$$returnhash{$version.':'.$_};
 1890: 		   $timestamp = scalar(localtime($$returnhash{$version.':timestamp'}));
 1891: 	    }
 1892: 	}
 1893: 	foreach ((keys %lasthash)) {
 1894: 	    if ($_ =~ /\.submission$/) {
 1895: 		my ($partid,$foo) = split(/submission$/,$_);
 1896: 		my $draft  = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ?
 1897: 		    '<font color="red">Draft Copy</font> ' : '';
 1898: 		push @string, (join(':',$_,$draft.$lasthash{$_}));
 1899: 	    }
 1900: 	}
 1901:     }
 1902:     @string = $string[0] eq '' ? '<font color="red">Nothing submitted - no attempts.</font>' : @string;
 1903:     return \@string,\$timestamp;
 1904: }
 1905: 
 1906: #--- High light keywords, with style choosen by user.
 1907: sub keywords_highlight {
 1908:     my $string    = shift;
 1909:     my $size      = $env{'form.kwsize'} eq '0' ? '' : 'size='.$env{'form.kwsize'};
 1910:     my $styleon   = $env{'form.kwstyle'} eq ''  ? '' : $env{'form.kwstyle'};
 1911:     (my $styleoff = $styleon) =~ s/\</\<\//;
 1912:     my @keylist   = split(/[,\s+]/,$env{'form.keywords'});
 1913:     foreach (@keylist) {
 1914: 	$string =~ s/\b\Q$_\E(\b|\.)/<font color\=$env{'form.kwclr'} $size\>$styleon$_$styleoff<\/font>/gi;
 1915:     }
 1916:     return $string;
 1917: }
 1918: 
 1919: #--- Called from submission routine
 1920: sub processHandGrade {
 1921:     my ($request) = shift;
 1922:     my $url    = $env{'form.url'};
 1923:     my $symb   = $env{'form.symb'};
 1924:     my $button = $env{'form.gradeOpt'};
 1925:     my $ngrade = $env{'form.NCT'};
 1926:     my $ntstu  = $env{'form.NTSTU'};
 1927:     if ($button eq 'Save & Next') {
 1928: 	my $ctr = 0;
 1929: 	while ($ctr < $ngrade) {
 1930: 	    my ($uname,$udom) = split(/:/,$env{'form.unamedom'.$ctr});
 1931: 	    my ($errorflag,$pts,$wgt) = &saveHandGrade($request,$url,$symb,$uname,$udom,$ctr);
 1932: 	    if ($errorflag eq 'no_score') {
 1933: 		$ctr++;
 1934: 		next;
 1935: 	    }
 1936: 	    if ($errorflag eq 'not_allowed') {
 1937: 		$request->print("<font color=\"red\">Not allowed to modify grades for $uname:$udom</font>");
 1938: 		$ctr++;
 1939: 		next;
 1940: 	    }
 1941: 	    my $includemsg = $env{'form.includemsg'.$ctr};
 1942: 	    my ($subject,$message,$msgstatus) = ('','','');
 1943: 	    if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) {
 1944: 		$subject = $env{'form.msgsub'} if ($includemsg =~ /^msgsub/);
 1945: 		my (@msgnum) = split(/,/,$includemsg);
 1946: 		foreach (@msgnum) {
 1947: 		    $message.=$env{'form.'.$_} if ($_ =~ /savemsg|newmsg/ && $_ ne '');
 1948: 		}
 1949: 		$message =&Apache::lonfeedback::clear_out_html($message);
 1950: 		$message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt;
 1951: 		$message.=" for <a href=\"".
 1952: 		    &Apache::lonnet::clutter($url).
 1953: 		    "?symb=$symb\">$env{'form.probTitle'}</a>";
 1954: 		$msgstatus = &Apache::lonmsg::user_normal_msg ($uname,$udom,
 1955: 							       $env{'form.msgsub'},$message);
 1956: 	    }
 1957: 	    if ($env{'form.collaborator'.$ctr}) {
 1958: 		my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr");
 1959: 		foreach my $collabstr (@collabstrs) {
 1960: 		    my ($part,@collaborators) = split(/:/,$collabstr);
 1961: 		    foreach (@collaborators) {
 1962: 			my ($errorflag,$pts,$wgt) = 
 1963: 			    &saveHandGrade($request,$url,$symb,$_,$udom,$ctr,
 1964: 					   $env{'form.unamedom'.$ctr},$part);
 1965: 			if ($errorflag eq 'not_allowed') {
 1966: 			    $request->print("<font color=\"red\">Not allowed to modify grades for $_:$udom</font>");
 1967: 			    next;
 1968: 			} else {
 1969: 			    if ($message ne '') {
 1970: 				$msgstatus = &Apache::lonmsg::user_normal_msg($_,$udom,$env{'form.msgsub'},$message);
 1971: 			    }
 1972: 			    
 1973: 			}
 1974: 		    }
 1975: 		}
 1976: 	    }
 1977: 	    $ctr++;
 1978: 	}
 1979:     }
 1980: 
 1981:     if ($env{'form.handgrade'} eq 'yes') {
 1982: 	# Keywords sorted in alphabatical order
 1983: 	my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
 1984: 	my %keyhash = ();
 1985: 	$env{'form.keywords'}           =~ s/,\s{0,}|\s+/ /g;
 1986: 	$env{'form.keywords'}           =~ s/^\s+|\s+$//;
 1987: 	my (@keywords) = sort(split(/\s+/,$env{'form.keywords'}));
 1988: 	$env{'form.keywords'} = join(' ',@keywords);
 1989: 	$keyhash{$symb.'_keywords'}     = $env{'form.keywords'};
 1990: 	$keyhash{$symb.'_subject'}      = $env{'form.msgsub'};
 1991: 	$keyhash{$loginuser.'_kwclr'}   = $env{'form.kwclr'};
 1992: 	$keyhash{$loginuser.'_kwsize'}  = $env{'form.kwsize'};
 1993: 	$keyhash{$loginuser.'_kwstyle'} = $env{'form.kwstyle'};
 1994: 
 1995: 	# message center - Order of message gets changed. Blank line is eliminated.
 1996: 	# New messages are saved in env for the next student.
 1997: 	# All messages are saved in nohist_handgrade.db
 1998: 	my ($ctr,$idx) = (1,1);
 1999: 	while ($ctr <= $env{'form.savemsgN'}) {
 2000: 	    if ($env{'form.savemsg'.$ctr} ne '') {
 2001: 		$keyhash{$symb.'_savemsg'.$idx} = $env{'form.savemsg'.$ctr};
 2002: 		$idx++;
 2003: 	    }
 2004: 	    $ctr++;
 2005: 	}
 2006: 	$ctr = 0;
 2007: 	while ($ctr < $ngrade) {
 2008: 	    if ($env{'form.newmsg'.$ctr} ne '') {
 2009: 		$keyhash{$symb.'_savemsg'.$idx} = $env{'form.newmsg'.$ctr};
 2010: 		$env{'form.savemsg'.$idx} = $env{'form.newmsg'.$ctr};
 2011: 		$idx++;
 2012: 	    }
 2013: 	    $ctr++;
 2014: 	}
 2015: 	$env{'form.savemsgN'} = --$idx;
 2016: 	$keyhash{$symb.'_savemsgN'} = $env{'form.savemsgN'};
 2017: 	my $putresult = &Apache::lonnet::put
 2018: 	    ('nohist_handgrade',\%keyhash,
 2019: 	     $env{'course.'.$env{'request.course.id'}.'.domain'},
 2020: 	     $env{'course.'.$env{'request.course.id'}.'.num'});
 2021:     }
 2022:     # Called by Save & Refresh from Highlight Attribute Window
 2023:     my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
 2024:     if ($env{'form.refresh'} eq 'on') {
 2025: 	my ($ctr,$total) = (0,0);
 2026: 	while ($ctr < $ngrade) {
 2027: 	    $total++ if  $env{'form.unamedom'.$ctr} ne '';
 2028: 	    $ctr++;
 2029: 	}
 2030: 	$env{'form.NTSTU'}=$ngrade;
 2031: 	$ctr = 0;
 2032: 	while ($ctr < $total) {
 2033: 	    my $processUser = $env{'form.unamedom'.$ctr};
 2034: 	    ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
 2035: 	    $env{'form.fullname'} = $$fullname{$processUser};
 2036: 	    &submission($request,$ctr,$total-1);
 2037: 	    $ctr++;
 2038: 	}
 2039: 	return '';
 2040:     }
 2041: 
 2042: # Go directly to grade student - from submission or link from chart page
 2043:     if ($button eq 'Grade Student') {
 2044: 	(undef,undef,$env{'form.handgrade'},undef,undef) = &showResourceInfo($url);
 2045: 	my $processUser = $env{'form.unamedom'.$env{'form.studentNo'}};
 2046: 	($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
 2047: 	$env{'form.fullname'} = $$fullname{$processUser};
 2048: 	&submission($request,0,0);
 2049: 	return '';
 2050:     }
 2051: 
 2052:     # Get the next/previous one or group of students
 2053:     my $firststu = $env{'form.unamedom0'};
 2054:     my $laststu = $env{'form.unamedom'.($ngrade-1)};
 2055:     my $ctr = 2;
 2056:     while ($laststu eq '') {
 2057: 	$laststu  = $env{'form.unamedom'.($ngrade-$ctr)};
 2058: 	$ctr++;
 2059: 	$laststu = $firststu if ($ctr > $ngrade);
 2060:     }
 2061: 
 2062:     my (@parsedlist,@nextlist);
 2063:     my ($nextflg) = 0;
 2064:     foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {
 2065: 	if ($nextflg == 1 && $button =~ /Next$/) {
 2066: 	    push @parsedlist,$_;
 2067: 	}
 2068: 	$nextflg = 1 if ($_ eq $laststu);
 2069: 	if ($button eq 'Previous') {
 2070: 	    last if ($_ eq $firststu);
 2071: 	    push @parsedlist,$_;
 2072: 	}
 2073:     }
 2074:     $ctr = 0;
 2075:     @parsedlist = reverse @parsedlist if ($button eq 'Previous');
 2076:     my ($partlist) = &response_type($url);
 2077:     foreach my $student (@parsedlist) {
 2078: 	my $submitonly=$env{'form.submitonly'};
 2079: 	my ($uname,$udom) = split(/:/,$student);
 2080: 	if ($submitonly =~ /^(yes|graded|incorrect)$/) {
 2081: #	    my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
 2082: 	    my %status=&student_gradeStatus($url,$symb,$udom,$uname,$partlist);
 2083: 	    my $submitted = 0;
 2084: 	    my $ungraded = 0;
 2085: 	    my $incorrect = 0;
 2086: 	    foreach (keys(%status)) {
 2087: 		$submitted = 1 if ($status{$_} ne 'nothing');
 2088: 		$ungraded = 1 if ($status{$_} =~ /^ungraded/);
 2089: 		$incorrect = 1 if ($status{$_} =~ /^incorrect/);
 2090: 		my ($foo,$partid,$foo1) = split(/\./,$_);
 2091: 		if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
 2092: 		    $submitted = 0;
 2093: 		}
 2094: 	    }
 2095: 	    next if (!$submitted && ($submitonly eq 'yes' ||
 2096: 				     $submitonly eq 'incorrect' ||
 2097: 				     $submitonly eq 'graded'));
 2098: 	    next if (!$ungraded && ($submitonly eq 'graded'));
 2099: 	    next if (!$incorrect && $submitonly eq 'incorrect');
 2100: 	}
 2101: 	push @nextlist,$student if ($ctr < $ntstu);
 2102: 	last if ($ctr == $ntstu);
 2103: 	$ctr++;
 2104:     }
 2105: 
 2106:     $ctr = 0;
 2107:     my $total = scalar(@nextlist)-1;
 2108: 
 2109:     foreach (sort @nextlist) {
 2110: 	my ($uname,$udom,$submitter) = split(/:/);
 2111: 	$env{'form.student'}  = $uname;
 2112: 	$env{'form.userdom'}  = $udom;
 2113: 	$env{'form.fullname'} = $$fullname{$_};
 2114: 	&submission($request,$ctr,$total);
 2115: 	$ctr++;
 2116:     }
 2117:     if ($total < 0) {
 2118: 	my $the_end = '<h3><font color="red">LON-CAPA User Message</font></h3><br />'."\n";
 2119: 	$the_end.='<b>Message: </b> No more students for this section or class.<br /><br />'."\n";
 2120: 	$the_end.='Click on the button below to return to the grading menu.<br /><br />'."\n";
 2121: 	$the_end.=&show_grading_menu_form ($symb,$url);
 2122: 	$request->print($the_end);
 2123:     }
 2124:     return '';
 2125: }
 2126: 
 2127: #---- Save the score and award for each student, if changed
 2128: sub saveHandGrade {
 2129:     my ($request,$url,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_;
 2130:     my @v_flag;
 2131:     my $usec = &Apache::lonnet::getsection($domain,$stuname,
 2132: 					   $env{'request.course.id'});
 2133:     if (!&canmodify($usec)) { return('not_allowed'); }
 2134:     my %record     = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$stuname);
 2135:     my @parts_graded;
 2136:     my %newrecord  = ();
 2137:     my ($pts,$wgt) = ('','');
 2138:     my %aggregate = ();
 2139:     my $aggregateflag = 0;
 2140:     foreach my $new_part (split(/:/,$env{'form.partlist'.$newflg})) {
 2141: 	#collaborator may vary for different parts
 2142: 	if ($submitter && $new_part ne $part) { next; }
 2143: 	my $dropMenu = $env{'form.GD_SEL'.$newflg.'_'.$new_part};
 2144: 	if ($dropMenu eq 'excused') {
 2145: 	    if ($record{'resource.'.$new_part.'.solved'} ne 'excused') {
 2146: 		$newrecord{'resource.'.$new_part.'.solved'} = 'excused';
 2147: 		if (exists($record{'resource.'.$new_part.'.awarded'})) {
 2148: 		    $newrecord{'resource.'.$new_part.'.awarded'} = '';
 2149: 		}
 2150: 	    $newrecord{'resource.'.$new_part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
 2151: 	    }
 2152: 	} elsif ($dropMenu eq 'reset status'
 2153: 		 && exists($record{'resource.'.$new_part.'.solved'})) { #don't bother if no old records -> no attempts
 2154: 	    foreach my $key (keys (%record)) {
 2155: 		if ($key=~/^resource\.\Q$new_part\E\./) { $newrecord{$key} = ''; }
 2156: 	    }
 2157: 	    $newrecord{'resource.'.$new_part.'.regrader'}=
 2158: 		"$env{'user.name'}:$env{'user.domain'}";
 2159:             my @partids = ("$new_part");
 2160:             my %last_resets = ();
 2161:             &get_last_resets($symb,$env{'request.course.id'},\%last_resets,\@partids);
 2162:             my ($totaltries,$aggtries,$solvedstatus);
 2163:             $totaltries = $record{'resource.'.$part.'.tries'};
 2164:             if ($last_resets{$new_part}) {
 2165:                 $aggtries = &get_num_tries(\%record,$last_resets{$new_part},$new_part);
 2166:             } else {
 2167:                 $aggtries = $totaltries;
 2168:             }
 2169:             $solvedstatus = $record{'resource.'.$new_part.'.solved'};
 2170:             if ($aggtries > 0) {
 2171:                 &decrement($symb,$new_part,\%aggregate,$aggtries,$totaltries,$solvedstatus);
 2172:                 $aggregateflag = 1;
 2173:             }
 2174: 	} elsif ($dropMenu eq '') {
 2175: 	    $pts = ($env{'form.GD_BOX'.$newflg.'_'.$new_part} ne '' ? 
 2176: 		    $env{'form.GD_BOX'.$newflg.'_'.$new_part} : 
 2177: 		    $env{'form.RADVAL'.$newflg.'_'.$new_part});
 2178: 	    if ($pts eq '' && $env{'form.GD_SEL'.$newflg.'_'.$new_part} eq '') {
 2179: 		next;
 2180: 	    }
 2181: 	    $wgt = $env{'form.WGT'.$newflg.'_'.$new_part} eq '' ? 1 : 
 2182: 		$env{'form.WGT'.$newflg.'_'.$new_part};
 2183: 	    my $partial= $pts/$wgt;
 2184: 	    if ($partial eq $record{'resource.'.$new_part.'.awarded'}) {
 2185: 		#do not update score for part if not changed.
 2186: 		next;
 2187: 	    } else {
 2188: 	        push @parts_graded, $new_part;
 2189: 	    }
 2190: 	    if ($record{'resource.'.$new_part.'.awarded'} ne $partial) {
 2191: 		$newrecord{'resource.'.$new_part.'.awarded'}  = $partial;
 2192: 	    }
 2193: 	    my $reckey = 'resource.'.$new_part.'.solved';
 2194: 	    if ($partial == 0) {
 2195: 		if ($record{$reckey} ne 'incorrect_by_override') {
 2196: 		    $newrecord{$reckey} = 'incorrect_by_override';
 2197: 		}
 2198: 	    } else {
 2199: 		if ($record{$reckey} ne 'correct_by_override') {
 2200: 		    $newrecord{$reckey} = 'correct_by_override';
 2201: 		}
 2202: 	    }	    
 2203: 	    if ($submitter && 
 2204: 		($record{'resource.'.$new_part.'.submitted_by'} ne $submitter)) {
 2205: 		$newrecord{'resource.'.$new_part.'.submitted_by'} = $submitter;
 2206: 	    }
 2207: 	    $newrecord{'resource.'.$new_part.'.regrader'}=
 2208: 		"$env{'user.name'}:$env{'user.domain'}";
 2209: 	}
 2210: 	# unless problem has been graded, set flag to version the submitted files
 2211: 	unless ($record{'resource.'.$new_part.'.solved'} =~ /^correct_/  || $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override') {
 2212: 	    push (@v_flag,$new_part);
 2213: 	}
 2214:     }
 2215:     if (scalar(keys(%newrecord)) > 0) {
 2216:         if (scalar(@v_flag)) {
 2217:             &version_portfiles(\%record, \@parts_graded, $env{'request.course.id'}, $symb, $domain, $stuname, \@v_flag);
 2218:         }
 2219: 	&Apache::lonnet::cstore(\%newrecord,$symb,
 2220: 				$env{'request.course.id'},$domain,$stuname);
 2221:     }
 2222:     if ($aggregateflag) {
 2223:         &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
 2224:                   $env{'course.'.$env{'request.course.id'}.'.domain'},
 2225:                   $env{'course.'.$env{'request.course.id'}.'.num'});
 2226:     }
 2227:     return '',$pts,$wgt;
 2228: }
 2229: 
 2230: # ----------- Provides number of tries since last reset.
 2231: sub get_num_tries {
 2232:     my ($record,$last_reset,$part) = @_;
 2233:     my $timestamp = '';
 2234:     my $num_tries = 0;
 2235:     if ($$record{'version'}) {
 2236:         for (my $version=$$record{'version'};$version>=1;$version--) {
 2237:             if (exists($$record{$version.':resource.'.$part.'.solved'})) {
 2238:                 $timestamp = $$record{$version.':timestamp'};
 2239:                 if ($timestamp > $last_reset) {
 2240:                     $num_tries ++;
 2241:                 } else {
 2242:                     last;
 2243:                 }
 2244:             }
 2245:         }
 2246:     }
 2247:     return $num_tries;
 2248: }
 2249: 
 2250: # ----------- Determine decrements required in aggregate totals 
 2251: sub decrement_aggs {
 2252:     my ($symb,$part,$aggregate,$aggtries,$totaltries,$solvedstatus) = @_;
 2253:     my %decrement = (
 2254:                         attempts => 0,
 2255:                         users => 0,
 2256:                         correct => 0
 2257:                     );
 2258:     $decrement{'attempts'} = $aggtries;
 2259:     if ($solvedstatus =~ /^correct/) {
 2260:         $decrement{'correct'} = 1;
 2261:     }
 2262:     if ($aggtries == $totaltries) {
 2263:         $decrement{'users'} = 1;
 2264:     }
 2265:     foreach my $type (keys (%decrement)) {
 2266:         $$aggregate{$symb."\0".$part."\0".$type} = -$decrement{$type};
 2267:     }
 2268:     return;
 2269: }
 2270: 
 2271: # ----------- Determine timestamps for last reset of aggregate totals for parts  
 2272: sub get_last_resets {
 2273:     my ($symb,$courseid,$last_resets,$partids) =@_;
 2274:     my $cdom = $env{'course.'.$courseid.'.domain'};
 2275:     my $cname = $env{'course.'.$courseid.'.num'};
 2276:     my %resethash = &Apache::lonnet::restore($symb,'nohist_resourcetracker',$cdom,$cname);
 2277:     if ($resethash{'version'}) {
 2278:         foreach my $part (@{$partids}) {
 2279:             $$last_resets{$part} = '';
 2280:             for (my $version=$resethash{'version'};$version>=1;$version--) {
 2281:                 if (exists($resethash{$version.':'.$part."\0".'prev_attempts'})) {
 2282:                     $$last_resets{$part} = $resethash{$version.':timestamp'};
 2283:                     last;
 2284:                 }
 2285:             }
 2286:         }
 2287:     }
 2288:     return;
 2289: }
 2290: 
 2291: # ----------- Handles creating versions for portfolio files as answers
 2292: sub version_portfiles {
 2293:     my ($record, $parts_graded, $courseid, $symb, $domain, $stuname, $v_flag) = @_;
 2294:     my $version_parts = join('|',@$v_flag);
 2295:     my $parts = join('|', @$parts_graded);
 2296:     my $portfolio_root = &Apache::loncommon::propath($domain,
 2297: 						 $stuname).
 2298: 						'/userfiles/portfolio';
 2299:     foreach my $key(keys %$record) {
 2300:         my $new_portfiles;
 2301:         if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {
 2302:             my @v_portfiles;
 2303:             my @portfiles = split(/,/,$$record{$key});
 2304:             # &Apache::lonnet::logthis("should be unmarking and remarking");
 2305:             foreach my $file (@portfiles) {
 2306:                 &Apache::lonnet::unmark_as_readonly($domain,$stuname,[$symb,$env{'request.course.id'}],$file);
 2307:                 my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*$)/);
 2308:                 my $version = 0;
 2309:                 my @answer_file_parts = split(/\./, $answer_file);
 2310:                 my @dir_list = &Apache::lonnet::dirlist($directory,$domain,$stuname,$portfolio_root);
 2311:                 my @file_names;
 2312:                 my @file_name_parts;
 2313:                 foreach my $row (@dir_list) {
 2314:                     @file_names = split(/\&/,$row,2);
 2315:                     @file_name_parts = split(/\./, $file_names[0]);
 2316:                     # ($file_name_parts[scalar @file_name_parts] eq $answer_file_parts[scalar @answer_file_parts])
 2317:                     if (($file_name_parts[0] eq $answer_file_parts[0]) && 
 2318:                         ($file_name_parts[-1] eq $answer_file_parts[-1])) {
 2319:                         # gets here if filename and extension match, regardless of version
 2320:                         if (scalar @file_name_parts == 3) { # a versioned file is found
 2321:                             # so save it for later
 2322:                             if ($file_name_parts[1] > $version) {$version = $file_name_parts[1]};
 2323:                         }
 2324:                     }
 2325:                 }
 2326:                 $version++;
 2327:                 my $home_server = &Apache::lonnet::homeserver($stuname,$domain,undef);
 2328:                 $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stuname/portfolio$directory$answer_file");
 2329:                 if($env{'form.copy'} eq '-1') {
 2330:                     &Apache::lonnet::logthis('problem getting file '.$directory.$answer_file);
 2331:                 } else {
 2332:                    my $copy_result = &Apache::lonnet::finishuserfileupload($stuname,$domain,$home_server,'copy',
 2333:                                     '/portfolio'.$directory.$answer_file_parts[0].'.'.$version.'.'.$answer_file_parts[-1]);
 2334:                     push(@v_portfiles, $answer_file_parts[0].'.'.$version.'.'.$answer_file_parts[-1]);
 2335:                     &Apache::lonnet::mark_as_readonly($domain,$stuname,
 2336:                                 ['/portfolio'.$directory.$answer_file_parts[0].'.'.$version.'.'.$answer_file_parts[-1]],
 2337:                                 [$symb,$env{'request.course.id'},'graded']);
 2338:                 }
 2339:             }
 2340:             $$record{$key} = join(',',@v_portfiles);
 2341:         }
 2342:     } 
 2343:     return 'ok';   
 2344:     
 2345: }
 2346: 
 2347: #--------------------------------------------------------------------------------------
 2348: #
 2349: #-------------------------- Next few routines handles grading by section or whole class
 2350: #
 2351: #--- Javascript to handle grading by section or whole class
 2352: sub viewgrades_js {
 2353:     my ($request) = shift;
 2354: 
 2355:     $request->print(<<VIEWJAVASCRIPT);
 2356: <script type="text/javascript" language="javascript">
 2357:    function writePoint(partid,weight,point) {
 2358: 	var radioButton = document.classgrade["RADVAL_"+partid];
 2359: 	var textbox = document.classgrade["TEXTVAL_"+partid];
 2360: 	if (point == "textval") {
 2361: 	    point = document.classgrade["TEXTVAL_"+partid].value;
 2362: 	    if (isNaN(point) || parseFloat(point) < 0) {
 2363: 		alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point));
 2364: 		var resetbox = false;
 2365: 		for (var i=0; i<radioButton.length; i++) {
 2366: 		    if (radioButton[i].checked) {
 2367: 			textbox.value = i;
 2368: 			resetbox = true;
 2369: 		    }
 2370: 		}
 2371: 		if (!resetbox) {
 2372: 		    textbox.value = "";
 2373: 		}
 2374: 		return;
 2375: 	    }
 2376: 	    if (parseFloat(point) > parseFloat(weight)) {
 2377: 		var resp = confirm("You entered a value ("+parseFloat(point)+
 2378: 				   ") greater than the weight for the part. Accept?");
 2379: 		if (resp == false) {
 2380: 		    textbox.value = "";
 2381: 		    return;
 2382: 		}
 2383: 	    }
 2384: 	    for (var i=0; i<radioButton.length; i++) {
 2385: 		radioButton[i].checked=false;
 2386: 		if (parseFloat(point) == i) {
 2387: 		    radioButton[i].checked=true;
 2388: 		}
 2389: 	    }
 2390: 
 2391: 	} else {
 2392: 	    textbox.value = parseFloat(point);
 2393: 	}
 2394: 	for (i=0;i<document.classgrade.total.value;i++) {
 2395: 	    var user = document.classgrade["ctr"+i].value;
 2396: 	    var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
 2397: 	    var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
 2398: 	    var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
 2399: 	    if (saveval != "correct") {
 2400: 		scorename.value = point;
 2401: 		if (selname[0].selected != true) {
 2402: 		    selname[0].selected = true;
 2403: 		}
 2404: 	    }
 2405: 	}
 2406: 	document.classgrade["SELVAL_"+partid][0].selected = true;
 2407:     }
 2408: 
 2409:     function writeRadText(partid,weight) {
 2410: 	var selval   = document.classgrade["SELVAL_"+partid];
 2411: 	var radioButton = document.classgrade["RADVAL_"+partid];
 2412:         var override = document.classgrade["FORCE_"+partid].checked;
 2413: 	var textbox = document.classgrade["TEXTVAL_"+partid];
 2414: 	if (selval[1].selected || selval[2].selected) {
 2415: 	    for (var i=0; i<radioButton.length; i++) {
 2416: 		radioButton[i].checked=false;
 2417: 
 2418: 	    }
 2419: 	    textbox.value = "";
 2420: 
 2421: 	    for (i=0;i<document.classgrade.total.value;i++) {
 2422: 		var user = document.classgrade["ctr"+i].value;
 2423: 		var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
 2424: 		var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
 2425: 		var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
 2426: 		if ((saveval != "correct") || override) {
 2427: 		    scorename.value = "";
 2428: 		    if (selval[1].selected) {
 2429: 			selname[1].selected = true;
 2430: 		    } else {
 2431: 			selname[2].selected = true;
 2432: 			if (Number(document.classgrade["GD_"+user+"_"+partid+"_tries"].value)) 
 2433: 			{document.classgrade["GD_"+user+"_"+partid+"_tries"].value = '0';}
 2434: 		    }
 2435: 		}
 2436: 	    }
 2437: 	} else {
 2438: 	    for (i=0;i<document.classgrade.total.value;i++) {
 2439: 		var user = document.classgrade["ctr"+i].value;
 2440: 		var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
 2441: 		var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
 2442: 		var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
 2443: 		if ((saveval != "correct") || override) {
 2444: 		    scorename.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
 2445: 		    selname[0].selected = true;
 2446: 		}
 2447: 	    }
 2448: 	}	    
 2449:     }
 2450: 
 2451:     function changeSelect(partid,user) {
 2452: 	var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
 2453: 	var textbox = document.classgrade["GD_"+user+'_'+partid+"_awarded"];
 2454: 	var point  = textbox.value;
 2455: 	var weight = document.classgrade["weight_"+partid].value;
 2456: 
 2457: 	if (isNaN(point) || parseFloat(point) < 0) {
 2458: 	    alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point));
 2459: 	    textbox.value = "";
 2460: 	    return;
 2461: 	}
 2462: 	if (parseFloat(point) > parseFloat(weight)) {
 2463: 	    var resp = confirm("You entered a value ("+parseFloat(point)+
 2464: 			       ") greater than the weight of the part. Accept?");
 2465: 	    if (resp == false) {
 2466: 		textbox.value = "";
 2467: 		return;
 2468: 	    }
 2469: 	}
 2470: 	selval[0].selected = true;
 2471:     }
 2472: 
 2473:     function changeOneScore(partid,user) {
 2474: 	var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
 2475: 	if (selval[1].selected || selval[2].selected) {
 2476: 	    document.classgrade["GD_"+user+'_'+partid+"_awarded"].value = "";
 2477: 	    if (selval[2].selected) {
 2478: 		document.classgrade["GD_"+user+'_'+partid+"_tries"].value = "0";
 2479: 	    }
 2480:         }
 2481:     }
 2482: 
 2483:     function resetEntry(numpart) {
 2484: 	for (ctpart=0;ctpart<numpart;ctpart++) {
 2485: 	    var partid = document.classgrade["partid_"+ctpart].value;
 2486: 	    var radioButton = document.classgrade["RADVAL_"+partid];
 2487: 	    var textbox = document.classgrade["TEXTVAL_"+partid];
 2488: 	    var selval  = document.classgrade["SELVAL_"+partid];
 2489: 	    for (var i=0; i<radioButton.length; i++) {
 2490: 		radioButton[i].checked=false;
 2491: 
 2492: 	    }
 2493: 	    textbox.value = "";
 2494: 	    selval[0].selected = true;
 2495: 
 2496: 	    for (i=0;i<document.classgrade.total.value;i++) {
 2497: 		var user = document.classgrade["ctr"+i].value;
 2498: 		var resetscore = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
 2499: 		resetscore.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
 2500: 		var resettries = document.classgrade["GD_"+user+"_"+partid+"_tries"];
 2501: 		resettries.value = document.classgrade["GD_"+user+"_"+partid+"_tries_s"].value;
 2502: 		var saveselval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
 2503: 		var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
 2504: 		if (saveselval == "excused") {
 2505: 		    if (selname[1].selected == false) { selname[1].selected = true;}
 2506: 		} else {
 2507: 		    if (selname[0].selected == false) {selname[0].selected = true};
 2508: 		}
 2509: 	    }
 2510: 	}
 2511:     }
 2512: 
 2513: </script>
 2514: VIEWJAVASCRIPT
 2515: }
 2516: 
 2517: #--- show scores for a section or whole class w/ option to change/update a score
 2518: sub viewgrades {
 2519:     my ($request) = shift;
 2520:     &viewgrades_js($request);
 2521: 
 2522:     my ($symb,$url) = ($env{'form.symb'},$env{'form.url'}); 
 2523:     #need to make sure we have the correct data for later EXT calls, 
 2524:     #thus invalidate the cache
 2525:     &Apache::lonnet::devalidatecourseresdata(
 2526:                  $env{'course.'.$env{'request.course.id'}.'.num'},
 2527:                  $env{'course.'.$env{'request.course.id'}.'.domain'});
 2528:     &Apache::lonnet::clear_EXT_cache_status();
 2529: 
 2530:     my $result='<h3><font color="#339933">'.&mt('Manual Grading').'</font></h3>';
 2531:     $result.='<font size=+1><b>Current Resource: </b>'.$env{'form.probTitle'}.'</font>'."\n";
 2532: 
 2533:     #view individual student submission form - called using Javascript viewOneStudent
 2534:     $result.=&jscriptNform($url,$symb);
 2535: 
 2536:     #beginning of class grading form
 2537:     $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n".
 2538: 	'<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
 2539: 	'<input type="hidden" name="url"     value="'.$url.'" />'."\n".
 2540: 	'<input type="hidden" name="command" value="editgrades" />'."\n".
 2541: 	'<input type="hidden" name="section" value="'.$env{'form.section'}.'" />'."\n".
 2542: 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
 2543: 	'<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n".
 2544: 	'<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
 2545: 
 2546:     my $sectionClass;
 2547:     if ($env{'form.section'} eq 'all') {
 2548: 	$sectionClass='Class </h3>';
 2549:     } elsif ($env{'form.section'} eq 'none') {
 2550: 	$sectionClass='Students in no Section </h3>';
 2551:     } else {
 2552: 	$sectionClass='Students in Section '.$env{'form.section'}.'</h3>';
 2553:     }
 2554:     $result.='<h3>Assign Common Grade To '.$sectionClass;
 2555:     $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".
 2556: 	'<table border=0><tr bgcolor="#ffffdd"><td>';
 2557:     #radio buttons/text box for assigning points for a section or class.
 2558:     #handles different parts of a problem
 2559:     my ($partlist,$handgrade) = &response_type($url,$symb);
 2560:     my %weight = ();
 2561:     my $ctsparts = 0;
 2562:     $result.='<table border="0">';
 2563:     my %seen = ();
 2564:     for (sort keys(%$handgrade)) {
 2565: 	my ($partid,$respid) = split (/_/,$_,2);
 2566: 	next if $seen{$partid};
 2567: 	$seen{$partid}++;
 2568: 	my $handgrade=$$handgrade{$_};
 2569: 	my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb);
 2570: 	$weight{$partid} = $wgt eq '' ? '1' : $wgt;
 2571: 
 2572: 	$result.='<input type="hidden" name="partid_'.
 2573: 	    $ctsparts.'" value="'.$partid.'" />'."\n";
 2574: 	$result.='<input type="hidden" name="weight_'.
 2575: 	    $partid.'" value="'.$weight{$partid}.'" />'."\n";
 2576: 	my $display_part=&get_display_part($partid,$url,$symb);
 2577: 	$result.='<tr><td><b>Part:</b> '.$display_part.'&nbsp; &nbsp;<b>Point:</b> </td><td>';
 2578: 	$result.='<table border="0"><tr>';  
 2579: 	my $ctr = 0;
 2580: 	while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across
 2581: 	    $result.= '<td><input type="radio" name="RADVAL_'.$partid.'" '.
 2582: 		'onclick="javascript:writePoint(\''.$partid.'\','.$weight{$partid}.
 2583: 		','.$ctr.')" />'.$ctr."</td>\n";
 2584: 	    $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
 2585: 	    $ctr++;
 2586: 	}
 2587: 	$result.='</tr></table>';
 2588: 	$result.= '</td><td><b> or </b><input type="text" name="TEXTVAL_'.
 2589: 	    $partid.'" size="4" '.'onChange="javascript:writePoint(\''.
 2590: 		$partid.'\','.$weight{$partid}.',\'textval\')" /> /'.
 2591: 	    $weight{$partid}.' (problem weight)</td>'."\n";
 2592: 	$result.= '</td><td><select name="SELVAL_'.$partid.'"'.
 2593: 	    'onChange="javascript:writeRadText(\''.$partid.'\','.
 2594: 		$weight{$partid}.')"> '.
 2595: 	    '<option selected="on"> </option>'.
 2596: 	    '<option>excused</option>'.
 2597: 	    '<option>reset status</option></select></td>'.
 2598:             '<td><label><input type="checkbox" name="FORCE_'.$partid.'" /> Override "Correct"</label></td></tr>'."\n";
 2599: 	$ctsparts++;
 2600:     }
 2601:     $result.='</table>'.'</td></tr></table>'.'</td></tr></table>'."\n".
 2602: 	'<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';
 2603:     $result.='<input type="button" value="Reset" '.
 2604: 	'onClick="javascript:resetEntry('.$ctsparts.');" TARGET=_self>';
 2605: 
 2606:     #table listing all the students in a section/class
 2607:     #header of table
 2608:     $result.= '<h3>Assign Grade to Specific Students in '.$sectionClass;
 2609:     $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".
 2610: 	'<table border=0><tr bgcolor="#deffff"><td>&nbsp;<b>No.</b>&nbsp;</td>'.
 2611: 	'<td>'.&nameUserString('header')."</td>\n";
 2612:     my (@parts) = sort(&getpartlist($url,$symb));
 2613:     my @partids = ();
 2614:     foreach my $part (@parts) {
 2615: 	my $display=&Apache::lonnet::metadata($url,$part.'.display');
 2616: 	$display =~ s|^Number of Attempts|Tries<br />|; # makes the column narrower
 2617: 	if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }
 2618: 	my ($partid) = &split_part_type($part);
 2619:         push(@partids, $partid);
 2620: 	my $display_part=&get_display_part($partid,$url,$symb);
 2621: 	if ($display =~ /^Partial Credit Factor/) {
 2622: 	    $result.='<td><b>Score Part:</b> '.$display_part.
 2623: 		' <br /><b>(weight = '.$weight{$partid}.')</b></td>'."\n";
 2624: 	    next;
 2625: 	} else {
 2626: 	    $display =~s/\[Part: \Q$partid\E\]/Part:<\/b> $display_part/;
 2627: 	}
 2628: 	$display =~ s|Problem Status|Grade Status<br />|;
 2629: 	$result.='<td><b>'.$display.'</td>'."\n";
 2630:     }
 2631:     $result.='</tr>';
 2632: 
 2633:     # retrieve last reset information
 2634:     my %last_resets = ();
 2635:     &get_last_resets($symb,$env{'request.course.id'},\%last_resets,\@partids);
 2636: 
 2637:     #get info for each student
 2638:     #list all the students - with points and grade status
 2639:     my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
 2640:     my $ctr = 0;
 2641:     foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {
 2642: 	$ctr++;
 2643: 	$result.=&viewstudentgrade($url,$symb,$env{'request.course.id'},
 2644: 				   $_,$$fullname{$_},\@parts,\%weight,$ctr,\%last_resets);
 2645:     }
 2646:     $result.='</table></td></tr></table>';
 2647:     $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";
 2648:     $result.='<input type="button" value="Save" '.
 2649: 	'onClick="javascript:submit();" TARGET=_self /></form>'."\n";
 2650:     if (scalar(%$fullname) eq 0) {
 2651: 	my $colspan=3+scalar(@parts);
 2652: 	$result='<font color="red">There are no students in section "'.$env{'form.section'}.
 2653: 	    '" with enrollment status "'.$env{'form.Status'}.'" to modify or grade.</font>';
 2654:     }
 2655:     $result.=&show_grading_menu_form($symb,$url);
 2656:     return $result;
 2657: }
 2658: 
 2659: #--- call by previous routine to display each student
 2660: sub viewstudentgrade {
 2661:     my ($url,$symb,$courseid,$student,$fullname,$parts,$weight,$ctr,$last_resets) = @_;
 2662:     my ($uname,$udom) = split(/:/,$student);
 2663:     $student=~s/:/_/;
 2664:     my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
 2665:     my %aggregates = (); 
 2666:     my $result='<tr bgcolor="#ffffdd"><td align="right">'.
 2667: 	'<input type="hidden" name="ctr'.($ctr-1).'" value="'.$student.'" />'.
 2668: 	"\n".$ctr.'&nbsp;</td><td>&nbsp;'.
 2669: 	'<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
 2670: 	'\')"; TARGET=_self>'.$fullname.'</a> '.
 2671: 	'<font color="#999999">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</font></td>'."\n";
 2672:     foreach my $apart (@$parts) {
 2673: 	my ($part,$type) = &split_part_type($apart);
 2674: 	my $score=$record{"resource.$part.$type"};
 2675:         $result.='<td align="middle">';
 2676:         my ($aggtries,$totaltries);
 2677:         unless (exists($aggregates{$part})) {
 2678:             $totaltries = $record{'resource.'.$part.'.tries'};
 2679:             if ($$last_resets{$part}) {  
 2680:                 $aggtries = &get_num_tries(\%record,$$last_resets{$part},$part);
 2681:             } else {
 2682:                 $aggtries = $totaltries;
 2683:             } 
 2684:             $result.='<input type="hidden" name="'.
 2685:                 'GD_'.$student.'_'.$part.'_aggtries" value="'.$aggtries.'" />'."\n";
 2686:             $result.='<input type="hidden" name="'.
 2687:                 'GD_'.$student.'_'.$part.'_totaltries" value="'.$totaltries.'" />'."\n";
 2688:             $aggregates{$part} = 1;
 2689:         }
 2690: 	if ($type eq 'awarded') {
 2691: 	    my $pts = $score eq '' ? '' : $score*$$weight{$part};
 2692: 	    $result.='<input type="hidden" name="'.
 2693: 		'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";
 2694: 	    $result.='<input type="text" name="'.
 2695: 		'GD_'.$student.'_'.$part.'_awarded" '.
 2696: 		'onChange="javascript:changeSelect(\''.$part.'\',\''.$student.
 2697: 		'\')" value="'.$pts.'" size="4" /></td>'."\n";
 2698: 	} elsif ($type eq 'solved') {
 2699: 	    my ($status,$foo)=split(/_/,$score,2);
 2700: 	    $status = 'nothing' if ($status eq '');
 2701: 	    $result.='<input type="hidden" name="'.'GD_'.$student.'_'.
 2702: 		$part.'_solved_s" value="'.$status.'" />'."\n";
 2703: 	    $result.='&nbsp;<select name="'.
 2704: 		'GD_'.$student.'_'.$part.'_solved" '.
 2705: 		'onChange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n";
 2706: 	    $result.= (($status eq 'excused') ? '<option> </option><option selected="on">excused</option>' 
 2707: 		: '<option selected="on"> </option><option>excused</option>')."\n";
 2708: 	    $result.='<option>reset status</option>';
 2709: 	    $result.="</select>&nbsp;</td>\n";
 2710: 	} else {
 2711: 	    $result.='<input type="hidden" name="'.
 2712: 		'GD_'.$student.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'.
 2713: 		    "\n";
 2714: 	    $result.='<input type="text" name="'.
 2715: 		'GD_'.$student.'_'.$part.'_'.$type.'" '.
 2716: 		'value="'.$score.'" size="4" /></td>'."\n";
 2717: 	}
 2718:     }
 2719:     $result.='</tr>';
 2720:     return $result;
 2721: }
 2722: 
 2723: #--- change scores for all the students in a section/class
 2724: #    record does not get update if unchanged
 2725: sub editgrades {
 2726:     my ($request) = @_;
 2727: 
 2728:     my $symb=$env{'form.symb'};
 2729:     my $url =$env{'form.url'};
 2730:     my $title='<h3><font color="#339933">Current Grade Status</font></h3>';
 2731:     $title.='<font size=+1><b>Current Resource: </b>'.$env{'form.probTitle'}.'</font><br />'."\n";
 2732:     $title.='<font size=+1><b>Section: </b>'.$env{'form.section'}.'</font>'."\n";
 2733: 
 2734:     my $result= '<table border="0"><tr><td bgcolor="#777777">'."\n";
 2735:     $result.= '<table border="0"><tr bgcolor="#deffff">'.
 2736: 	'<td rowspan=2 valign="center">&nbsp;<b>No.</b>&nbsp;</td>'.
 2737: 	'<td rowspan=2 valign="center">'.&nameUserString('header')."</td>\n";
 2738: 
 2739:     my %scoreptr = (
 2740: 		    'correct'  =>'correct_by_override',
 2741: 		    'incorrect'=>'incorrect_by_override',
 2742: 		    'excused'  =>'excused',
 2743: 		    'ungraded' =>'ungraded_attempted',
 2744: 		    'nothing'  => '',
 2745: 		    );
 2746:     my ($classlist,undef,$fullname) = &getclasslist($env{'form.section'},'0');
 2747: 
 2748:     my (@partid);
 2749:     my %weight = ();
 2750:     my %columns = ();
 2751:     my ($i,$ctr,$count,$rec_update) = (0,0,0,0);
 2752: 
 2753:     my (@parts) = sort(&getpartlist($url,$symb));
 2754:     my $header;
 2755:     while ($ctr < $env{'form.totalparts'}) {
 2756: 	my $partid = $env{'form.partid_'.$ctr};
 2757: 	push @partid,$partid;
 2758: 	$weight{$partid} = $env{'form.weight_'.$partid};
 2759: 	$ctr++;
 2760:     }
 2761:     foreach my $partid (@partid) {
 2762: 	$header .= '<td align="center">&nbsp;<b>Old Score</b>&nbsp;</td>'.
 2763: 	    '<td align="center">&nbsp;<b>New Score</b>&nbsp;</td>';
 2764: 	$columns{$partid}=2;
 2765: 	foreach my $stores (@parts) {
 2766: 	    my ($part,$type) = &split_part_type($stores);
 2767: 	    if ($part !~ m/^\Q$partid\E/) { next;}
 2768: 	    if ($type eq 'awarded' || $type eq 'solved') { next; }
 2769: 	    my $display=&Apache::lonnet::metadata($url,$stores.'.display');
 2770: 	    $display =~ s/\[Part: (\w)+\]//;
 2771: 	    $display =~ s/Number of Attempts/Tries/;
 2772: 	    $header .= '<td align="center">&nbsp;<b>Old '.$display.'</b>&nbsp;</td>'.
 2773: 		'<td align="center">&nbsp;<b>New '.$display.'</b>&nbsp;</td>';
 2774: 	    $columns{$partid}+=2;
 2775: 	}
 2776:     }
 2777:     foreach my $partid (@partid) {
 2778: 	my $display_part=&get_display_part($partid,$url,$symb);
 2779: 	$result .= '<td colspan="'.$columns{$partid}.
 2780: 	    '" align="center"><b>Part:</b> '.$display_part.
 2781: 	    ' (Weight = '.$weight{$partid}.')</td>';
 2782: 
 2783:     }
 2784:     $result .= '</tr><tr bgcolor="#deffff">';
 2785:     $result .= $header;
 2786:     $result .= '</tr>'."\n";
 2787:     my $noupdate;
 2788:     my ($updateCtr,$noupdateCtr) = (1,1);
 2789:     for ($i=0; $i<$env{'form.total'}; $i++) {
 2790: 	my $line;
 2791: 	my $user = $env{'form.ctr'.$i};
 2792: 	my $usercolon = $user;
 2793: 	$usercolon =~s/_/:/;
 2794: 	my ($uname,$udom)=split(/_/,$user);
 2795: 	my %newrecord;
 2796: 	my $updateflag = 0;
 2797: 	$line .= '<td>'.&nameUserString(undef,$$fullname{$usercolon},$uname,$udom).'</td>';
 2798: 	my $usec=$classlist->{"$uname:$udom"}[5];
 2799: 	if (!&canmodify($usec)) {
 2800: 	    my $numcols=scalar(@partid)*4+2;
 2801: 	    $noupdate.=$line."<td colspan=\"$numcols\"><font color=\"red\">Not allowed to modify student</font></td></tr>";
 2802: 	    next;
 2803: 	}
 2804:         my %aggregate = ();
 2805:         my $aggregateflag = 0;
 2806: 	foreach (@partid) {
 2807: 	    my $old_aw    = $env{'form.GD_'.$user.'_'.$_.'_awarded_s'};
 2808: 	    my $old_part_pcr = $old_aw/($weight{$_} ne '0' ? $weight{$_}:1);
 2809: 	    my $old_part  = $old_aw eq '' ? '' : $old_part_pcr;
 2810: 	    my $old_score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
 2811: 	    my $awarded   = $env{'form.GD_'.$user.'_'.$_.'_awarded'};
 2812: 	    my $pcr       = $awarded/($weight{$_} ne '0' ? $weight{$_} : 1);
 2813: 	    my $partial   = $awarded eq '' ? '' : $pcr;
 2814: 	    my $score;
 2815: 	    if ($partial eq '') {
 2816: 		$score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
 2817: 	    } elsif ($partial > 0) {
 2818: 		$score = 'correct_by_override';
 2819: 	    } elsif ($partial == 0) {
 2820: 		$score = 'incorrect_by_override';
 2821: 	    }
 2822: 	    my $dropMenu = $env{'form.GD_'.$user.'_'.$_.'_solved'};
 2823: 	    $score = 'excused' if (($dropMenu eq 'excused') && ($score ne 'excused'));
 2824: 
 2825: 	    if ($dropMenu eq 'reset status' &&
 2826: 		$old_score ne '') { # ignore if no previous attempts => nothing to reset
 2827: 		$newrecord{'resource.'.$_.'.tries'} = 0;
 2828: 		$newrecord{'resource.'.$_.'.solved'} = '';
 2829: 		$newrecord{'resource.'.$_.'.award'} = '';
 2830: 		$newrecord{'resource.'.$_.'.awarded'} = 0;
 2831: 		$newrecord{'resource.'.$_.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
 2832: 		$updateflag = 1;
 2833:                 if ($env{'form.GD_'.$user.'_'.$_.'_aggtries'} > 0) {
 2834:                     my $aggtries = $env{'form.GD_'.$user.'_'.$_.'_aggtries'};
 2835:                     my $totaltries = $env{'form.GD_'.$user.'_'.$_.'_totaltries'};
 2836:                     my $solvedstatus = $env{'form.GD_'.$user.'_'.$_.'_solved_s'};
 2837:                     &decrement_aggs($symb,$_,\%aggregate,$aggtries,$totaltries,$solvedstatus);
 2838:                     $aggregateflag = 1;
 2839:                 }
 2840: 	    } elsif (!($old_part eq $partial && $old_score eq $score)) {
 2841: 		$updateflag = 1;
 2842: 		$newrecord{'resource.'.$_.'.awarded'}  = $partial if $partial ne '';
 2843: 		$newrecord{'resource.'.$_.'.solved'}   = $score;
 2844: 		$rec_update++;
 2845: 	    }
 2846: 
 2847: 	    $line .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
 2848: 		'<td align="center">'.$awarded.
 2849: 		($score eq 'excused' ? $score : '').'&nbsp;</td>';
 2850: 
 2851: 
 2852: 	    my $partid=$_;
 2853: 	    foreach my $stores (@parts) {
 2854: 		my ($part,$type) = &split_part_type($stores);
 2855: 		if ($part !~ m/^\Q$partid\E/) { next;}
 2856: 		if ($type eq 'awarded' || $type eq 'solved') { next; }
 2857: 		my $old_aw    = $env{'form.GD_'.$user.'_'.$part.'_'.$type.'_s'};
 2858: 		my $awarded   = $env{'form.GD_'.$user.'_'.$part.'_'.$type};
 2859: 		if ($awarded ne '' && $awarded ne $old_aw) {
 2860: 		    $newrecord{'resource.'.$part.'.'.$type}= $awarded;
 2861: 		    $newrecord{'resource.'.$part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
 2862: 		    $updateflag=1;
 2863: 		}
 2864: 		$line .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
 2865: 		    '<td align="center">'.$awarded.'&nbsp;</td>';
 2866: 	    }
 2867: 	}
 2868: 	$line.='</tr>'."\n";
 2869: 	if ($updateflag) {
 2870: 	    $count++;
 2871: 	    &Apache::lonnet::cstore(\%newrecord,$symb,$env{'request.course.id'},
 2872: 				    $udom,$uname);
 2873: 	    $result.='<tr bgcolor="#ffffde"><td align="right">&nbsp;'.$updateCtr.'&nbsp;</td>'.$line;
 2874: 	    $updateCtr++;
 2875: 	} else {
 2876: 	    $noupdate.='<tr bgcolor="#ffffde"><td align="right">&nbsp;'.$noupdateCtr.'&nbsp;</td>'.$line;
 2877: 	    $noupdateCtr++;
 2878: 	}
 2879:         if ($aggregateflag) {
 2880:             &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
 2881:                       $env{'course.'.$env{'request.course.id'}.'.domain'},
 2882:                       $env{'course.'.$env{'request.course.id'}.'.num'});
 2883:         }
 2884:     }
 2885:     if ($noupdate) {
 2886: #	my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3;
 2887: 	my $numcols=scalar(@partid)*4+2;
 2888: 	$result .= '<tr bgcolor="#ffffff"><td align="center" colspan="'.$numcols.'">No Changes Occurred For the Students Below</td></tr><tr bgcolor="#ffffde">'.$noupdate;
 2889:     }
 2890:     $result .= '</table></td></tr></table>'."\n".
 2891: 	&show_grading_menu_form ($symb,$url);
 2892:     my $msg = '<br /><b>Number of records updated = '.$rec_update.
 2893: 	' for '.$count.' student'.($count <= 1 ? '' : 's').'.</b><br />'.
 2894: 	'<b>Total number of students = '.$env{'form.total'}.'</b><br />';
 2895:     return $title.$msg.$result;
 2896: }
 2897: 
 2898: sub split_part_type {
 2899:     my ($partstr) = @_;
 2900:     my ($temp,@allparts)=split(/_/,$partstr);
 2901:     my $type=pop(@allparts);
 2902:     my $part=join('.',@allparts);
 2903:     return ($part,$type);
 2904: }
 2905: 
 2906: #------------- end of section for handling grading by section/class ---------
 2907: #
 2908: #----------------------------------------------------------------------------
 2909: 
 2910: 
 2911: #----------------------------------------------------------------------------
 2912: #
 2913: #-------------------------- Next few routines handles grading by csv upload
 2914: #
 2915: #--- Javascript to handle csv upload
 2916: sub csvupload_javascript_reverse_associate {
 2917:     my $error1=&mt('You need to specify the username or ID');
 2918:     my $error2=&mt('You need to specify at least one grading field');
 2919:   return(<<ENDPICK);
 2920:   function verify(vf) {
 2921:     var foundsomething=0;
 2922:     var founduname=0;
 2923:     var foundID=0;
 2924:     for (i=0;i<=vf.nfields.value;i++) {
 2925:       tw=eval('vf.f'+i+'.selectedIndex');
 2926:       if (i==0 && tw!=0) { foundID=1; }
 2927:       if (i==1 && tw!=0) { founduname=1; }
 2928:       if (i!=0 && i!=1 && i!=2 && tw!=0) { foundsomething=1; }
 2929:     }
 2930:     if (founduname==0 && foundID==0) {
 2931: 	alert('$error1');
 2932: 	return;
 2933:     }
 2934:     if (foundsomething==0) {
 2935: 	alert('$error2');
 2936: 	return;
 2937:     }
 2938:     vf.submit();
 2939:   }
 2940:   function flip(vf,tf) {
 2941:     var nw=eval('vf.f'+tf+'.selectedIndex');
 2942:     var i;
 2943:     for (i=0;i<=vf.nfields.value;i++) {
 2944:       //can not pick the same destination field for both name and domain
 2945:       if (((i ==0)||(i ==1)) && 
 2946:           ((tf==0)||(tf==1)) && 
 2947:           (i!=tf) &&
 2948:           (eval('vf.f'+i+'.selectedIndex')==nw)) {
 2949:         eval('vf.f'+i+'.selectedIndex=0;')
 2950:       }
 2951:     }
 2952:   }
 2953: ENDPICK
 2954: }
 2955: 
 2956: sub csvupload_javascript_forward_associate {
 2957:     my $error1=&mt('You need to specify the username or ID');
 2958:     my $error2=&mt('You need to specify at least one grading field');
 2959:   return(<<ENDPICK);
 2960:   function verify(vf) {
 2961:     var foundsomething=0;
 2962:     var founduname=0;
 2963:     var foundID=0;
 2964:     for (i=0;i<=vf.nfields.value;i++) {
 2965:       tw=eval('vf.f'+i+'.selectedIndex');
 2966:       if (tw==1) { foundID=1; }
 2967:       if (tw==2) { founduname=1; }
 2968:       if (tw>3) { foundsomething=1; }
 2969:     }
 2970:     if (founduname==0 && foundID==0) {
 2971: 	alert('$error1');
 2972: 	return;
 2973:     }
 2974:     if (foundsomething==0) {
 2975: 	alert('$error2');
 2976: 	return;
 2977:     }
 2978:     vf.submit();
 2979:   }
 2980:   function flip(vf,tf) {
 2981:     var nw=eval('vf.f'+tf+'.selectedIndex');
 2982:     var i;
 2983:     //can not pick the same destination field twice
 2984:     for (i=0;i<=vf.nfields.value;i++) {
 2985:       if ((i!=tf) && (eval('vf.f'+i+'.selectedIndex')==nw)) {
 2986:         eval('vf.f'+i+'.selectedIndex=0;')
 2987:       }
 2988:     }
 2989:   }
 2990: ENDPICK
 2991: }
 2992: 
 2993: sub csvuploadmap_header {
 2994:     my ($request,$symb,$url,$datatoken,$distotal)= @_;
 2995:     my $javascript;
 2996:     if ($env{'form.upfile_associate'} eq 'reverse') {
 2997: 	$javascript=&csvupload_javascript_reverse_associate();
 2998:     } else {
 2999: 	$javascript=&csvupload_javascript_forward_associate();
 3000:     }
 3001: 
 3002:     my ($result) = &showResourceInfo($url,$env{'form.probTitle'});
 3003:     my $checked=(($env{'form.noFirstLine'})?' checked="checked"':'');
 3004:     my $ignore=&mt('Ignore First Line');
 3005:     $request->print(<<ENDPICK);
 3006: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 3007: <h3><font color="#339933">Uploading Class Grades</font></h3>
 3008: $result
 3009: <hr>
 3010: <h3>Identify fields</h3>
 3011: Total number of records found in file: $distotal <hr />
 3012: Enter as many fields as you can. The system will inform you and bring you back
 3013: to this page if the data selected is insufficient to run your class.<hr />
 3014: <input type="button" value="Reverse Association" onClick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" />
 3015: <label><input type="checkbox" name="noFirstLine" $checked />$ignore</label>
 3016: <input type="hidden" name="associate"  value="" />
 3017: <input type="hidden" name="phase"      value="three" />
 3018: <input type="hidden" name="datatoken"  value="$datatoken" />
 3019: <input type="hidden" name="fileupload" value="$env{'form.fileupload'}" />
 3020: <input type="hidden" name="upfiletype" value="$env{'form.upfiletype'}" />
 3021: <input type="hidden" name="upfile_associate" 
 3022:                                        value="$env{'form.upfile_associate'}" />
 3023: <input type="hidden" name="symb"       value="$symb" />
 3024: <input type="hidden" name="url"        value="$url" />
 3025: <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
 3026: <input type="hidden" name="probTitle"  value="$env{'form.probTitle'}" />
 3027: <input type="hidden" name="command"    value="csvuploadoptions" />
 3028: <hr />
 3029: <script type="text/javascript" language="Javascript">
 3030: $javascript
 3031: </script>
 3032: ENDPICK
 3033:     return '';
 3034: 
 3035: }
 3036: 
 3037: sub csvupload_fields {
 3038:     my ($url,$symb) = @_;
 3039:     my (@parts) = &getpartlist($url,$symb);
 3040:     my @fields=(['ID','Student ID'],
 3041: 		['username','Student Username'],
 3042: 		['domain','Student Domain']);
 3043:     foreach my $part (sort(@parts)) {
 3044: 	my @datum;
 3045: 	my $display=&Apache::lonnet::metadata($url,$part.'.display');
 3046: 	my $name=$part;
 3047: 	if  (!$display) { $display = $name; }
 3048: 	@datum=($name,$display);
 3049: 	if ($name=~/^stores_(.*)_awarded/) {
 3050: 	    push(@fields,['stores_'.$1.'_points',"Points [Part: $1]"]);
 3051: 	}
 3052: 	push(@fields,\@datum);
 3053:     }
 3054:     return (@fields);
 3055: }
 3056: 
 3057: sub csvuploadmap_footer {
 3058:     my ($request,$i,$keyfields) =@_;
 3059:     $request->print(<<ENDPICK);
 3060: </table>
 3061: <input type="hidden" name="nfields" value="$i" />
 3062: <input type="hidden" name="keyfields" value="$keyfields" />
 3063: <input type="button" onClick="javascript:verify(this.form)" value="Assign Grades" /><br />
 3064: </form>
 3065: ENDPICK
 3066: }
 3067: 
 3068: sub upcsvScores_form {
 3069:     my ($request) = shift;
 3070:     my ($symb,$url)=&get_symb_and_url($request);
 3071:     if (!$symb) {return '';}
 3072:     my $result =<<CSVFORMJS;
 3073: <script type="text/javascript" language="javascript">
 3074:     function checkUpload(formname) {
 3075: 	if (formname.upfile.value == "") {
 3076: 	    alert("Please use the browse button to select a file from your local directory.");
 3077: 	    return false;
 3078: 	}
 3079: 	formname.submit();
 3080:     }
 3081:     </script>
 3082: CSVFORMJS
 3083:     $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
 3084:     my ($table) = &showResourceInfo($url,$env{'form.probTitle'});
 3085:     $result.=$table;
 3086:     $result.='<br /><table width=100% border=0><tr><td bgcolor="#777777">'."\n";
 3087:     $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";
 3088:     $result.='&nbsp;<b>Specify a file containing the class scores for current resource'.
 3089: 	'.</b></td></tr>'."\n";
 3090:     $result.='<tr bgcolor=#ffffe6><td>'."\n";
 3091:     my $upfile_select=&Apache::loncommon::upfile_select_html();
 3092:     my $ignore=&mt('Ignore First Line');
 3093:     $result.=<<ENDUPFORM;
 3094: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 3095: <input type="hidden" name="symb" value="$symb" />
 3096: <input type="hidden" name="url" value="$url" />
 3097: <input type="hidden" name="command" value="csvuploadmap" />
 3098: <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
 3099: <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
 3100: $upfile_select
 3101: <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scores" />
 3102: <label><input type="checkbox" name="noFirstLine" />$ignore</lable>
 3103: </form>
 3104: ENDUPFORM
 3105:     $result.='</td></tr></table>'."\n";
 3106:     $result.='</td></tr></table><br /><br />'."\n";
 3107:     $result.=&show_grading_menu_form($symb,$url);
 3108:     return $result;
 3109: }
 3110: 
 3111: 
 3112: sub csvuploadmap {
 3113:     my ($request)= @_;
 3114:     my ($symb,$url)=&get_symb_and_url($request);
 3115:     if (!$symb) {return '';}
 3116: 
 3117:     my $datatoken;
 3118:     if (!$env{'form.datatoken'}) {
 3119: 	$datatoken=&Apache::loncommon::upfile_store($request);
 3120:     } else {
 3121: 	$datatoken=$env{'form.datatoken'};
 3122: 	&Apache::loncommon::load_tmp_file($request);
 3123:     }
 3124:     my @records=&Apache::loncommon::upfile_record_sep();
 3125:     if ($env{'form.noFirstLine'}) { shift(@records); }
 3126:     &csvuploadmap_header($request,$symb,$url,$datatoken,$#records+1);
 3127:     my ($i,$keyfields);
 3128:     if (@records) {
 3129: 	my @fields=&csvupload_fields($url,$symb);
 3130: 
 3131: 	if ($env{'form.upfile_associate'} eq 'reverse') {	
 3132: 	    &Apache::loncommon::csv_print_samples($request,\@records);
 3133: 	    $i=&Apache::loncommon::csv_print_select_table($request,\@records,
 3134: 							  \@fields);
 3135: 	    foreach (@fields) { $keyfields.=$_->[0].','; }
 3136: 	    chop($keyfields);
 3137: 	} else {
 3138: 	    unshift(@fields,['none','']);
 3139: 	    $i=&Apache::loncommon::csv_samples_select_table($request,\@records,
 3140: 							    \@fields);
 3141: 	    my %sone=&Apache::loncommon::record_sep($records[0]);
 3142: 	    $keyfields=join(',',sort(keys(%sone)));
 3143: 	}
 3144:     }
 3145:     &csvuploadmap_footer($request,$i,$keyfields);
 3146:     $request->print(&show_grading_menu_form($symb,$url));
 3147: 
 3148:     return '';
 3149: }
 3150: 
 3151: sub csvuploadoptions {
 3152:     my ($request)= @_;
 3153:     my ($symb,$url)=&get_symb_and_url($request);
 3154:     my $checked=(($env{'form.noFirstLine'})?'1':'0');
 3155:     my $ignore=&mt('Ignore First Line');
 3156:     $request->print(<<ENDPICK);
 3157: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 3158: <h3><font color="#339933">Uploading Class Grade Options</font></h3>
 3159: <input type="hidden" name="command"    value="csvuploadassign" />
 3160: <input type="submit" value="Assign Grades" /><br />
 3161: <p>
 3162: <label>
 3163:    <input type="checkbox" name="show_full_results" />
 3164:    Show a table of all changes
 3165: </label>
 3166: </p>
 3167: <p>
 3168: <label>
 3169:    <input type="checkbox" name="overwite_scores" checked="checked" />
 3170:    Overwrite any existing score
 3171: </label>
 3172: </p>
 3173: ENDPICK
 3174:     my %fields=&get_fields();
 3175:     if (!defined($fields{'domain'})) {
 3176: 	my $domform = &Apache::loncommon::select_dom_form($env{'request.role.domain'},'default_domain');
 3177: 	$request->print("\n<p> Users are in domain: ".$domform."</p>\n");
 3178:     }
 3179:     foreach my $key (sort(keys(%env))) {
 3180: 	if ($key !~ /^form\.(.*)$/) { next; }
 3181: 	my $cleankey=$1;
 3182: 	if ($cleankey eq 'command') { next; }
 3183: 	$request->print('<input type="hidden" name="'.$cleankey.
 3184: 			'"  value="'.$env{$key}.'" />'."\n");
 3185:     }
 3186:     # FIXME do a check for any duplicated user ids...
 3187:     # FIXME do a check for any invalid user ids?...
 3188:     $request->print("<hr /></form>\n");
 3189:     $request->print(&show_grading_menu_form($symb,$url));
 3190:     return '';
 3191: }
 3192: 
 3193: sub get_fields {
 3194:     my %fields;
 3195:     my @keyfields = split(/\,/,$env{'form.keyfields'});
 3196:     for (my $i=0; $i<=$env{'form.nfields'}; $i++) {
 3197: 	if ($env{'form.upfile_associate'} eq 'reverse') {
 3198: 	    if ($env{'form.f'.$i} ne 'none') {
 3199: 		$fields{$keyfields[$i]}=$env{'form.f'.$i};
 3200: 	    }
 3201: 	} else {
 3202: 	    if ($env{'form.f'.$i} ne 'none') {
 3203: 		$fields{$env{'form.f'.$i}}=$keyfields[$i];
 3204: 	    }
 3205: 	}
 3206:     }
 3207:     return %fields;
 3208: }
 3209: 
 3210: sub csvuploadassign {
 3211:     my ($request)= @_;
 3212:     my ($symb,$url)=&get_symb_and_url($request);
 3213:     if (!$symb) {return '';}
 3214:     &Apache::loncommon::load_tmp_file($request);
 3215:     my @gradedata = &Apache::loncommon::upfile_record_sep();
 3216:     if ($env{'form.noFirstLine'}) { shift(@gradedata); }
 3217:     my %fields=&get_fields();
 3218:     $request->print('<h3>Assigning Grades</h3>');
 3219:     my $courseid=$env{'request.course.id'};
 3220:     my ($classlist) = &getclasslist('all',0);
 3221:     my @notallowed;
 3222:     my @skipped;
 3223:     my $countdone=0;
 3224:     foreach my $grade (@gradedata) {
 3225: 	my %entries=&Apache::loncommon::record_sep($grade);
 3226: 	my $domain;
 3227: 	if ($entries{$fields{'domain'}}) {
 3228: 	    $domain=$entries{$fields{'domain'}};
 3229: 	} else {
 3230: 	    $domain=$env{'form.default_domain'};
 3231: 	}
 3232: 	$domain=~s/\s//g;
 3233: 	my $username=$entries{$fields{'username'}};
 3234: 	$username=~s/\s//g;
 3235: 	if (!$username) {
 3236: 	    my $id=$entries{$fields{'ID'}};
 3237: 	    $id=~s/\s//g;
 3238: 	    my %ids=&Apache::lonnet::idget($domain,$id);
 3239: 	    $username=$ids{$id};
 3240: 	}
 3241: 	if (!exists($$classlist{"$username:$domain"})) {
 3242: 	    my $id=$entries{$fields{'ID'}};
 3243: 	    $id=~s/\s//g;
 3244: 	    if ($id) {
 3245: 		push(@skipped,"$id:$domain");
 3246: 	    } else {
 3247: 		push(@skipped,"$username:$domain");
 3248: 	    }
 3249: 	    next;
 3250: 	}
 3251: 	my $usec=$classlist->{"$username:$domain"}[5];
 3252: 	if (!&canmodify($usec)) {
 3253: 	    push(@notallowed,"$username:$domain");
 3254: 	    next;
 3255: 	}
 3256: 	my %points;
 3257: 	my %grades;
 3258: 	foreach my $dest (keys(%fields)) {
 3259: 	    if ($dest eq 'ID' || $dest eq 'username' ||
 3260: 		$dest eq 'domain') { next; }
 3261: 	    if ($entries{$fields{$dest}} =~ /^\s*$/) { next; }
 3262: 	    if ($dest=~/stores_(.*)_points/) {
 3263: 		my $part=$1;
 3264: 		my $wgt =&Apache::lonnet::EXT('resource.'.$part.'.weight',
 3265: 					      $symb,$domain,$username);
 3266: 		$entries{$fields{$dest}}=~s/\s//g;
 3267: 		my $pcr=$entries{$fields{$dest}} / $wgt;
 3268: 		my $award='correct_by_override';
 3269: 		$grades{"resource.$part.awarded"}=$pcr;
 3270: 		$grades{"resource.$part.solved"}=$award;
 3271: 		$points{$part}=1;
 3272: 	    } else {
 3273: 		if ($dest=~/stores_(.*)_awarded/) { if ($points{$1}) {next;} }
 3274: 		if ($dest=~/stores_(.*)_solved/)  { if ($points{$1}) {next;} }
 3275: 		my $store_key=$dest;
 3276: 		$store_key=~s/^stores/resource/;
 3277: 		$store_key=~s/_/\./g;
 3278: 		$grades{$store_key}=$entries{$fields{$dest}};
 3279: 	    }
 3280: 	}
 3281: 	if (! %grades) { push(@skipped,"$username:$domain no data to store"); }
 3282: 	$grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
 3283: #	&Apache::lonnet::logthis(" storing ".(join('-',%grades)));
 3284: 	&Apache::lonnet::cstore(\%grades,$symb,$env{'request.course.id'},
 3285: 				$domain,$username);
 3286: 	$request->print('.');
 3287: 	$request->rflush();
 3288: 	$countdone++;
 3289:     }
 3290:     $request->print("<br />Stored $countdone students\n");
 3291:     if (@skipped) {
 3292: 	$request->print('<p<font size="+1"><b>Skipped Students</b></font></p>');
 3293: 	foreach my $student (@skipped) { $request->print("$student<br />\n"); }
 3294:     }
 3295:     if (@notallowed) {
 3296: 	$request->print('<p><font size="+1" color="red"><b>Students Not Allowed to Modify</b></font></p>');
 3297: 	foreach my $student (@notallowed) { $request->print("$student<br />\n"); }
 3298:     }
 3299:     $request->print("<br />\n");
 3300:     $request->print(&show_grading_menu_form($symb,$url));
 3301:     return '';
 3302: }
 3303: #------------- end of section for handling csv file upload ---------
 3304: #
 3305: #-------------------------------------------------------------------
 3306: #
 3307: #-------------- Next few routines handle grading by page/sequence
 3308: #
 3309: #--- Select a page/sequence and a student to grade
 3310: sub pickStudentPage {
 3311:     my ($request) = shift;
 3312: 
 3313:     $request->print(<<LISTJAVASCRIPT);
 3314: <script type="text/javascript" language="javascript">
 3315: 
 3316: function checkPickOne(formname) {
 3317:     if (radioSelection(formname.student) == null) {
 3318: 	alert("Please select the student you wish to grade.");
 3319: 	return;
 3320:     }
 3321:     ptr = pullDownSelection(formname.selectpage);
 3322:     formname.page.value = formname["page"+ptr].value;
 3323:     formname.title.value = formname["title"+ptr].value;
 3324:     formname.submit();
 3325: }
 3326: 
 3327: </script>
 3328: LISTJAVASCRIPT
 3329:     &commonJSfunctions($request);
 3330:     my ($symb,$url) = &get_symb_and_url($request);
 3331:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
 3332:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
 3333:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
 3334: 
 3335:     my $result='<h3><font color="#339933">&nbsp;'.
 3336: 	'Manual Grading by Page or Sequence</font></h3>';
 3337: 
 3338:     $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";
 3339:     $result.='&nbsp;<b>Problems from:</b> <select name="selectpage">'."\n";
 3340:     my ($titles,$symbx) = &getSymbMap($request);
 3341:     my ($curpage) =&Apache::lonnet::decode_symb($symb); 
 3342: #    my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb); 
 3343: #    my $type=($curpage =~ /\.(page|sequence)/);
 3344:     my $ctr=0;
 3345:     foreach (@$titles) {
 3346: 	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
 3347: 	$result.='<option value="'.$ctr.'" '.
 3348: 	    ($$symbx{$_} =~ /$curpage$/ ? 'selected="on"' : '').
 3349: 	    '>'.$showtitle.'</option>'."\n";
 3350: 	$ctr++;
 3351:     }
 3352:     $result.= '</select>'."<br>\n";
 3353:     $ctr=0;
 3354:     foreach (@$titles) {
 3355: 	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
 3356: 	$result.='<input type="hidden" name="page'.$ctr.'" value="'.$$symbx{$_}.'" />'."\n";
 3357: 	$result.='<input type="hidden" name="title'.$ctr.'" value="'.$showtitle.'" />'."\n";
 3358: 	$ctr++;
 3359:     }
 3360:     $result.='<input type="hidden" name="page" />'."\n".
 3361: 	'<input type="hidden" name="title" />'."\n";
 3362: 
 3363:     $result.='&nbsp;<b>View Problems Text: </b><input type="radio" name="vProb" value="no" checked="on" /> no '."\n".
 3364: 	'<input type="radio" name="vProb" value="yes" /> yes '."<br>\n";
 3365: 
 3366:     $result.='&nbsp;<b>Submission Details: </b>'.
 3367: 	'<input type="radio" name="lastSub" value="none" /> none'."\n".
 3368: 	'<input type="radio" name="lastSub" value="datesub" checked /> by dates and submissions'."\n".
 3369: 	'<input type="radio" name="lastSub" value="all" /> all details'."\n";
 3370: 
 3371:     $result.='<input type="hidden" name="section"     value="'.$getsec.'" />'."\n".
 3372: 	'<input type="hidden" name="Status"  value="'.$env{'form.Status'}.'" />'."\n".
 3373: 	'<input type="hidden" name="command" value="displayPage" />'."\n".
 3374: 	'<input type="hidden" name="url"     value="'.$url.'" />'."\n".
 3375: 	'<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
 3376: 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n";
 3377: 
 3378:     $result.='&nbsp;<input type="button" '.
 3379: 	'onClick="javascript:checkPickOne(this.form);"value="Next->" /><br />'."\n";
 3380: 
 3381:     $request->print($result);
 3382: 
 3383:     my $studentTable.='&nbsp;<b>Select a student you wish to grade and then click on the Next button.</b><br>'.
 3384: 	'<table border="0"><tr><td bgcolor="#777777">'.
 3385: 	'<table border="0"><tr bgcolor="#e6ffff">'.
 3386: 	'<td align="right">&nbsp;<b>No.</b></td>'.
 3387: 	'<td>'.&nameUserString('header').'</td>'.
 3388: 	'<td align="right">&nbsp;<b>No.</b></td>'.
 3389: 	'<td>'.&nameUserString('header').'</td></tr>';
 3390:  
 3391:     my (undef,undef,$fullname) = &getclasslist($getsec,'1');
 3392:     my $ptr = 1;
 3393:     foreach my $student (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {
 3394: 	my ($uname,$udom) = split(/:/,$student);
 3395: 	$studentTable.=($ptr%2 == 1 ? '<tr bgcolor="#ffffe6">' : '</td>');
 3396: 	$studentTable.='<td align="right">'.$ptr.'&nbsp;</td>';
 3397: 	$studentTable.='<td>&nbsp;<input type="radio" name="student" value="'.$student.'" /> '
 3398: 	    .&nameUserString(undef,$$fullname{$student},$uname,$udom)."\n";
 3399: 	$studentTable.=($ptr%2 == 0 ? '</td></tr>' : '');
 3400: 	$ptr++;
 3401:     }
 3402:     $studentTable.='</td><td>&nbsp;</td><td>&nbsp;' if ($ptr%2 == 0);
 3403:     $studentTable.='</td></tr></table></td></tr></table>'."\n";
 3404:     $studentTable.='<input type="button" '.
 3405: 	'onClick="javascript:checkPickOne(this.form);"value="Next->" /></form>'."\n";
 3406: 
 3407:     $studentTable.=&show_grading_menu_form($symb,$url);
 3408:     $request->print($studentTable);
 3409: 
 3410:     return '';
 3411: }
 3412: 
 3413: sub getSymbMap {
 3414:     my ($request) = @_;
 3415:     my $navmap = Apache::lonnavmaps::navmap->new();
 3416: 
 3417:     my %symbx = ();
 3418:     my @titles = ();
 3419:     my $minder = 0;
 3420: 
 3421:     # Gather every sequence that has problems.
 3422:     my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); },
 3423: 					       1,0,1);
 3424:     for my $sequence ($navmap->getById('0.0'), @sequences) {
 3425: 	if ($navmap->hasResource($sequence, sub { shift->is_problem(); }, 0) ) {
 3426: 	    my $title = $minder.'.'.$sequence->compTitle();
 3427: 	    push @titles, $title; # minder in case two titles are identical
 3428: 	    $symbx{$title} = $sequence->symb();
 3429: 	    $minder++;
 3430: 	}
 3431:     }
 3432:     return \@titles,\%symbx;
 3433: }
 3434: 
 3435: #
 3436: #--- Displays a page/sequence w/wo problems, w/wo submissions
 3437: sub displayPage {
 3438:     my ($request) = shift;
 3439: 
 3440:     my ($symb,$url) = &get_symb_and_url($request);
 3441:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
 3442:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
 3443:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
 3444:     my $pageTitle = $env{'form.page'};
 3445:     my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
 3446:     my ($uname,$udom) = split(/:/,$env{'form.student'});
 3447:     my $usec=$classlist->{$env{'form.student'}}[5];
 3448: 
 3449:     #need to make sure we have the correct data for later EXT calls, 
 3450:     #thus invalidate the cache
 3451:     &Apache::lonnet::devalidatecourseresdata(
 3452:                  $env{'course.'.$env{'request.course.id'}.'.num'},
 3453:                  $env{'course.'.$env{'request.course.id'}.'.domain'});
 3454:     &Apache::lonnet::clear_EXT_cache_status();
 3455: 
 3456:     if (!&canview($usec)) {
 3457: 	$request->print('<font color="red">Unable to view requested student.('.$env{'form.student'}.')</font>');
 3458: 	$request->print(&show_grading_menu_form($symb,$url));
 3459: 	return;
 3460:     }
 3461:     my $result='<h3><font color="#339933">&nbsp;'.$env{'form.title'}.'</font></h3>';
 3462:     $result.='<h3>&nbsp;Student: '.&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom).
 3463: 	'</h3>'."\n";
 3464:     &sub_page_js($request);
 3465:     $request->print($result);
 3466: 
 3467:     my $navmap = Apache::lonnavmaps::navmap->new();
 3468:     my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'});
 3469:     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
 3470: 
 3471:     my $iterator = $navmap->getIterator($map->map_start(),
 3472: 					$map->map_finish());
 3473: 
 3474:     my $studentTable='<form action="/adm/grades" method="post" name="gradePage">'."\n".
 3475: 	'<input type="hidden" name="command" value="gradeByPage" />'."\n".
 3476: 	'<input type="hidden" name="fullname" value="'.$$fullname{$env{'form.student'}}.'" />'."\n".
 3477: 	'<input type="hidden" name="student" value="'.$env{'form.student'}.'" />'."\n".
 3478: 	'<input type="hidden" name="page"    value="'.$pageTitle.'" />'."\n".
 3479: 	'<input type="hidden" name="title"   value="'.$env{'form.title'}.'" />'."\n".
 3480: 	'<input type="hidden" name="url"     value="'.$url.'" />'."\n".
 3481: 	'<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
 3482: 	'<input type="hidden" name="overRideScore" value="no" />'."\n".
 3483: 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n";
 3484: 
 3485:     my $checkIcon = '<img src="'.$request->dir_config('lonIconsURL').
 3486: 	'/check.gif" height="16" border="0" />';
 3487: 
 3488:     $studentTable.='&nbsp;<b>Note:</b> Problems graded correct by the computer are marked with a '.$checkIcon.
 3489: 	' symbol.'."\n".
 3490: 	'<table border="0"><tr><td bgcolor="#777777">'.
 3491: 	'<table border="0"><tr bgcolor="#e6ffff">'.
 3492: 	'<td align="center"><b>&nbsp;Prob.&nbsp;</b></td>'.
 3493: 	'<td><b>&nbsp;'.($env{'form.vProb'} eq 'no' ? 'Title' : 'Problem Text').'/Grade</b></td></tr>';
 3494: 
 3495:     my ($depth,$question,$prob) = (1,1,1);
 3496:     $iterator->next(); # skip the first BEGIN_MAP
 3497:     my $curRes = $iterator->next(); # for "current resource"
 3498:     while ($depth > 0) {
 3499:         if($curRes == $iterator->BEGIN_MAP) { $depth++; }
 3500:         if($curRes == $iterator->END_MAP) { $depth--; }
 3501: 
 3502:         if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout) {
 3503: 	    my $parts = $curRes->parts();
 3504:             my $title = $curRes->compTitle();
 3505: 	    my $symbx = $curRes->symb();
 3506: 	    $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$prob.
 3507: 		(scalar(@{$parts}) == 1 ? '' : '<br>('.scalar(@{$parts}).'&nbsp;parts)').'</td>';
 3508: 	    $studentTable.='<td valign="top">';
 3509: 	    if ($env{'form.vProb'} eq 'yes' ) {
 3510: 		$studentTable.=&show_problem($request,$symbx,$uname,$udom,1,
 3511: 					     undef,'both');
 3512: 	    } else {
 3513: 		my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'});
 3514: 		$companswer =~ s|<form(.*?)>||g;
 3515: 		$companswer =~ s|</form>||g;
 3516: #		while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a>
 3517: #		    $companswer =~ s/$1/ /ms;
 3518: #		    $request->print('match='.$1."<br>\n");
 3519: #		}
 3520: #		$companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;
 3521: 		$studentTable.='&nbsp;<b>'.$title.'</b>&nbsp;<br>&nbsp;<b>Correct answer:</b><br>'.$companswer;
 3522: 	    }
 3523: 
 3524: 	    my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
 3525: 
 3526: 	    if ($env{'form.lastSub'} eq 'datesub') {
 3527: 		if ($record{'version'} eq '') {
 3528: 		    $studentTable.='<br />&nbsp;<font color="red">No recorded submission for this problem</font><br />';
 3529: 		} else {
 3530: 		    my %responseType = ();
 3531: 		    foreach my $partid (@{$parts}) {
 3532: 			my @responseIds =$curRes->responseIds($partid);
 3533: 			my @responseType =$curRes->responseType($partid);
 3534: 			my %responseIds;
 3535: 			for (my $i=0;$i<=$#responseIds;$i++) {
 3536: 			    $responseIds{$responseIds[$i]}=$responseType[$i];
 3537: 			}
 3538: 			$responseType{$partid} = \%responseIds;
 3539: 		    }
 3540: 		    $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom);
 3541: 
 3542: 		}
 3543: 	    } elsif ($env{'form.lastSub'} eq 'all') {
 3544: 		my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
 3545: 		$studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom,
 3546: 									$env{'request.course.id'},
 3547: 									'','.submission');
 3548:  
 3549: 	    }
 3550: 	    if (&canmodify($usec)) {
 3551: 		foreach my $partid (@{$parts}) {
 3552: 		    $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record);
 3553: 		    $studentTable.='<input type="hidden" name="q_'.$question.'" value="'.$partid.'" />'."\n";
 3554: 		    $question++;
 3555: 		}
 3556: 		$prob++;
 3557: 	    }
 3558: 	    $studentTable.='</td></tr>';
 3559: 
 3560: 	}
 3561:         $curRes = $iterator->next();
 3562:     }
 3563: 
 3564:     $studentTable.='</td></tr></table></td></tr></table>'."\n".
 3565: 	'<input type="button" value="Save" '.
 3566: 	'onClick="javascript:checkSubmitPage(this.form,'.$question.');" TARGET=_self />'.
 3567: 	'</form>'."\n";
 3568:     $studentTable.=&show_grading_menu_form($symb,$url);
 3569:     $request->print($studentTable);
 3570: 
 3571:     return '';
 3572: }
 3573: 
 3574: sub displaySubByDates {
 3575:     my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;
 3576:     my $isCODE=0;
 3577:     if (exists($record->{'resource.CODE'})) { $isCODE=1; }
 3578:     my $studentTable='<table border="0" width="100%"><tr><td bgcolor="#777777">'.
 3579: 	'<table border="0" width="100%"><tr bgcolor="#e6ffff">'.
 3580: 	'<td><b>Date/Time</b></td>'.
 3581: 	($isCODE?'<td><b>CODE</b></td>':'').
 3582: 	'<td><b>Submission</b></td>'.
 3583: 	'<td><b>Status&nbsp;</b></td></tr>';
 3584:     my ($version);
 3585:     my %mark;
 3586:     my %orders;
 3587:     $mark{'correct_by_student'} = $checkIcon;
 3588:     if (!exists($$record{'1:timestamp'})) {
 3589: 	return '<br />&nbsp;<font color="red">Nothing submitted - no attempts</font><br />';
 3590:     }
 3591:     for ($version=1;$version<=$$record{'version'};$version++) {
 3592: 	my $timestamp = scalar(localtime($$record{$version.':timestamp'}));
 3593: 	$studentTable.='<tr bgcolor="#ffffff" valign="top"><td>'.$timestamp.'</td>';
 3594: 	if ($isCODE) {
 3595: 	    $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';
 3596: 	}
 3597: 	my @versionKeys = split(/\:/,$$record{$version.':keys'});
 3598: 	my @displaySub = ();
 3599: 	foreach my $partid (@{$parts}) {
 3600: 	    my @matchKey = sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys);
 3601: #	    next if ($$record{"$version:resource.$partid.solved"} eq '');
 3602: 	    my $display_part=&get_display_part($partid,undef,$symb);
 3603: 	    foreach my $matchKey (@matchKey) {
 3604: 		if (exists($$record{$version.':'.$matchKey}) &&
 3605: 		    $$record{$version.':'.$matchKey} ne '') {
 3606: 		    my ($responseId)=($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/);
 3607: 		    $displaySub[0].='<b>Part:</b>&nbsp;'.$display_part.'&nbsp;';
 3608: 		    $displaySub[0].='<font color="#999999">(ID&nbsp;'.
 3609: 			$responseId.')</font>&nbsp;<b>';
 3610: 		    if ($$record{"$version:resource.$partid.tries"} eq '') {
 3611: 			$displaySub[0].='Trial&nbsp;not&nbsp;counted';
 3612: 		    } else {
 3613: 			$displaySub[0].='Trial&nbsp;'.
 3614: 			    $$record{"$version:resource.$partid.tries"};
 3615: 		    }
 3616: 		    my $responseType=$responseType->{$partid}->{$responseId};
 3617: 		    if (!exists($orders{$partid})) { $orders{$partid}={}; }
 3618: 		    if (!exists($orders{$partid}->{$responseId})) {
 3619: 			$orders{$partid}->{$responseId}=
 3620: 			    &get_order($partid,$responseId,$symb,$uname,$udom);
 3621: 		    }
 3622: 		    $displaySub[0].='</b>&nbsp; '.
 3623: 			&cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:").'<br />';
 3624: 		}
 3625: 	    }
 3626: 	    if (exists $$record{"$version:resource.$partid.award"}) {
 3627: 		$displaySub[1].='<b>Part:</b>&nbsp;'.$display_part.' &nbsp;'.
 3628: 		    lc($$record{"$version:resource.$partid.award"}).' '.
 3629: 		    $mark{$$record{"$version:resource.$partid.solved"}}.
 3630: 		    '<br />';
 3631: 	    }
 3632: 	    if (exists $$record{"$version:resource.$partid.regrader"}) {
 3633: 		$displaySub[2].=$$record{"$version:resource.$partid.regrader"}.
 3634: 		    ' (<b>'.&mt('Part').':</b> '.$display_part.')';
 3635: 	    }
 3636: 	}
 3637: 	# needed because old essay regrader has not parts info
 3638: 	if (exists $$record{"$version:resource.regrader"}) {
 3639: 	    $displaySub[2].=$$record{"$version:resource.regrader"};
 3640: 	}
 3641: 	$studentTable.='<td>'.$displaySub[0].'&nbsp;</td><td>'.$displaySub[1];
 3642: 	if ($displaySub[2]) {
 3643: 	    $studentTable.='Manually graded by '.$displaySub[2];
 3644: 	}
 3645: 	$studentTable.='&nbsp;</td></tr>';
 3646:     
 3647:     }
 3648:     $studentTable.='</table></td></tr></table>';
 3649:     return $studentTable;
 3650: }
 3651: 
 3652: sub updateGradeByPage {
 3653:     my ($request) = shift;
 3654: 
 3655:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
 3656:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
 3657:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
 3658:     my $pageTitle = $env{'form.page'};
 3659:     my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
 3660:     my ($uname,$udom) = split(/:/,$env{'form.student'});
 3661:     my $usec=$classlist->{$env{'form.student'}}[5];
 3662:     if (!&canmodify($usec)) {
 3663: 	$request->print('<font color="red">Unable to modify requested student.('.$env{'form.student'}.'</font>');
 3664: 	$request->print(&show_grading_menu_form($env{'form.symb'},$env{'form.url'}));
 3665: 	return;
 3666:     }
 3667:     my $result='<h3><font color="#339933">&nbsp;'.$env{'form.title'}.'</font></h3>';
 3668:     $result.='<h3>&nbsp;Student: '.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
 3669: 	'</h3>'."\n";
 3670: 
 3671:     $request->print($result);
 3672: 
 3673:     my $navmap = Apache::lonnavmaps::navmap->new();
 3674:     my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'});
 3675:     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
 3676: 
 3677:     my $iterator = $navmap->getIterator($map->map_start(),
 3678: 					$map->map_finish());
 3679: 
 3680:     my $studentTable='<table border="0"><tr><td bgcolor="#777777">'.
 3681: 	'<table border="0"><tr bgcolor="#e6ffff">'.
 3682: 	'<td align="center"><b>&nbsp;Prob.&nbsp;</b></td>'.
 3683: 	'<td><b>&nbsp;Title&nbsp;</b></td>'.
 3684: 	'<td><b>&nbsp;Previous Score&nbsp;</b></td>'.
 3685: 	'<td><b>&nbsp;New Score&nbsp;</b></td></tr>';
 3686: 
 3687:     $iterator->next(); # skip the first BEGIN_MAP
 3688:     my $curRes = $iterator->next(); # for "current resource"
 3689:     my ($depth,$question,$prob,$changeflag)= (1,1,1,0);
 3690:     while ($depth > 0) {
 3691:         if($curRes == $iterator->BEGIN_MAP) { $depth++; }
 3692:         if($curRes == $iterator->END_MAP) { $depth--; }
 3693: 
 3694:         if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout) {
 3695: 	    my $parts = $curRes->parts();
 3696:             my $title = $curRes->compTitle();
 3697: 	    my $symbx = $curRes->symb();
 3698: 	    $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$prob.
 3699: 		(scalar(@{$parts}) == 1 ? '' : '<br>('.scalar(@{$parts}).'&nbsp;parts)').'</td>';
 3700: 	    $studentTable.='<td valign="top">&nbsp;<b>'.$title.'</b>&nbsp;</td>';
 3701: 
 3702: 	    my %newrecord=();
 3703: 	    my @displayPts=();
 3704:             my %aggregate = ();
 3705:             my $aggregateflag = 0;
 3706: 	    foreach my $partid (@{$parts}) {
 3707: 		my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};
 3708: 		my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};
 3709: 
 3710: 		my $wgt = $env{'form.WGT'.$question.'_'.$partid} != 0 ? 
 3711: 		    $env{'form.WGT'.$question.'_'.$partid} : 1;
 3712: 		my $partial = $newpts/$wgt;
 3713: 		my $score;
 3714: 		if ($partial > 0) {
 3715: 		    $score = 'correct_by_override';
 3716: 		} elsif ($newpts ne '') { #empty is taken as 0
 3717: 		    $score = 'incorrect_by_override';
 3718: 		}
 3719: 		my $dropMenu = $env{'form.GD_SEL'.$question.'_'.$partid};
 3720: 		if ($dropMenu eq 'excused') {
 3721: 		    $partial = '';
 3722: 		    $score = 'excused';
 3723: 		} elsif ($dropMenu eq 'reset status'
 3724: 			 && $env{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists
 3725: 		    $newrecord{'resource.'.$partid.'.tries'} = 0;
 3726: 		    $newrecord{'resource.'.$partid.'.solved'} = '';
 3727: 		    $newrecord{'resource.'.$partid.'.award'} = '';
 3728: 		    $newrecord{'resource.'.$partid.'.awarded'} = 0;
 3729: 		    $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}";
 3730: 		    $changeflag++;
 3731: 		    $newpts = '';
 3732:                     
 3733:                     my $aggtries =  $env{'form.aggtries'.$question.'_'.$partid};
 3734:                     my $totaltries = $env{'form.totaltries'.$question.'_'.$partid};
 3735:                     my $solvedstatus = $env{'form.solved'.$question.'_'.$partid};
 3736:                     if ($aggtries > 0) {
 3737:                         &decrement_aggs($symbx,$partid,\%aggregate,$aggtries,$totaltries,$solvedstatus);
 3738:                         $aggregateflag = 1;
 3739:                     }
 3740: 		}
 3741: 		my $display_part=&get_display_part($partid,undef,
 3742: 						   $curRes->symb());
 3743: 		my $oldstatus = $env{'form.solved'.$question.'_'.$partid};
 3744: 		$displayPts[0].='&nbsp;<b>Part:</b> '.$display_part.' = '.
 3745: 		    (($oldstatus eq 'excused') ? 'excused' : $oldpts).
 3746: 		    '&nbsp;<br>';
 3747: 		$displayPts[1].='&nbsp;<b>Part:</b> '.$display_part.' = '.
 3748: 		     (($score eq 'excused') ? 'excused' : $newpts).
 3749: 		    '&nbsp;<br>';
 3750: 
 3751: 		$question++;
 3752: 		next if ($dropMenu eq 'reset status' || ($newpts == $oldpts && $score ne 'excused'));
 3753: 
 3754: 		$newrecord{'resource.'.$partid.'.awarded'}  = $partial if $partial ne '';
 3755: 		$newrecord{'resource.'.$partid.'.solved'}   = $score if $score ne '';
 3756: 		$newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}"
 3757: 		    if (scalar(keys(%newrecord)) > 0);
 3758: 
 3759: 		$changeflag++;
 3760: 	    }
 3761: 	    if (scalar(keys(%newrecord)) > 0) {
 3762: 		&Apache::lonnet::cstore(\%newrecord,$symbx,$env{'request.course.id'},
 3763: 					$udom,$uname);
 3764: 	    }
 3765:             if ($aggregateflag) {
 3766:                 &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
 3767:                       $env{'course.'.$env{'request.course.id'}.'.domain'},
 3768:                       $env{'course.'.$env{'request.course.id'}.'.num'});
 3769:             }
 3770: 
 3771: 	    $studentTable.='<td valign="top">'.$displayPts[0].'</td>'.
 3772: 		'<td valign="top">'.$displayPts[1].'</td>'.
 3773: 		'</tr>';
 3774: 
 3775: 	    $prob++;
 3776: 	}
 3777:         $curRes = $iterator->next();
 3778:     }
 3779: 
 3780:     $studentTable.='</td></tr></table></td></tr></table>';
 3781:     $studentTable.=&show_grading_menu_form($env{'form.symb'},$env{'form.url'});
 3782:     my $grademsg=($changeflag == 0 ? 'No score was changed or updated.' :
 3783: 		  'The scores were changed for '.
 3784: 		  $changeflag.' problem'.($changeflag == 1 ? '.' : 's.'));
 3785:     $request->print($grademsg.$studentTable);
 3786: 
 3787:     return '';
 3788: }
 3789: 
 3790: #-------- end of section for handling grading by page/sequence ---------
 3791: #
 3792: #-------------------------------------------------------------------
 3793: 
 3794: #--------------------Scantron Grading-----------------------------------
 3795: #
 3796: #------ start of section for handling grading by page/sequence ---------
 3797: 
 3798: sub defaultFormData {
 3799:     my ($symb,$url)=@_;
 3800:     return '
 3801:       <input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
 3802:      '<input type="hidden" name="url"     value="'.$url.'" />'."\n".
 3803:      '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
 3804:      '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
 3805: }
 3806: 
 3807: sub getSequenceDropDown {
 3808:     my ($request,$symb)=@_;
 3809:     my $result='<select name="selectpage">'."\n";
 3810:     my ($titles,$symbx) = &getSymbMap($request);
 3811:     my ($curpage)=&Apache::lonnet::decode_symb($symb); 
 3812:     my $ctr=0;
 3813:     foreach (@$titles) {
 3814: 	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
 3815: 	$result.='<option value="'.$$symbx{$_}.'" '.
 3816: 	    ($$symbx{$_} =~ /$curpage$/ ? 'selected="on"' : '').
 3817: 	    '>'.$showtitle.'</option>'."\n";
 3818: 	$ctr++;
 3819:     }
 3820:     $result.= '</select>';
 3821:     return $result;
 3822: }
 3823: 
 3824: sub scantron_filenames {
 3825:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 3826:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 3827:     my @files=&Apache::lonnet::dirlist('userfiles',$cdom,$cname,
 3828: 				    &Apache::loncommon::propath($cdom,$cname));
 3829:     my @possiblenames;
 3830:     foreach my $filename (sort(@files)) {
 3831: 	($filename)=split(/&/,$filename);
 3832: 	if ($filename!~/^scantron_orig_/) { next ; }
 3833: 	$filename=~s/^scantron_orig_//;
 3834: 	push(@possiblenames,$filename);
 3835:     }
 3836:     return @possiblenames;
 3837: }
 3838: 
 3839: sub scantron_uploads {
 3840:     my ($file2grade) = @_;
 3841:     my $result=	'<select name="scantron_selectfile">';
 3842:     $result.="<option></option>";
 3843:     foreach my $filename (sort(&scantron_filenames())) {
 3844: 	$result.="<option".($filename eq $file2grade ? ' selected="on"':'').">$filename</option>\n";
 3845:     }
 3846:     $result.="</select>";
 3847:     return $result;
 3848: }
 3849: 
 3850: sub scantron_scantab {
 3851:     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
 3852:     my $result='<select name="scantron_format">'."\n";
 3853:     $result.='<option></option>'."\n";
 3854:     foreach my $line (<$fh>) {
 3855: 	my ($name,$descrip)=split(/:/,$line);
 3856: 	if ($name =~ /^\#/) { next; }
 3857: 	$result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";
 3858:     }
 3859:     $result.='</select>'."\n";
 3860: 
 3861:     return $result;
 3862: }
 3863: 
 3864: sub scantron_CODElist {
 3865:     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
 3866:     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
 3867:     my @names=&Apache::lonnet::getkeys('CODEs',$cdom,$cnum);
 3868:     my $namechoice='<option></option>';
 3869:     foreach my $name (sort {uc($a) cmp uc($b)} @names) {
 3870: 	if ($name =~ /^error: 2 /) { next; }
 3871: 	$namechoice.='<option value="'.$name.'">'.$name.'</option>';
 3872:     }
 3873:     $namechoice='<select name="scantron_CODElist">'.$namechoice.'</select>';
 3874:     return $namechoice;
 3875: }
 3876: 
 3877: sub scantron_CODEunique {
 3878:     my $result='<nobr>
 3879:                  <input type="radio" name="scantron_CODEunique"
 3880:                         value="Yes" checked="on" /> Yes
 3881:                 </nobr>
 3882:                 <nobr>
 3883:                  <input type="radio" name="scantron_CODEunique"
 3884:                         value="No" /> No
 3885:                 </nobr>';
 3886:     return $result;
 3887: }
 3888: 
 3889: sub scantron_selectphase {
 3890:     my ($r,$file2grade) = @_;
 3891:     my ($symb,$url)=&get_symb_and_url($r);
 3892:     if (!$symb) {return '';}
 3893:     my $sequence_selector=&getSequenceDropDown($r,$symb);
 3894:     my $default_form_data=&defaultFormData($symb,$url);
 3895:     my $grading_menu_button=&show_grading_menu_form($symb,$url);
 3896:     my $file_selector=&scantron_uploads($file2grade);
 3897:     my $format_selector=&scantron_scantab();
 3898:     my $CODE_selector=&scantron_CODElist();
 3899:     my $CODE_unique=&scantron_CODEunique();
 3900:     my $result;
 3901:     #FIXME allow instructor to be able to download the scantron file
 3902:     # and to upload it,
 3903:     $result.= <<SCANTRONFORM;
 3904:     <table width="100%" border="0">
 3905:     <tr>
 3906:      <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">
 3907:       <td bgcolor="#777777">
 3908:        <input type="hidden" name="command" value="scantron_warning" />
 3909:         $default_form_data
 3910:         <table width="100%" border="0">
 3911:           <tr bgcolor="#e6ffff">
 3912:             <td colspan="2">
 3913:               &nbsp;<b>Specify file and which Folder/Sequence to grade</b>
 3914:             </td>
 3915:           </tr>
 3916:           <tr bgcolor="#ffffe6">
 3917:             <td> Sequence to grade: </td><td> $sequence_selector </td>
 3918:           </tr>
 3919:           <tr bgcolor="#ffffe6">
 3920:             <td> Filename of scoring office file: </td><td> $file_selector </td>
 3921:           </tr>
 3922:           <tr bgcolor="#ffffe6">
 3923:             <td> Format of data file: </td><td> $format_selector </td>
 3924:           </tr>
 3925:           <tr bgcolor="#ffffe6">
 3926:             <td> Saved CODEs to validate against: </td><td> $CODE_selector</td>
 3927:           </tr>
 3928:           <tr bgcolor="#ffffe6">
 3929:             <td> Each CODE is only to be used once:</td><td> $CODE_unique </td>
 3930:           </tr>
 3931:           <tr bgcolor="#ffffe6">
 3932: 	    <td> Options: </td>
 3933:             <td>
 3934:                 <input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> Do only previously skipped records <br />
 3935:                 <input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> Remove all exisiting corrections
 3936: 	    </td>
 3937:           </tr>
 3938:           <tr bgcolor="#ffffe6">
 3939:             <td colspan="2">
 3940:               <input type="submit" value="Grading: Validate Scantron Records" />
 3941:             </td>
 3942:           </tr>
 3943:         </table>
 3944:        </td>
 3945:      </form>
 3946:     </tr>
 3947: SCANTRONFORM
 3948:    
 3949:     $r->print($result);
 3950: 
 3951:     if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
 3952:         &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
 3953: 
 3954:         $r->print(<<SCANTRONFORM);
 3955:     <tr>
 3956:       <td bgcolor="#777777">
 3957:         <table width="100%" border="0">
 3958:           <tr bgcolor="#e6ffff">
 3959:             <td>
 3960:               &nbsp;<b>Specify a Scantron data file to upload.</b>
 3961:             </td>
 3962:           </tr>
 3963:           <tr bgcolor="#ffffe6">
 3964:             <td>
 3965: SCANTRONFORM
 3966:     my $default_form_data=&defaultFormData(&get_symb_and_url($r,1));
 3967:     my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
 3968:     my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};
 3969:     $r->print(<<UPLOAD);
 3970:               <script type="text/javascript" language="javascript">
 3971:     function checkUpload(formname) {
 3972: 	if (formname.upfile.value == "") {
 3973: 	    alert("Please use the browse button to select a file from your local directory.");
 3974: 	    return false;
 3975: 	}
 3976: 	formname.submit();
 3977:     }
 3978:               </script>
 3979: 
 3980:               <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'>
 3981:                 $default_form_data
 3982:                 <input name='courseid' type='hidden' value='$cnum' />
 3983:                 <input name='domainid' type='hidden' value='$cdom' />
 3984:                 <input name='command' value='scantronupload_save' type='hidden' />
 3985:                 File to upload:<input type="file" name="upfile" size="50" />
 3986:                 <br />
 3987:                 <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" />
 3988:               </form>
 3989: UPLOAD
 3990: 
 3991:         $r->print(<<SCANTRONFORM);
 3992:             </td>
 3993:           </tr>
 3994:         </table>
 3995:       </td>
 3996:     </tr>
 3997: SCANTRONFORM
 3998:     }
 3999:     $r->print(<<SCANTRONFORM);
 4000:     <tr>
 4001:       <form action='/adm/grades' name='scantron_download'>
 4002:         <td bgcolor="#777777">
 4003:           <input type="hidden" name="command" value="scantron_download" />
 4004:           <table width="100%" border="0">
 4005:             <tr bgcolor="#e6ffff">
 4006:               <td colspan="2">
 4007:                 &nbsp;<b>Download a scoring office file</b>
 4008:               </td>
 4009:             </tr>
 4010:             <tr bgcolor="#ffffe6">
 4011:               <td> Filename of scoring office file: </td><td> $file_selector </td>
 4012:             </tr>
 4013:             <tr bgcolor="#ffffe6">
 4014:               <td colspan="2">
 4015:                 <input type="submit" value="Show List of Files" />
 4016:               </td>
 4017:             </tr>
 4018:           </table>
 4019:         </td>
 4020:       </form>
 4021:     </tr>
 4022: SCANTRONFORM
 4023: 
 4024:     $r->print(<<SCANTRONFORM);
 4025:   </table>
 4026: $grading_menu_button
 4027: SCANTRONFORM
 4028: 
 4029:     return
 4030: }
 4031: 
 4032: sub get_scantron_config {
 4033:     my ($which) = @_;
 4034:     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
 4035:     my %config;
 4036:     #FIXME probably should move to XML it has already gotten a bit much now
 4037:     foreach my $line (<$fh>) {
 4038: 	my ($name,$descrip)=split(/:/,$line);
 4039: 	if ($name ne $which ) { next; }
 4040: 	chomp($line);
 4041: 	my @config=split(/:/,$line);
 4042: 	$config{'name'}=$config[0];
 4043: 	$config{'description'}=$config[1];
 4044: 	$config{'CODElocation'}=$config[2];
 4045: 	$config{'CODEstart'}=$config[3];
 4046: 	$config{'CODElength'}=$config[4];
 4047: 	$config{'IDstart'}=$config[5];
 4048: 	$config{'IDlength'}=$config[6];
 4049: 	$config{'Qstart'}=$config[7];
 4050: 	$config{'Qlength'}=$config[8];
 4051: 	$config{'Qoff'}=$config[9];
 4052: 	$config{'Qon'}=$config[10];
 4053: 	$config{'PaperID'}=$config[11];
 4054: 	$config{'PaperIDlength'}=$config[12];
 4055: 	$config{'FirstName'}=$config[13];
 4056: 	$config{'FirstNamelength'}=$config[14];
 4057: 	$config{'LastName'}=$config[15];
 4058: 	$config{'LastNamelength'}=$config[16];
 4059: 	last;
 4060:     }
 4061:     return %config;
 4062: }
 4063: 
 4064: sub username_to_idmap {
 4065:     my ($classlist)= @_;
 4066:     my %idmap;
 4067:     foreach my $student (keys(%$classlist)) {
 4068: 	$idmap{$classlist->{$student}->[&Apache::loncoursedata::CL_ID]}=
 4069: 	    $student;
 4070:     }
 4071:     return %idmap;
 4072: }
 4073: 
 4074: sub scantron_fixup_scanline {
 4075:     my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
 4076:     if ($field eq 'ID') {
 4077: 	if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {
 4078: 	    return ($line,1,'New value too large');
 4079: 	}
 4080: 	if (length($args->{'newid'}) < $$scantron_config{'IDlength'}) {
 4081: 	    $args->{'newid'}=sprintf('%-'.$$scantron_config{'IDlength'}.'s',
 4082: 				     $args->{'newid'});
 4083: 	}
 4084: 	substr($line,$$scantron_config{'IDstart'}-1,
 4085: 	       $$scantron_config{'IDlength'})=$args->{'newid'};
 4086: 	if ($args->{'newid'}=~/^\s*$/) {
 4087: 	    &scan_data($scan_data,"$whichline.user",
 4088: 		       $args->{'username'}.':'.$args->{'domain'});
 4089: 	}
 4090:     } elsif ($field eq 'CODE') {
 4091: 	if ($args->{'CODE_ignore_dup'}) {
 4092: 	    &scan_data($scan_data,"$whichline.CODE_ignore_dup",'1');
 4093: 	}
 4094: 	&scan_data($scan_data,"$whichline.useCODE",'1');
 4095: 	if ($args->{'CODE'} ne 'use_unfound') {
 4096: 	    if (length($args->{'CODE'}) > $$scantron_config{'CODElength'}) {
 4097: 		return ($line,1,'New CODE value too large');
 4098: 	    }
 4099: 	    if (length($args->{'CODE'}) < $$scantron_config{'CODElength'}) {
 4100: 		$args->{'CODE'}=sprintf('%-'.$$scantron_config{'CODElength'}.'s',$args->{'CODE'});
 4101: 	    }
 4102: 	    substr($line,$$scantron_config{'CODEstart'}-1,
 4103: 		   $$scantron_config{'CODElength'})=$args->{'CODE'};
 4104: 	}
 4105:     } elsif ($field eq 'answer') {
 4106: 	my $length=$scantron_config->{'Qlength'};
 4107: 	my $off=$scantron_config->{'Qoff'};
 4108: 	my $on=$scantron_config->{'Qon'};
 4109: 	my $answer=${off}x$length;
 4110: 	if ($args->{'response'} eq 'none') {
 4111: 	    &scan_data($scan_data,
 4112: 		       "$whichline.no_bubble.".$args->{'question'},'1');
 4113: 	} else {
 4114: 	    substr($answer,$args->{'response'},1)=$on;
 4115: 	    &scan_data($scan_data,
 4116: 		       "$whichline.no_bubble.".$args->{'question'},undef,'1');
 4117: 	}
 4118: 	my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'};
 4119: 	substr($line,$where-1,$length)=$answer;
 4120:     }
 4121:     return $line;
 4122: }
 4123: 
 4124: sub scan_data {
 4125:     my ($scan_data,$key,$value,$delete)=@_;
 4126:     my $filename=$env{'form.scantron_selectfile'};
 4127:     if (defined($value)) {
 4128: 	$scan_data->{$filename.'_'.$key} = $value;
 4129:     }
 4130:     if ($delete) { delete($scan_data->{$filename.'_'.$key}); }
 4131:     return $scan_data->{$filename.'_'.$key};
 4132: }
 4133: 
 4134: sub scantron_parse_scanline {
 4135:     my ($line,$whichline,$scantron_config,$scan_data,$justHeader)=@_;
 4136:     my %record;
 4137:     my $questions=substr($line,$$scantron_config{'Qstart'}-1);
 4138:     my $data=substr($line,0,$$scantron_config{'Qstart'}-1);
 4139:     if ($$scantron_config{'CODElocation'} ne 0) {
 4140: 	if ($$scantron_config{'CODElocation'} < 0) {
 4141: 	    $record{'scantron.CODE'}=substr($data,
 4142: 					    $$scantron_config{'CODEstart'}-1,
 4143: 					    $$scantron_config{'CODElength'});
 4144: 	    if (&scan_data($scan_data,"$whichline.useCODE")) {
 4145: 		$record{'scantron.useCODE'}=1;
 4146: 	    }
 4147: 	    if (&scan_data($scan_data,"$whichline.CODE_ignore_dup")) {
 4148: 		$record{'scantron.CODE_ignore_dup'}=1;
 4149: 	    }
 4150: 	} else {
 4151: 	    #FIXME interpret first N questions
 4152: 	}
 4153:     }
 4154:     $record{'scantron.ID'}=substr($data,$$scantron_config{'IDstart'}-1,
 4155: 				  $$scantron_config{'IDlength'});
 4156:     $record{'scantron.PaperID'}=
 4157: 	substr($data,$$scantron_config{'PaperID'}-1,
 4158: 	       $$scantron_config{'PaperIDlength'});
 4159:     $record{'scantron.FirstName'}=
 4160: 	substr($data,$$scantron_config{'FirstName'}-1,
 4161: 	       $$scantron_config{'FirstNamelength'});
 4162:     $record{'scantron.LastName'}=
 4163: 	substr($data,$$scantron_config{'LastName'}-1,
 4164: 	       $$scantron_config{'LastNamelength'});
 4165:     if ($justHeader) { return \%record; }
 4166: 
 4167:     my @alphabet=('A'..'Z');
 4168:     my $questnum=0;
 4169:     while ($questions) {
 4170: 	$questnum++;
 4171: 	my $currentquest=substr($questions,0,$$scantron_config{'Qlength'});
 4172: 	substr($questions,0,$$scantron_config{'Qlength'})='';
 4173: 	if (length($currentquest) < $$scantron_config{'Qlength'}) { next; }
 4174: 	if ($$scantron_config{'Qon'} eq 'letter') {
 4175: 	    if (!$currentquest || $currentquest eq $$scantron_config{'Qoff'} ||
 4176: 		$currentquest !~ /^[A-Z]$/) {
 4177: 		$record{"scantron.$questnum.answer"}='';
 4178: 		if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
 4179: 		    push(@{$record{"scantron.missingerror"}},$questnum);
 4180: 		}
 4181: 	    } else {
 4182: 		$record{"scantron.$questnum.answer"}=$currentquest;
 4183: 	    }
 4184: 	} elsif ($$scantron_config{'Qon'} eq 'number') {
 4185: 	    if (!$currentquest || $currentquest eq $$scantron_config{'Qoff'} ||
 4186: 		$currentquest !~ /^\d$/) {
 4187: 		$record{"scantron.$questnum.answer"}='';
 4188: 		if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
 4189: 		    push(@{$record{"scantron.missingerror"}},$questnum);
 4190: 		}
 4191: 	    } else {
 4192: 		$record{"scantron.$questnum.answer"}=
 4193: 		    $alphabet[$currentquest-1];
 4194: 	    }
 4195: 	} else {
 4196: 	    my @array=split($$scantron_config{'Qon'},$currentquest,-1);
 4197: 	    if (length($array[0]) eq $$scantron_config{'Qlength'}) {
 4198: 		$record{"scantron.$questnum.answer"}='';
 4199: 		if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
 4200: 		    push(@{$record{"scantron.missingerror"}},$questnum);
 4201: 		}
 4202: 	    } else {
 4203: 		$record{"scantron.$questnum.answer"}=
 4204: 		    $alphabet[length($array[0])];
 4205: 	    }
 4206: 	    if (scalar(@array) gt 2) {
 4207: 		push(@{$record{'scantron.doubleerror'}},$questnum);
 4208: 		my @ans=@array;
 4209: 		my $i=length($ans[0]);shift(@ans);
 4210: 		while ($#ans) {
 4211: 		    $i+=length($ans[0])+1;
 4212: 		    $record{"scantron.$questnum.answer"}.=$alphabet[$i];
 4213: 		    shift(@ans);
 4214: 		}
 4215: 	    }
 4216: 	}
 4217:     }
 4218:     $record{'scantron.maxquest'}=$questnum;
 4219:     return \%record;
 4220: }
 4221: 
 4222: sub scantron_add_delay {
 4223:     my ($delayqueue,$scanline,$errormessage,$errorcode)=@_;
 4224:     push(@$delayqueue,
 4225: 	 {'line' => $scanline, 'emsg' => $errormessage,
 4226: 	  'ecode' => $errorcode }
 4227: 	 );
 4228: }
 4229: 
 4230: sub scantron_find_student {
 4231:     my ($scantron_record,$scan_data,$idmap,$line)=@_;
 4232:     my $scanID=$$scantron_record{'scantron.ID'};
 4233:     if ($scanID =~ /^\s*$/) {
 4234:  	return &scan_data($scan_data,"$line.user");
 4235:     }
 4236:     foreach my $id (keys(%$idmap)) {
 4237:  	if (lc($id) eq lc($scanID)) {
 4238:  	    return $$idmap{$id};
 4239:  	}
 4240:     }
 4241:     return undef;
 4242: }
 4243: 
 4244: sub scantron_filter {
 4245:     my ($curres)=@_;
 4246:                         # randomout is dysfunctional at best for this purpose
 4247:     if (ref($curres) && $curres->is_problem()) { #&& !$curres->randomout) {
 4248: 	return 1;
 4249:     }
 4250:     return 0;
 4251: }
 4252: 
 4253: sub scantron_process_corrections {
 4254:     my ($r) = @_;
 4255:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
 4256:     my ($scanlines,$scan_data)=&scantron_getfile();
 4257:     my $classlist=&Apache::loncoursedata::get_classlist();
 4258:     my $which=$env{'form.scantron_line'};
 4259:     my $line=&scantron_get_line($scanlines,$scan_data,$which);
 4260:     my ($skip,$err,$errmsg);
 4261:     if ($env{'form.scantron_skip_record'}) {
 4262: 	$skip=1;
 4263:     } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) {
 4264: 	my $newstudent=$env{'form.scantron_username'}.':'.
 4265: 	    $env{'form.scantron_domain'};
 4266: 	my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID];
 4267: 	($line,$err,$errmsg)=
 4268: 	    &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
 4269: 				     'ID',{'newid'=>$newid,
 4270: 				    'username'=>$env{'form.scantron_username'},
 4271: 				    'domain'=>$env{'form.scantron_domain'}});
 4272:     } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)CODE$/) {
 4273: 	my $resolution=$env{'form.scantron_CODE_resolution'};
 4274: 	my $newCODE;
 4275: 	my %args;
 4276: 	if      ($resolution eq 'use_unfound') {
 4277: 	    $newCODE='use_unfound';
 4278: 	} elsif ($resolution eq 'use_found') {
 4279: 	    $newCODE=$env{'form.scantron_CODE_selectedvalue'};
 4280: 	} elsif ($resolution eq 'use_typed') {
 4281: 	    $newCODE=$env{'form.scantron_CODE_newvalue'};
 4282: 	} elsif ($resolution =~ /^use_closest_(\d+)/) {
 4283: 	    $newCODE=$env{"form.scantron_CODE_closest_$1"};
 4284: 	}
 4285: 	if ($env{'form.scantron_corrections'} eq 'duplicateCODE') {
 4286: 	    $args{'CODE_ignore_dup'}=1;
 4287: 	}
 4288: 	$args{'CODE'}=$newCODE;
 4289: 	($line,$err,$errmsg)=
 4290: 	    &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
 4291: 				     'CODE',\%args);
 4292:     } elsif ($env{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) {
 4293: 	foreach my $question (split(',',$env{'form.scantron_questions'})) {
 4294: 	    ($line,$err,$errmsg)=
 4295: 		&scantron_fixup_scanline(\%scantron_config,$scan_data,$line,
 4296: 					 $which,'answer',
 4297: 					 { 'question'=>$question,
 4298: 		       'response'=>$env{"form.scantron_correct_Q_$question"}});
 4299: 	    if ($err) { last; }
 4300: 	}
 4301:     }
 4302:     if ($err) {
 4303: 	$r->print("Unable to accept last correction, an error occurred :$errmsg:");
 4304:     } else {
 4305: 	&scantron_put_line($scanlines,$scan_data,$which,$line,$skip);
 4306: 	&scantron_putfile($scanlines,$scan_data);
 4307:     }
 4308: }
 4309: 
 4310: sub reset_skipping_status {
 4311:     my ($scanlines,$scan_data)=&scantron_getfile();
 4312:     &scan_data($scan_data,'remember_skipping',undef,1);
 4313:     &scantron_putfile(undef,$scan_data);
 4314: }
 4315: 
 4316: sub allow_skipping {
 4317:     my ($scan_data,$i)=@_;
 4318:     my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
 4319:     delete($remembered{$i});
 4320:     &scan_data($scan_data,'remember_skipping',join(':',%remembered));
 4321: }
 4322: 
 4323: sub should_be_skipped {
 4324:     my ($scan_data,$i)=@_;
 4325:     if ($env{'form.scantron_options_redo'} !~ /^redo_/) {
 4326: 	# not redoing old skips
 4327: 	return 0;
 4328:     }
 4329:     my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
 4330:     if (exists($remembered{$i})) { return 0; }
 4331:     return 1;
 4332: }
 4333: 
 4334: sub remember_current_skipped {
 4335:     my ($scanlines,$scan_data)=&scantron_getfile();
 4336:     my %to_remember;
 4337:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 4338: 	if ($scanlines->{'skipped'}[$i]) {
 4339: 	    $to_remember{$i}=1;
 4340: 	}
 4341:     }
 4342:     &Apache::lonnet::logthis('remembering '.join(':',%to_remember));
 4343:     &scan_data($scan_data,'remember_skipping',join(':',%to_remember));
 4344:     &scantron_putfile(undef,$scan_data);
 4345: }
 4346: 
 4347: sub check_for_error {
 4348:     my ($r,$result)=@_;
 4349:     if ($result ne 'ok' && $result ne 'not_found' ) {
 4350: 	$r->print("An error occured ($result) when trying to Remove the existing corrections.");
 4351:     }
 4352: }
 4353: 
 4354: sub scantron_warning_screen {
 4355:     my ($button_text)=@_;
 4356:     my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
 4357:     return (<<STUFF);
 4358: <p>
 4359: <font color="red">Please double check the information
 4360:                  below before clicking on '$button_text'</font>
 4361: </p>
 4362: <table>
 4363: <tr><td><b>Sequence To be Graded:</b></td><td>$title</td></tr>
 4364: <tr><td><b>Data File that will be used:</b></td><td><tt>$env{'form.scantron_selectfile'}</tt></td></tr>
 4365: </table>
 4366: </font>
 4367: <br />
 4368: <p> If this information is correct, please click on '$button_text'.</p>
 4369: <p> If something is incorrect, please click the 'Grading Menu' button to start over.</p>
 4370: 
 4371: <br />
 4372: STUFF
 4373: }
 4374: 
 4375: sub scantron_do_warning {
 4376:     my ($r)=@_;
 4377:     my ($symb,$url)=&get_symb_and_url($r);
 4378:     if (!$symb) {return '';}
 4379:     my $default_form_data=&defaultFormData($symb,$url);
 4380:     $r->print(&scantron_form_start().$default_form_data);
 4381:     if ( $env{'form.selectpage'} eq '' ||
 4382: 	 $env{'form.scantron_selectfile'} eq '' ||
 4383: 	 $env{'form.scantron_format'} eq '' ) {
 4384: 	$r->print("<p>You have forgetten to specify some information. Please go Back and try again.</p>");
 4385: 	if ( $env{'form.selectpage'} eq '') {
 4386: 	    $r->print('<p><font color="red">You have not selected a Sequence to grade</font></p>');
 4387: 	} 
 4388: 	if ( $env{'form.scantron_selectfile'} eq '') {
 4389: 	    $r->print('<p><font color="red">You have not selected a file that contains the student\'s response data.</font></p>');
 4390: 	} 
 4391: 	if ( $env{'form.scantron_format'} eq '') {
 4392: 	    $r->print('<p><font color="red">You have not selected a the format of the student\'s response data.</font></p>');
 4393: 	} 
 4394:     } else {
 4395: 	my $warning=&scantron_warning_screen('Grading: Validate Records');
 4396: 	$r->print(<<STUFF);
 4397: $warning
 4398: <input type="submit" name="submit" value="Grading: Validate Records" />
 4399: <input type="hidden" name="command" value="scantron_validate" />
 4400: STUFF
 4401:     }
 4402:     $r->print("</form><br />".&show_grading_menu_form($symb,$url)."</body></html>");
 4403:     return '';
 4404: }
 4405: 
 4406: sub scantron_form_start {
 4407:     my ($max_bubble)=@_;
 4408:     my $result= <<SCANTRONFORM;
 4409: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
 4410:   <input type="hidden" name="selectpage" value="$env{'form.selectpage'}" />
 4411:   <input type="hidden" name="scantron_format" value="$env{'form.scantron_format'}" />
 4412:   <input type="hidden" name="scantron_selectfile" value="$env{'form.scantron_selectfile'}" />
 4413:   <input type="hidden" name="scantron_maxbubble" value="$max_bubble" />
 4414:   <input type="hidden" name="scantron_CODElist" value="$env{'form.scantron_CODElist'}" />
 4415:   <input type="hidden" name="scantron_CODEunique" value="$env{'form.scantron_CODEunique'}" />
 4416:   <input type="hidden" name="scantron_options_redo" value="$env{'form.scantron_options_redo'}" />
 4417:   <input type="hidden" name="scantron_options_ignore" value="$env{'form.scantron_options_ignore'}" />
 4418: SCANTRONFORM
 4419:     return $result;
 4420: }
 4421: 
 4422: sub scantron_validate_file {
 4423:     my ($r) = @_;
 4424:     my ($symb,$url)=&get_symb_and_url($r);
 4425:     if (!$symb) {return '';}
 4426:     my $default_form_data=&defaultFormData($symb,$url);
 4427:     
 4428:     # do the detection of only doing skipped records first befroe we delete
 4429:     # them  when doing the corrections reset
 4430:     if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') {
 4431: 	&reset_skipping_status();
 4432:     }
 4433:     if ($env{'form.scantron_options_redo'} eq 'redo_skipped') {
 4434: 	&remember_current_skipped();
 4435: 	&scantron_remove_file('skipped');
 4436: 	$env{'form.scantron_options_redo'}='redo_skipped_ready';
 4437:     }
 4438: 
 4439:     if ($env{'form.scantron_options_ignore'} eq 'ignore_corrections') {
 4440: 	&check_for_error($r,&scantron_remove_file('corrected'));
 4441: 	&check_for_error($r,&scantron_remove_file('skipped'));
 4442: 	&check_for_error($r,&scantron_remove_scan_data());
 4443: 	$env{'form.scantron_options_ignore'}='done';
 4444:     }
 4445: 
 4446:     if ($env{'form.scantron_corrections'}) {
 4447: 	&scantron_process_corrections($r);
 4448:     }
 4449:     $r->print("<p>Gathering neccessary info.</p>");$r->rflush();
 4450:     #get the student pick code ready
 4451:     $r->print(&Apache::loncommon::studentbrowser_javascript());
 4452:     my $max_bubble=&scantron_get_maxbubble($r);
 4453:     my $result=&scantron_form_start($max_bubble).$default_form_data;
 4454:     $r->print($result);
 4455:     
 4456:     my @validate_phases=( 'ID',
 4457: 			  'CODE',
 4458: 			  'doublebubble',
 4459: 			  'missingbubbles');
 4460:     if (!$env{'form.validatepass'}) {
 4461: 	$env{'form.validatepass'} = 0;
 4462:     }
 4463:     my $currentphase=$env{'form.validatepass'};
 4464: 
 4465:     my $stop=0;
 4466:     while (!$stop && $currentphase < scalar(@validate_phases)) {
 4467: 	$r->print("<p> Validating ".$validate_phases[$currentphase]."</p>");
 4468: 	$r->rflush();
 4469: 	my $which="scantron_validate_".$validate_phases[$currentphase];
 4470: 	{
 4471: 	    no strict 'refs';
 4472: 	    ($stop,$currentphase)=&$which($r,$currentphase);
 4473: 	}
 4474:     }
 4475:     if (!$stop) {
 4476: 	my $warning=&scantron_warning_screen('Start Grading');
 4477: 	$r->print(<<STUFF);
 4478: Validation process complete.<br />
 4479: $warning
 4480: <input type="submit" name="submit" value="Start Grading" />
 4481: <input type="hidden" name="command" value="scantron_process" />
 4482: STUFF
 4483: 
 4484:     } else {
 4485: 	$r->print('<input type="hidden" name="command" value="scantron_validate" />');
 4486: 	$r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />");
 4487:     }
 4488:     if ($stop) {
 4489: 	$r->print('<input type="submit" name="submit" value="Continue ->" />');
 4490: 	$r->print(' using corrected info <br />');
 4491: 	$r->print("<input type='submit' value='Skip' name='scantron_skip_record' />");
 4492: 	$r->print(" this scanline saving it for later.");
 4493:     }
 4494:     $r->print(" </form><br />".&show_grading_menu_form($symb,$url).
 4495: 	      "</body></html>");
 4496:     return '';
 4497: }
 4498: 
 4499: sub scantron_remove_file {
 4500:     my ($which)=@_;
 4501:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 4502:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 4503:     my $file='scantron_';
 4504:     if ($which eq 'corrected' || $which eq 'skipped') {
 4505: 	$file.=$which.'_';
 4506:     } else {
 4507: 	return 'refused';
 4508:     }
 4509:     $file.=$env{'form.scantron_selectfile'};
 4510:     return &Apache::lonnet::removeuserfile($cname,$cdom,$file);
 4511: }
 4512: 
 4513: sub scantron_remove_scan_data {
 4514:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 4515:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 4516:     my @keys=&Apache::lonnet::getkeys('nohist_scantrondata',$cdom,$cname);
 4517:     my @todelete;
 4518:     my $filename=$env{'form.scantron_selectfile'};
 4519:     foreach my $key (@keys) {
 4520: 	if ($key=~/^\Q$filename\E_/) {
 4521: 	    if ($env{'form.scantron_options_redo'} eq 'redo_skipped_ready' &&
 4522: 		$key=~/remember_skipping/) {
 4523: 		next;
 4524: 	    }
 4525: 	    push(@todelete,$key);
 4526: 	}
 4527:     }
 4528:     my $result;
 4529:     if (@todelete) {
 4530: 	$result=&Apache::lonnet::del('nohist_scantrondata',\@todelete,$cdom,$cname);
 4531:     }
 4532:     return $result;
 4533: }
 4534: 
 4535: sub scantron_getfile {
 4536:     #FIXME really would prefer a scantron directory
 4537:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 4538:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 4539:     my $lines;
 4540:     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
 4541: 		       'scantron_orig_'.$env{'form.scantron_selectfile'});
 4542:     my %scanlines;
 4543:     $scanlines{'orig'}=[(split("\n",$lines,-1))];
 4544:     my $temp=$scanlines{'orig'};
 4545:     $scanlines{'count'}=$#$temp;
 4546: 
 4547:     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
 4548: 		       'scantron_corrected_'.$env{'form.scantron_selectfile'});
 4549:     if ($lines eq '-1') {
 4550: 	$scanlines{'corrected'}=[];
 4551:     } else {
 4552: 	$scanlines{'corrected'}=[(split("\n",$lines,-1))];
 4553:     }
 4554:     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
 4555: 		       'scantron_skipped_'.$env{'form.scantron_selectfile'});
 4556:     if ($lines eq '-1') {
 4557: 	$scanlines{'skipped'}=[];
 4558:     } else {
 4559: 	$scanlines{'skipped'}=[(split("\n",$lines,-1))];
 4560:     }
 4561:     my @tmp=&Apache::lonnet::dump('nohist_scantrondata',$cdom,$cname);
 4562:     if ($tmp[0] =~ /^(error:|no_such_host)/) { @tmp=(); }
 4563:     my %scan_data = @tmp;
 4564:     return (\%scanlines,\%scan_data);
 4565: }
 4566: 
 4567: sub lonnet_putfile {
 4568:     my ($contents,$filename)=@_;
 4569:     my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};
 4570:     my $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 4571:     my $docuhome=$env{'course.'.$env{'request.course.id'}.'.home'};
 4572:     $env{'form.sillywaytopassafilearound'}=$contents;
 4573:     &Apache::lonnet::finishuserfileupload($docuname,$docudom,$docuhome,'sillywaytopassafilearound',$filename);
 4574: 
 4575: }
 4576: 
 4577: sub scantron_putfile {
 4578:     my ($scanlines,$scan_data) = @_;
 4579:     #FIXME really would prefer a scantron directory
 4580:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 4581:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 4582:     if ($scanlines) {
 4583: 	my $prefix='scantron_';
 4584: # no need to update orig, shouldn't change
 4585: #   &lonnet_putfile(join("\n",@{$scanlines->{'orig'}}),$prefix.'orig_'.
 4586: #		    $env{'form.scantron_selectfile'});
 4587: 	&lonnet_putfile(join("\n",@{$scanlines->{'corrected'}}),
 4588: 			$prefix.'corrected_'.
 4589: 			$env{'form.scantron_selectfile'});
 4590: 	&lonnet_putfile(join("\n",@{$scanlines->{'skipped'}}),
 4591: 			$prefix.'skipped_'.
 4592: 			$env{'form.scantron_selectfile'});
 4593:     }
 4594:     &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname);
 4595: }
 4596: 
 4597: sub scantron_get_line {
 4598:     my ($scanlines,$scan_data,$i)=@_;
 4599:     if (&should_be_skipped($scan_data,$i)) { return undef; }
 4600:     if ($scanlines->{'skipped'}[$i]) { return undef; }
 4601:     if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$i];}
 4602:     return $scanlines->{'orig'}[$i]; 
 4603: }
 4604: 
 4605: sub get_todo_count {
 4606:     my ($scanlines,$scan_data)=@_;
 4607:     my $count=0;
 4608:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 4609: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 4610: 	if ($line=~/^[\s\cz]*$/) { next; }
 4611: 	$count++;
 4612:     }
 4613:     return $count;
 4614: }
 4615: 
 4616: sub scantron_put_line {
 4617:     my ($scanlines,$scan_data,$i,$newline,$skip)=@_;
 4618:     if ($skip) {
 4619: 	$scanlines->{'skipped'}[$i]=$newline;
 4620: 	&allow_skipping($scan_data,$i);
 4621: 	return;
 4622:     }
 4623:     $scanlines->{'corrected'}[$i]=$newline;
 4624: }
 4625: 
 4626: sub scantron_validate_ID {
 4627:     my ($r,$currentphase) = @_;
 4628:     
 4629:     #get student info
 4630:     my $classlist=&Apache::loncoursedata::get_classlist();
 4631:     my %idmap=&username_to_idmap($classlist);
 4632: 
 4633:     #get scantron line setup
 4634:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
 4635:     my ($scanlines,$scan_data)=&scantron_getfile();
 4636: 
 4637:     my %found=('ids'=>{},'usernames'=>{});
 4638:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 4639: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 4640: 	if ($line=~/^[\s\cz]*$/) { next; }
 4641: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
 4642: 						 $scan_data);
 4643: 	my $id=$$scan_record{'scantron.ID'};
 4644: 	my $found;
 4645: 	foreach my $checkid (keys(%idmap)) {
 4646: 	    if (lc($checkid) eq lc($id)) { $found=$checkid;last; }
 4647: 	}
 4648: 	if ($found) {
 4649: 	    my $username=$idmap{$found};
 4650: 	    if ($found{'ids'}{$found}) {
 4651: 		&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
 4652: 					 $line,'duplicateID',$found);
 4653: 		return(1,$currentphase);
 4654: 	    } elsif ($found{'usernames'}{$username}) {
 4655: 		&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
 4656: 					 $line,'duplicateID',$username);
 4657: 		return(1,$currentphase);
 4658: 	    }
 4659: 	    #FIXME store away line we previously saw the ID on to use above
 4660: 	    $found{'ids'}{$found}++;
 4661: 	    $found{'usernames'}{$username}++;
 4662: 	} else {
 4663: 	    if ($id =~ /^\s*$/) {
 4664: 		my $username=&scan_data($scan_data,"$i.user");
 4665: 		if (defined($username) && $found{'usernames'}{$username}) {
 4666: 		    &scantron_get_correction($r,$i,$scan_record,
 4667: 					     \%scantron_config,
 4668: 					     $line,'duplicateID',$username);
 4669: 		    return(1,$currentphase);
 4670: 		} elsif (!defined($username)) {
 4671: 		    &scantron_get_correction($r,$i,$scan_record,
 4672: 					     \%scantron_config,
 4673: 					     $line,'incorrectID');
 4674: 		    return(1,$currentphase);
 4675: 		}
 4676: 		$found{'usernames'}{$username}++;
 4677: 	    } else {
 4678: 		&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
 4679: 					 $line,'incorrectID');
 4680: 		return(1,$currentphase);
 4681: 	    }
 4682: 	}
 4683:     }
 4684: 
 4685:     return (0,$currentphase+1);
 4686: }
 4687: 
 4688: sub scantron_get_correction {
 4689:     my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;
 4690: 
 4691: #FIXME in the case of a duplicated ID the previous line, probaly need
 4692: #to show both the current line and the previous one and allow skipping
 4693: #the previous one or the current one
 4694: 
 4695:     $r->print("<p><b>An error was detected ($error)</b>");
 4696:     if ( defined($$scan_record{'scantron.PaperID'}) ) {
 4697: 	$r->print(" for PaperID <tt>".
 4698: 		  $$scan_record{'scantron.PaperID'}."</tt> \n");
 4699:     } else {
 4700: 	$r->print(" in scanline $i <pre>".
 4701: 		  $line."</pre> \n");
 4702:     }
 4703:     my $message="<p>The ID on the form is  <tt>".
 4704: 	$$scan_record{'scantron.ID'}."</tt><br />\n".
 4705: 	"The name on the paper is ".
 4706: 	$$scan_record{'scantron.LastName'}.",".
 4707: 	$$scan_record{'scantron.FirstName'}."</p>";
 4708: 
 4709:     $r->print('<input type="hidden" name="scantron_corrections" value="'.$error.'" />'."\n");
 4710:     $r->print('<input type="hidden" name="scantron_line" value="'.$i.'" />'."\n");
 4711:     if ($error =~ /ID$/) {
 4712: 	if ($error eq 'incorrectID') {
 4713: 	    $r->print("The encoded ID is not in the classlist</p>\n");
 4714: 	} elsif ($error eq 'duplicateID') {
 4715: 	    $r->print("The encoded ID has also been used by a previous paper $arg</p>\n");
 4716: 	}
 4717: 	$r->print($message);
 4718: 	$r->print("<p>How should I handle this? <br /> \n");
 4719: 	$r->print("\n<ul><li> ");
 4720: 	#FIXME it would be nice if this sent back the user ID and
 4721: 	#could do partial userID matches
 4722: 	$r->print(&Apache::loncommon::selectstudent_link('scantronupload',
 4723: 				       'scantron_username','scantron_domain'));
 4724: 	$r->print(": <input type='text' name='scantron_username' value='' />");
 4725: 	$r->print("\n@".
 4726: 		 &Apache::loncommon::select_dom_form($env{'request.role.domain'},'scantron_domain'));
 4727: 
 4728: 	$r->print('</li>');
 4729:     } elsif ($error =~ /CODE$/) {
 4730: 	if ($error eq 'incorrectCODE') {
 4731: 	    $r->print("</p><p>The encoded CODE is not in the list of possible CODEs</p>\n");
 4732: 	} elsif ($error eq 'duplicateCODE') {
 4733: 	    $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");
 4734: 	}
 4735: 	$r->print("<p>The CODE on the form is  <tt>'".
 4736: 		  $$scan_record{'scantron.CODE'}."'</tt><br />\n");
 4737: 	$r->print($message);
 4738: 	$r->print("<p>How should I handle this? <br /> \n");
 4739: 	$r->print("\n<br /> ");
 4740: 	my $i=0;
 4741: 	if ($error eq 'incorrectCODE') {
 4742: 	    my ($max,$closest)=&scantron_get_closely_matching_CODEs($arg,$$scan_record{'scantron.CODE'});
 4743: 	    foreach my $testcode (@{$closest}) {
 4744: 		my $checked='';
 4745: 		if (!$i) { $checked=' checked="on" '; }
 4746: 		$r->print("<input type='radio' name='scantron_CODE_resolution' value='use_closest_$i' $checked /> Use the similar CODE <b><tt>".$testcode."</tt></b> instead.<input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />");
 4747: 		$r->print("\n<br />");
 4748: 		$i++;
 4749: 	    }
 4750: 	}
 4751: 	my $checked; if (!$i) { $checked=' checked="on" '; }
 4752: 	$r->print("<input type='radio' name='scantron_CODE_resolution' value='use_unfound' $checked /> Use the CODE <b><tt>".$$scan_record{'scantron.CODE'}."</tt></b> that is was on the paper, ignoring the error.");
 4753: 	$r->print("\n<br />");
 4754: 
 4755: 	$r->print(<<ENDSCRIPT);
 4756: <script type="text/javascript">
 4757: function change_radio(field) {
 4758:     var slct=document.scantronupload.scantron_CODE_resolution;
 4759:     var i;
 4760:     for (i=0;i<slct.length;i++) {
 4761:         if (slct[i].value==field) { slct[i].checked=true; }
 4762:     }
 4763: }
 4764: </script>
 4765: ENDSCRIPT
 4766: 	my $href="/adm/pickcode?".
 4767: 	   "form=".&Apache::lonnet::escape("scantronupload").
 4768: 	   "&scantron_format=".&Apache::lonnet::escape($env{'form.scantron_format'}).
 4769: 	   "&scantron_CODElist=".&Apache::lonnet::escape($env{'form.scantron_CODElist'}).
 4770: 	   "&curCODE=".&Apache::lonnet::escape($$scan_record{'scantron.CODE'}).
 4771: 	   "&scantron_selectfile=".&Apache::lonnet::escape($env{'form.scantron_selectfile'});
 4772: 	$r->print("<input type='radio' name='scantron_CODE_resolution' value='use_found' /> <a target='_blank' href='$href'>Select</a> a CODE from the list of all CODEs and use it. Selected CODE is <input readonly='true' type='text' size='8' name='scantron_CODE_selectedvalue' onfocus=\"javascript:change_radio('use_found')\" onchange=\"javascript:change_radio('use_found')\" />");
 4773: 	$r->print("\n<br />");
 4774: 	$r->print("<input type='radio' name='scantron_CODE_resolution' value='use_typed' /> Use <input type='text' size='8' name='scantron_CODE_newvalue' onfocus=\"javascript:change_radio('use_typed')\" onkeypress=\"javascript:change_radio('use_typed')\" /> as the CODE.");
 4775: 	$r->print("\n<br /><br />");
 4776:     } elsif ($error eq 'doublebubble') {
 4777: 	$r->print("<p>There have been multiple bubbles scanned for a some question(s)</p>\n");
 4778: 	$r->print('<input type="hidden" name="scantron_questions" value="'.
 4779: 		  join(',',@{$arg}).'" />');
 4780: 	$r->print($message);
 4781: 	$r->print("<p>Please indicate which bubble should be used for grading</p>");
 4782: 	foreach my $question (@{$arg}) {
 4783: 	    my $selected=$$scan_record{"scantron.$question.answer"};
 4784: 	    &scantron_bubble_selector($r,$scan_config,$question,split('',$selected));
 4785: 	}
 4786:     } elsif ($error eq 'missingbubble') {
 4787: 	$r->print("<p>There have been <b>no</b> bubbles scanned for some question(s)</p>\n");
 4788: 	$r->print($message);
 4789: 	$r->print("<p>Please indicate which bubble should be used for grading</p>");
 4790: 	$r->print("Some questions have no scanned bubbles\n");
 4791: 	$r->print('<input type="hidden" name="scantron_questions" value="'.
 4792: 		  join(',',@{$arg}).'" />');
 4793: 	foreach my $question (@{$arg}) {
 4794: 	    my $selected=$$scan_record{"scantron.$question.answer"};
 4795: 	    &scantron_bubble_selector($r,$scan_config,$question);
 4796: 	}
 4797:     } else {
 4798: 	$r->print("\n<ul>");
 4799:     }
 4800:     $r->print("\n</li></ul>");
 4801: 
 4802: }
 4803: 
 4804: sub scantron_bubble_selector {
 4805:     my ($r,$scan_config,$quest,@selected)=@_;
 4806:     my $max=$$scan_config{'Qlength'};
 4807:     my @alphabet=('A'..'Z');
 4808:     $r->print("<table border='1'><tr><td rowspan='2'>$quest</td>");
 4809:     for (my $i=0;$i<$max+1;$i++) {
 4810: 	$r->print('<td align="center">');
 4811: 	if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) }
 4812: 	else { $r->print('&nbsp;'); }
 4813: 	$r->print('</td>');
 4814:     }
 4815:     $r->print('<td></td></tr><tr>');
 4816:     for (my $i=0;$i<$max;$i++) {
 4817: 	$r->print('<td><input type="radio" name="scantron_correct_Q_'.$quest.
 4818: 		  '" value="'.$i.'" />'.$alphabet[$i]."</td>");
 4819:     }
 4820:     $r->print('<td><input type="radio" name="scantron_correct_Q_'.$quest.
 4821: 	      '" value="none" /> No bubble </td>');
 4822:     $r->print('</tr></table>');
 4823: }
 4824: 
 4825: sub num_matches {
 4826:     my ($orig,$code) = @_;
 4827:     my @code=split(//,$code);
 4828:     my @orig=split(//,$orig);
 4829:     my $same=0;
 4830:     for (my $i=0;$i<scalar(@code);$i++) {
 4831: 	if ($code[$i] eq $orig[$i]) { $same++; }
 4832:     }
 4833:     return $same;
 4834: }
 4835: 
 4836: sub scantron_get_closely_matching_CODEs {
 4837:     my ($allcodes,$CODE)=@_;
 4838:     my @CODEs;
 4839:     foreach my $testcode (sort(keys(%{$allcodes}))) {
 4840: 	push(@{$CODEs[&num_matches($CODE,$testcode)]},$testcode);
 4841:     }
 4842: 
 4843:     return ($#CODEs,$CODEs[-1]);
 4844: }
 4845: 
 4846: sub get_codes {
 4847:     my $old_name=$env{'form.scantron_CODElist'};
 4848:     my $cdom =$env{'course.'.$env{'request.course.id'}.'.domain'};
 4849:     my $cnum =$env{'course.'.$env{'request.course.id'}.'.num'};
 4850:     my %result=&Apache::lonnet::get('CODEs',[$old_name],$cdom,$cnum);
 4851:     my %allcodes=map {(&Apache::lonprintout::num_to_letters($_),1)} split(',',$result{$old_name});
 4852:     return %allcodes;
 4853: }
 4854: 
 4855: sub scantron_validate_CODE {
 4856:     my ($r,$currentphase) = @_;
 4857:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
 4858:     if ($scantron_config{'CODElocation'} &&
 4859: 	$scantron_config{'CODEstart'} &&
 4860: 	$scantron_config{'CODElength'}) {
 4861: 	if (!defined($env{'form.scantron_CODElist'})) {
 4862: 	    &FIXME_blow_up()
 4863: 	}
 4864:     } else {
 4865: 	return (0,$currentphase+1);
 4866:     }
 4867:     
 4868:     my %usedCODEs;
 4869: 
 4870:     my %allcodes=&get_codes();
 4871: 
 4872:     my ($scanlines,$scan_data)=&scantron_getfile();
 4873:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 4874: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 4875: 	if ($line=~/^[\s\cz]*$/) { next; }
 4876: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
 4877: 						 $scan_data);
 4878: 	my $CODE=$$scan_record{'scantron.CODE'};
 4879: 	my $error=0;
 4880: 	if (!&Apache::lonnet::validCODE($CODE)) {
 4881: 	    &scantron_get_correction($r,$i,$scan_record,
 4882: 				     \%scantron_config,
 4883: 				     $line,'incorrectCODE',\%allcodes);
 4884: 	    return(1,$currentphase);
 4885: 	}
 4886: 	if (%allcodes && !exists($allcodes{$CODE}) 
 4887: 	    && !$$scan_record{'scantron.useCODE'}) {
 4888: 	    &scantron_get_correction($r,$i,$scan_record,
 4889: 				     \%scantron_config,
 4890: 				     $line,'incorrectCODE',\%allcodes);
 4891: 	    return(1,$currentphase);
 4892: 	}
 4893: 	if (exists($usedCODEs{$CODE}) 
 4894: 	    && $env{'form.scantron_CODEunique'} eq 'yes'
 4895: 	    && !$$scan_record{'scantron.CODE_ignore_dup'}) {
 4896: 	    &scantron_get_correction($r,$i,$scan_record,
 4897: 				     \%scantron_config,
 4898: 				     $line,'duplicateCODE',$usedCODEs{$CODE});
 4899: 	    return(1,$currentphase);
 4900: 	}
 4901: 	push (@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});
 4902:     }
 4903:     return (0,$currentphase+1);
 4904: }
 4905: 
 4906: sub scantron_validate_doublebubble {
 4907:     my ($r,$currentphase) = @_;
 4908:     #get student info
 4909:     my $classlist=&Apache::loncoursedata::get_classlist();
 4910:     my %idmap=&username_to_idmap($classlist);
 4911: 
 4912:     #get scantron line setup
 4913:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
 4914:     my ($scanlines,$scan_data)=&scantron_getfile();
 4915:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 4916: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 4917: 	if ($line=~/^[\s\cz]*$/) { next; }
 4918: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
 4919: 						 $scan_data);
 4920: 	if (!defined($$scan_record{'scantron.doubleerror'})) { next; }
 4921: 	&scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line,
 4922: 				 'doublebubble',
 4923: 				 $$scan_record{'scantron.doubleerror'});
 4924:     	return (1,$currentphase);
 4925:     }
 4926:     return (0,$currentphase+1);
 4927: }
 4928: 
 4929: sub scantron_get_maxbubble {
 4930:     my ($r)=@_;
 4931:     if (defined($env{'form.scantron_maxbubble'}) &&
 4932: 	$env{'form.scantron_maxbubble'}) {
 4933: 	return $env{'form.scantron_maxbubble'};
 4934:     }
 4935:     my $navmap=Apache::lonnavmaps::navmap->new();
 4936:     my (undef,undef,$sequence)=
 4937: 	&Apache::lonnet::decode_symb($env{'form.selectpage'});
 4938:     my $map=$navmap->getResourceByUrl($sequence);
 4939:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
 4940:     &Apache::lonnet::delenv('form.counter');
 4941:     foreach my $resource (@resources) {
 4942: 	my $result=&Apache::lonnet::ssi($resource->src().'?symb='.&Apache::lonnet::escape($resource->symb()));
 4943:     }
 4944:     &Apache::lonnet::delenv('scantron\.');
 4945:     my $envfile=$env{'user.environment'};
 4946:     $envfile=~/\/([^\/]+)\.id$/;
 4947:     $envfile=$1;
 4948:     &Apache::lonnet::transfer_profile_to_env($r->dir_config('lonIDsDir'),
 4949: 					     $envfile);
 4950:     $env{'form.scantron_maxbubble'}=$env{'form.counter'}-1;
 4951:     return $env{'form.scantron_maxbubble'};
 4952: }
 4953: 
 4954: sub scantron_validate_missingbubbles {
 4955:     my ($r,$currentphase) = @_;
 4956:     #get student info
 4957:     my $classlist=&Apache::loncoursedata::get_classlist();
 4958:     my %idmap=&username_to_idmap($classlist);
 4959: 
 4960:     #get scantron line setup
 4961:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
 4962:     my ($scanlines,$scan_data)=&scantron_getfile();
 4963:     my $max_bubble=&scantron_get_maxbubble();
 4964:     if (!$max_bubble) { $max_bubble=2**31; }
 4965:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 4966: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 4967: 	if ($line=~/^[\s\cz]*$/) { next; }
 4968: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
 4969: 						 $scan_data);
 4970: 	if (!defined($$scan_record{'scantron.missingerror'})) { next; }
 4971: 	my @to_correct;
 4972: 	foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {
 4973: 	    if ($missing > $max_bubble) { next; }
 4974: 	    push(@to_correct,$missing);
 4975: 	}
 4976: 	if (@to_correct) {
 4977: 	    &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
 4978: 				     $line,'missingbubble',\@to_correct);
 4979: 	    return (1,$currentphase);
 4980: 	}
 4981: 
 4982:     }
 4983:     return (0,$currentphase+1);
 4984: }
 4985: 
 4986: sub scantron_process_students {
 4987:     my ($r) = @_;
 4988:     my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
 4989:     my ($symb,$url)=&get_symb_and_url($r);
 4990:     if (!$symb) {return '';}
 4991:     my $default_form_data=&defaultFormData($symb,$url);
 4992: 
 4993:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
 4994:     my ($scanlines,$scan_data)=&scantron_getfile();
 4995:     my $classlist=&Apache::loncoursedata::get_classlist();
 4996:     my %idmap=&username_to_idmap($classlist);
 4997:     my $navmap=Apache::lonnavmaps::navmap->new();
 4998:     my $map=$navmap->getResourceByUrl($sequence);
 4999:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
 5000: #    $r->print("geto ".scalar(@resources)."<br />");
 5001:     my $result= <<SCANTRONFORM;
 5002: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
 5003:   <input type="hidden" name="command" value="scantron_configphase" />
 5004:   $default_form_data
 5005: SCANTRONFORM
 5006:     $r->print($result);
 5007: 
 5008:     my @delayqueue;
 5009:     my %completedstudents;
 5010:     
 5011:     my $count=&get_todo_count($scanlines,$scan_data);
 5012:     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron Status',
 5013:  				    'Scantron Progress',$count,
 5014: 				    'inline',undef,'scantronupload');
 5015:     &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
 5016: 					  'Processing first student');
 5017:     my $start=&Time::HiRes::time();
 5018:     my $i=-1;
 5019:     my ($uname,$udom,$started);
 5020:     while ($i<$scanlines->{'count'}) {
 5021:  	($uname,$udom)=('','');
 5022:  	$i++;
 5023:  	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 5024:  	if ($line=~/^[\s\cz]*$/) { next; }
 5025: 	if ($started) {
 5026: 	    &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
 5027: 						     'last student');
 5028: 	}
 5029: 	$started=1;
 5030:  	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
 5031:  						 $scan_data);
 5032:  	unless ($uname=&scantron_find_student($scan_record,$scan_data,
 5033:  					      \%idmap,$i)) {
 5034:   	    &scantron_add_delay(\@delayqueue,$line,
 5035:  				'Unable to find a student that matches',1);
 5036:  	    next;
 5037:   	}
 5038:  	if (exists $completedstudents{$uname}) {
 5039:  	    &scantron_add_delay(\@delayqueue,$line,
 5040:  				'Student '.$uname.' has multiple sheets',2);
 5041:  	    next;
 5042:  	}
 5043:   	($uname,$udom)=split(/:/,$uname);
 5044:   	&Apache::lonnet::delenv('form.counter');
 5045:   	&Apache::lonnet::appenv(%$scan_record);
 5046: 	
 5047: 	my $i=0;
 5048: 	foreach my $resource (@resources) {
 5049: 	    $i++;
 5050: 	    my %form=('submitted'     =>'scantron',
 5051: 		      'grade_target'  =>'grade',
 5052: 		      'grade_username'=>$uname,
 5053: 		      'grade_domain'  =>$udom,
 5054: 		      'grade_courseid'=>$env{'request.course.id'},
 5055: 		      'grade_symb'    =>$resource->symb());
 5056: 	    if (exists($scan_record->{'scantron.CODE'}) &&
 5057: 		$scan_record->{'scantron.CODE'}) {
 5058: 		$form{'CODE'}=$scan_record->{'scantron.CODE'};
 5059: 	    } else {
 5060: 		$form{'CODE'}='';
 5061: 	    }
 5062: 	    my $result=&Apache::lonnet::ssi($resource->src(),%form);
 5063: 	    if ($result ne '') {
 5064: 		&Apache::lonnet::logthis("scantron grading error -> $result");
 5065: 		&Apache::lonnet::logthis("scantron grading error info name $uname domain $udom course $env{'request.course.id'} url ".$resource->src());
 5066: 	    }
 5067: 	    if (&Apache::loncommon::connection_aborted($r)) { last; }
 5068: 	}
 5069: 	$completedstudents{$uname}={'line'=>$line};
 5070: 	if (&Apache::loncommon::connection_aborted($r)) { last; }
 5071:     } continue {
 5072: 	&Apache::lonnet::delenv('form.counter');
 5073: 	&Apache::lonnet::delenv('scantron\.');
 5074:     }
 5075:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
 5076: #    my $lasttime = &Time::HiRes::time()-$start;
 5077: #    $r->print("<p>took $lasttime</p>");
 5078: 
 5079:     $r->print("</form>");
 5080:     $r->print(&show_grading_menu_form($symb,$url));
 5081:     return '';
 5082: }
 5083: 
 5084: sub scantron_upload_scantron_data {
 5085:     my ($r)=@_;
 5086:     $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}));
 5087:     my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
 5088: 							  'domainid',
 5089: 							  'coursename');
 5090:     my $domsel=&Apache::loncommon::select_dom_form($env{'request.role.domain'},
 5091: 						   'domainid');
 5092:     my $default_form_data=&defaultFormData(&get_symb_and_url($r,1));
 5093:     $r->print(<<UPLOAD);
 5094: <script type="text/javascript" language="javascript">
 5095:     function checkUpload(formname) {
 5096: 	if (formname.upfile.value == "") {
 5097: 	    alert("Please use the browse button to select a file from your local directory.");
 5098: 	    return false;
 5099: 	}
 5100: 	formname.submit();
 5101:     }
 5102: </script>
 5103: 
 5104: <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'>
 5105: $default_form_data
 5106: <table>
 5107: <tr><td>$select_link </td></tr>
 5108: <tr><td>Course ID:   </td><td><input name='courseid' type='text' />  </td></tr>
 5109: <tr><td>Course Name: </td><td><input name='coursename' type='text' /></td></tr>
 5110: <tr><td>Domain:      </td><td>$domsel                                </td></tr>
 5111: <tr><td>File to upload:</td><td><input type="file" name="upfile" size="50" /></td></tr>
 5112: </table>
 5113: <input name='command' value='scantronupload_save' type='hidden' />
 5114: <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" />
 5115: </form>
 5116: UPLOAD
 5117:     return '';
 5118: }
 5119: 
 5120: sub scantron_upload_scantron_data_save {
 5121:     my($r)=@_;
 5122:     my ($symb,$url)=&get_symb_and_url($r,1);
 5123:     my $doanotherupload=
 5124: 	'<br /><form action="/adm/grades" method="post">'."\n".
 5125: 	'<input type="hidden" name="command" value="scantronupload" />'."\n".
 5126: 	'<input type="submit" name="submit" value="Do Another Upload" />'."\n".
 5127: 	'</form>'."\n";
 5128:     if (!&Apache::lonnet::allowed('usc',$env{'form.domainid'}) &&
 5129: 	!&Apache::lonnet::allowed('usc',
 5130: 			    $env{'form.domainid'}.'_'.$env{'form.courseid'})) {
 5131: 	$r->print("You are not allowed to upload Scantron data to the requested course.<br />");
 5132: 	if ($symb) {
 5133: 	    $r->print(&show_grading_menu_form($symb,$url));
 5134: 	} else {
 5135: 	    $r->print($doanotherupload);
 5136: 	}
 5137: 	return '';
 5138:     }
 5139:     my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
 5140:     $r->print("Doing upload to ".$coursedata{'description'}." <br />");
 5141:     my $home=&Apache::lonnet::homeserver($env{'form.courseid'},
 5142: 					 $env{'form.domainid'});
 5143:     my $fname=$env{'form.upfile.filename'};
 5144:     #FIXME
 5145:     #copied from lonnet::userfileupload()
 5146:     #make that function able to target a specified course
 5147:     # Replace Windows backslashes by forward slashes
 5148:     $fname=~s/\\/\//g;
 5149:     # Get rid of everything but the actual filename
 5150:     $fname=~s/^.*\/([^\/]+)$/$1/;
 5151:     # Replace spaces by underscores
 5152:     $fname=~s/\s+/\_/g;
 5153:     # Replace all other weird characters by nothing
 5154:     $fname=~s/[^\w\.\-]//g;
 5155:     # See if there is anything left
 5156:     unless ($fname) { return 'error: no uploaded file'; }
 5157:     my $uploadedfile=$fname;
 5158:     $fname='scantron_orig_'.$fname;
 5159:     if (length($env{'form.upfile'}) < 2) {
 5160: 	$r->print("<font color='red'>Error:</font> The file you attempted to upload, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>, contained no information. Please check that you entered the correct filename.");
 5161:     } else {
 5162: 	my $result=&Apache::lonnet::finishuserfileupload($env{'form.courseid'},$env{'form.domainid'},$home,'upfile',$fname);
 5163: 	if ($result =~ m|^/uploaded/|) {
 5164: 	    $r->print("<font color='green'>Success:</font> Successfully uploaded ".(length($env{'form.upfile'})-1)." bytes of data into location <tt>".$result."</tt>");
 5165: 	} else {
 5166: 	    $r->print("<font color='red'>Error:</font> An error (".$result.") occurred when attempting to upload the file, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>");
 5167: 	}
 5168:     }
 5169:     if ($symb) {
 5170: 	$r->print(&scantron_selectphase($r,$uploadedfile));
 5171:     } else {
 5172: 	$r->print($doanotherupload);
 5173:     }
 5174:     return '';
 5175: }
 5176: 
 5177: sub valid_file {
 5178:     my ($requested_file)=@_;
 5179:     foreach my $filename (sort(&scantron_filenames())) {
 5180: 	&Apache::lonnet::logthis("$requested_file  $filename");
 5181: 	if ($requested_file eq $filename) { return 1; }
 5182:     }
 5183:     return 0;
 5184: }
 5185: 
 5186: sub scantron_download_scantron_data {
 5187:     my ($r)=@_;
 5188:     my $default_form_data=&defaultFormData(&get_symb_and_url($r,1));
 5189:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 5190:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 5191:     my $file=$env{'form.scantron_selectfile'};
 5192:     if (! &valid_file($file)) {
 5193: 	$r->print(<<ERROR);
 5194: 	<p>
 5195: 	    The requested file name was invalid.
 5196:         </p>
 5197: ERROR
 5198: 	$r->print(&show_grading_menu_form(&get_symb_and_url($r,1)));
 5199: 	return;
 5200:     }
 5201:     my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file;
 5202:     my $corrected='/uploaded/'.$cdom.'/'.$cname.'/scantron_corrected_'.$file;
 5203:     my $skipped='/uploaded/'.$cdom.'/'.$cname.'/scantron_skipped_'.$file;
 5204:     &Apache::lonnet::allowuploaded('/adm/grades',$orig);
 5205:     &Apache::lonnet::allowuploaded('/adm/grades',$corrected);
 5206:     &Apache::lonnet::allowuploaded('/adm/grades',$skipped);
 5207:     $r->print(<<DOWNLOAD);
 5208:     <p>
 5209: 	<a href="$orig">Original</a> file as uploaded by the scantron office.
 5210:     </p>
 5211:     <p>
 5212: 	<a href="$corrected">Corrections</a>, a file of corrected records that were used in grading.
 5213:     </p>
 5214:     <p>
 5215: 	<a href="$skipped">Skipped</a>, a file of records that were skipped.
 5216:     </p>
 5217: DOWNLOAD
 5218:     $r->print(&show_grading_menu_form(&get_symb_and_url($r,1)));
 5219:     return '';
 5220: }
 5221: 
 5222: #-------- end of section for handling grading scantron forms -------
 5223: #
 5224: #-------------------------------------------------------------------
 5225: 
 5226: #-------------------------- Menu interface -------------------------
 5227: #
 5228: #--- Show a Grading Menu button - Calls the next routine ---
 5229: sub show_grading_menu_form {
 5230:     my ($symb,$url)=@_;
 5231:     my $result.='<br /><form action="/adm/grades" method="post">'."\n".
 5232: 	'<input type="hidden" name="symb" value="'.$symb.'" />'."\n".
 5233: 	'<input type="hidden" name="url" value="'.$url.'" />'."\n".
 5234: 	'<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
 5235: 	'<input type="hidden" name="command" value="gradingmenu" />'."\n".
 5236: 	'<input type="submit" name="submit" value="Grading Menu" />'."\n".
 5237: 	'</form>'."\n";
 5238:     return $result;
 5239: }
 5240: 
 5241: # -- Retrieve choices for grading form
 5242: sub savedState {
 5243:     my %savedState = ();
 5244:     if ($env{'form.saveState'}) {
 5245: 	foreach (split(/:/,$env{'form.saveState'})) {
 5246: 	    my ($key,$value) = split(/=/,$_,2);
 5247: 	    $savedState{$key} = $value;
 5248: 	}
 5249:     }
 5250:     return \%savedState;
 5251: }
 5252: 
 5253: #--- Displays the main menu page -------
 5254: sub gradingmenu {
 5255:     my ($request) = @_;
 5256:     my ($symb,$url)=&get_symb_and_url($request);
 5257:     if (!$symb) {return '';}
 5258:     my $probTitle = &Apache::lonnet::gettitle($symb);
 5259: 
 5260:     $request->print(<<GRADINGMENUJS);
 5261: <script type="text/javascript" language="javascript">
 5262:     function checkChoice(formname,val,cmdx) {
 5263: 	if (val <= 2) {
 5264: 	    var cmd = radioSelection(formname.radioChoice);
 5265: 	    var cmdsave = cmd;
 5266: 	} else {
 5267: 	    cmd = cmdx;
 5268: 	    cmdsave = 'submission';
 5269: 	}
 5270: 	formname.command.value = cmd;
 5271: 	formname.saveState.value = "saveCmd="+cmdsave+":saveSec="+pullDownSelection(formname.section)+
 5272: 	    ":saveSub="+pullDownSelection(formname.submitonly)+":saveStatus="+pullDownSelection(formname.Status);
 5273: 	if (val < 5) formname.submit();
 5274: 	if (val == 5) {
 5275: 	    if (!checkReceiptNo(formname,'notOK')) { return false;}
 5276: 	    formname.submit();
 5277: 	}
 5278: 	if (val < 7) formname.submit();
 5279:     }
 5280: 
 5281:     function checkReceiptNo(formname,nospace) {
 5282: 	var receiptNo = formname.receipt.value;
 5283: 	var checkOpt = false;
 5284: 	if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
 5285: 	if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
 5286: 	if (checkOpt) {
 5287: 	    alert("Please enter a receipt number given by a student in the receipt box.");
 5288: 	    formname.receipt.value = "";
 5289: 	    formname.receipt.focus();
 5290: 	    return false;
 5291: 	}
 5292: 	return true;
 5293:     }
 5294: </script>
 5295: GRADINGMENUJS
 5296:     &commonJSfunctions($request);
 5297:     my $result='<h3>&nbsp;<font color="#339933">Manual Grading/View Submission</font></h3>';
 5298:     my ($table,undef,$hdgrade) = &showResourceInfo($url,$probTitle);
 5299:     $result.=$table;
 5300:     my (undef,$sections) = &getclasslist('all','0');
 5301:     my $savedState = &savedState();
 5302:     my $saveCmd = ($$savedState{'saveCmd'} eq '' ? 'submission' : $$savedState{'saveCmd'});
 5303:     my $saveSec = ($$savedState{'saveSec'} eq '' ? 'all' : $$savedState{'saveSec'});
 5304:     my $saveSub = ($$savedState{'saveSub'} eq '' ? 'all' : $$savedState{'saveSub'});
 5305:     my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'});
 5306: 
 5307:     $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
 5308: 	'<input type="hidden" name="symb"        value="'.$symb.'" />'."\n".
 5309: 	'<input type="hidden" name="url"         value="'.$url.'" />'."\n".
 5310: 	'<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
 5311: 	'<input type="hidden" name="probTitle"   value="'.$probTitle.'" />'."\n".
 5312: 	'<input type="hidden" name="command"     value="" />'."\n".
 5313: 	'<input type="hidden" name="saveState"   value="" />'."\n".
 5314: 	'<input type="hidden" name="gradingMenu" value="1" />'."\n".
 5315: 	'<input type="hidden" name="showgrading" value="yes" />'."\n";
 5316: 
 5317:     $result.='<table width="100%" border=0><tr><td bgcolor=#777777>'."\n".
 5318: 	'<table width=100% border=0><tr bgcolor="#e6ffff"><td colspan="2">'."\n".
 5319: 	'&nbsp;<b>Select a Grading/Viewing Option</b></td></tr>'."\n".
 5320: 	'<tr bgcolor="#ffffe6" valign="top"><td>'."\n";
 5321: 
 5322:     $result.='<table width="100%" border=0>';
 5323:     $result.='<tr bgcolor="#ffffe6" valign="top"><td>'."\n".
 5324: 	'&nbsp;'.&mt('Select Section').': <select name="section">'."\n";
 5325:     if (ref($sections)) {
 5326: 	foreach (sort (@$sections)) {
 5327: 	    $result.='<option value="'.$_.'" '.
 5328: 		($saveSec eq $_ ? 'selected="on"':'').'>'.$_.'</option>'."\n";
 5329: 	}
 5330:     }
 5331:     $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="on"' : ''). '>all</option></select> &nbsp; ';
 5332: 
 5333:     $result.=&mt('Student Status').':</b>'.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,undef);
 5334: 
 5335:     $result.='</td></tr>';
 5336: 
 5337:     $result.='<tr bgcolor="#ffffe6"valign="top"><td>'.
 5338: 	'<input type="radio" name="radioChoice" value="submission" '.
 5339: 	($saveCmd eq 'submission' ? 'checked' : '').'> '.'<b>'.&mt('Current Resource').':</b> '.&mt('For one or more students').
 5340: 	' <select name="submitonly">'.
 5341: 	'<option value="yes" '.
 5342: 	($saveSub eq 'yes' ? 'selected="on"' : '').'>with submissions</option>'.
 5343: 	'<option value="graded" '.
 5344: 	($saveSub eq 'graded' ? 'selected="on"' : '').'>with ungraded submissions</option>'.
 5345: 	'<option value="incorrect" '.
 5346: 	($saveSub eq 'incorrect' ? 'selected="on"' : '').'>with incorrect submissions</option>'.
 5347: 	'<option value="all" '.
 5348: 	($saveSub eq 'all' ? 'selected="on"' : '').'>with any status</option></select></td></tr>'."\n";
 5349: 
 5350:     $result.='<tr bgcolor="#ffffe6"valign="top"><td>'.
 5351: 	'<input type="radio" name="radioChoice" value="viewgrades" '.
 5352: 	($saveCmd eq 'viewgrades' ? 'checked' : '').'> '.
 5353: 	'<b>Current Resource:</b> For all students in selected section or course</td></tr>'."\n";
 5354: 
 5355:     $result.='<tr bgcolor="#ffffe6" valign="top"><td>'.
 5356: 	'<input type="radio" name="radioChoice" value="pickStudentPage" '.
 5357: 	($saveCmd eq 'pickStudentPage' ? 'checked' : '').'> '.
 5358: 	'The <b>complete</b> set/page/sequence: For one student</td></tr>'."\n";
 5359: 
 5360:     $result.='<tr bgcolor="#ffffe6"><td><br />'.
 5361: 	'<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'.
 5362: 	'</td></tr></table>'."\n";
 5363: 
 5364:     $result.='</td><td valign="top">';
 5365: 
 5366:     $result.='<table width="100%" border=0>';
 5367:     $result.='<tr bgcolor="#ffffe6"><td>'.
 5368: 	'<input type="button" onClick="javascript:checkChoice(this.form,\'3\',\'csvform\');" value="'.&mt('Upload').'" />'.
 5369: 	' '.&mt('scores from file').' </td></tr>'."\n";
 5370: 
 5371:     $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
 5372: 	'<input type="button" onClick="javascript:checkChoice(this.form,\'4\',\'scantron_selectphase\');'.
 5373: 	'" value="'.&mt('Grade').'" /> scantron forms</td></tr>'."\n";
 5374: 
 5375:     if ((&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) && ($symb)) {
 5376: 	$result.='<tr bgcolor="#ffffe6"valign="top"><td>'.
 5377: 	    '<input type="button" onClick="javascript:checkChoice(this.form,\'5\',\'verify\');" value="'.&mt('Verify').'" />'.
 5378: 	    ' '.&mt('receipt').': '.
 5379: 	    &Apache::lonnet::recprefix($env{'request.course.id'}).
 5380: 	    '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')">'.
 5381: 	    '</td></tr>'."\n";
 5382:     } 
 5383:     $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
 5384: 	'<input type="button" onClick="javascript:this.form.action=\'/adm/helper/resettimes.helper\';this.form.submit();'.
 5385: 	'" value="'.&mt('Manage').'" /> access times.</td></tr>'."\n";
 5386: 
 5387:     $result.='</form></td></tr></table>'."\n".
 5388: 	'</td></tr></table>'."\n".
 5389: 	'</td></tr></table>'."\n";
 5390:     return $result;
 5391: }
 5392: 
 5393: sub handler {
 5394:     my $request=$_[0];
 5395: 
 5396:     undef(%perm);
 5397:     if ($env{'browser.mathml'}) {
 5398: 	&Apache::loncommon::content_type($request,'text/xml');
 5399:     } else {
 5400: 	&Apache::loncommon::content_type($request,'text/html');
 5401:     }
 5402:     $request->send_http_header;
 5403:     return '' if $request->header_only;
 5404:     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});
 5405:     my $url=$env{'form.url'};
 5406:     my $symb=$env{'form.symb'};
 5407:     my @commands=&Apache::loncommon::get_env_multiple('form.command');
 5408:     my $command=$commands[0];
 5409:     if ($#commands > 0) {
 5410: 	&Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
 5411:     }
 5412:     if (!$url) {
 5413: 	my ($temp1,$temp2);
 5414: 	($temp1,$temp2,$env{'form.url'})=&Apache::lonnet::decode_symb($symb);
 5415: 	$url = $env{'form.url'};
 5416:     }
 5417:     &send_header($request);
 5418:     if ($url eq '' && $symb eq '' && $command eq '') {
 5419: 	if ($env{'user.adv'}) {
 5420: 	    if (($env{'form.codeone'}) && ($env{'form.codetwo'}) &&
 5421: 		($env{'form.codethree'})) {
 5422: 		my $token=$env{'form.codeone'}.'*'.$env{'form.codetwo'}.'*'.
 5423: 		    $env{'form.codethree'};
 5424: 		my ($tsymb,$tuname,$tudom,$tcrsid)=
 5425: 		    &Apache::lonnet::checkin($token);
 5426: 		if ($tsymb) {
 5427: 		    my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb);
 5428: 		    if (&Apache::lonnet::allowed('mgr',$tcrsid)) {
 5429: 			$request->print(&Apache::lonnet::ssi_body('/res/'.$url,
 5430: 					  ('grade_username' => $tuname,
 5431: 					   'grade_domain' => $tudom,
 5432: 					   'grade_courseid' => $tcrsid,
 5433: 					   'grade_symb' => $tsymb)));
 5434: 		    } else {
 5435: 			$request->print('<h3>Not authorized: '.$token.'</h3>');
 5436: 		    }
 5437: 		} else {
 5438: 		    $request->print('<h3>Not a valid DocID: '.$token.'</h3>');
 5439: 		}
 5440: 	    } else {
 5441: 		$request->print(&Apache::lonxml::tokeninputfield());
 5442: 	    }
 5443: 	}
 5444:     } else {
 5445: 	if (!($perm{'vgr'}=&Apache::lonnet::allowed('vgr',$env{'request.course.id'}))) {
 5446: 	    if ($perm{'vgr'}=&Apache::lonnet::allowed('vgr',$env{'request.course.id'}.'/'.$env{'request.course.sec'})) {
 5447: 		$perm{'vgr_section'}=$env{'request.course.sec'};
 5448: 	    } else {
 5449: 		delete($perm{'vgr'});
 5450: 	    }
 5451: 	}
 5452: 	if (!($perm{'mgr'}=&Apache::lonnet::allowed('mgr',$env{'request.course.id'}))) {
 5453: 	    if ($perm{'mgr'}=&Apache::lonnet::allowed('mgr',$env{'request.course.id'}.'/'.$env{'request.course.sec'})) {
 5454: 		$perm{'mgr_section'}=$env{'request.course.sec'};
 5455: 	    } else {
 5456: 		delete($perm{'mgr'});
 5457: 	    }
 5458: 	}
 5459: 	if ($command eq 'submission' && $perm{'vgr'}) {
 5460: 	    ($env{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0));
 5461: 	} elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) {
 5462: 	    &pickStudentPage($request);
 5463: 	} elsif ($command eq 'displayPage' && $perm{'vgr'}) {
 5464: 	    &displayPage($request);
 5465: 	} elsif ($command eq 'gradeByPage' && $perm{'mgr'}) {
 5466: 	    &updateGradeByPage($request);
 5467: 	} elsif ($command eq 'processGroup' && $perm{'vgr'}) {
 5468: 	    &processGroup($request);
 5469: 	} elsif ($command eq 'gradingmenu' && $perm{'vgr'}) {
 5470: 	    $request->print(&gradingmenu($request));
 5471: 	} elsif ($command eq 'viewgrades' && $perm{'vgr'}) {
 5472: 	    $request->print(&viewgrades($request));
 5473: 	} elsif ($command eq 'handgrade' && $perm{'mgr'}) {
 5474: 	    $request->print(&processHandGrade($request));
 5475: 	} elsif ($command eq 'editgrades' && $perm{'mgr'}) {
 5476: 	    $request->print(&editgrades($request));
 5477: 	} elsif ($command eq 'verify' && $perm{'vgr'}) {
 5478: 	    $request->print(&verifyreceipt($request));
 5479: 	} elsif ($command eq 'csvform' && $perm{'mgr'}) {
 5480: 	    $request->print(&upcsvScores_form($request));
 5481: 	} elsif ($command eq 'csvupload' && $perm{'mgr'}) {
 5482: 	    $request->print(&csvupload($request));
 5483: 	} elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) {
 5484: 	    $request->print(&csvuploadmap($request));
 5485: 	} elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) {
 5486: 	    if ($env{'form.associate'} ne 'Reverse Association') {
 5487: 		$request->print(&csvuploadoptions($request));
 5488: 	    } else {
 5489: 		if ( $env{'form.upfile_associate'} ne 'reverse' ) {
 5490: 		    $env{'form.upfile_associate'} = 'reverse';
 5491: 		} else {
 5492: 		    $env{'form.upfile_associate'} = 'forward';
 5493: 		}
 5494: 		$request->print(&csvuploadmap($request));
 5495: 	    }
 5496: 	} elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {
 5497: 	    $request->print(&csvuploadassign($request));
 5498: 	} elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
 5499: 	    $request->print(&scantron_selectphase($request));
 5500:  	} elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {
 5501:  	    $request->print(&scantron_do_warning($request));
 5502: 	} elsif ($command eq 'scantron_validate' && $perm{'mgr'}) {
 5503: 	    $request->print(&scantron_validate_file($request));
 5504: 	} elsif ($command eq 'scantron_process' && $perm{'mgr'}) {
 5505: 	    $request->print(&scantron_process_students($request));
 5506:  	} elsif ($command eq 'scantronupload' && 
 5507:  		 (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
 5508: 		  &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
 5509:  	    $request->print(&scantron_upload_scantron_data($request)); 
 5510:  	} elsif ($command eq 'scantronupload_save' &&
 5511:  		 (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
 5512: 		  &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
 5513:  	    $request->print(&scantron_upload_scantron_data_save($request));
 5514:  	} elsif ($command eq 'scantron_download' &&
 5515: 		 &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
 5516:  	    $request->print(&scantron_download_scantron_data($request));
 5517: 	} elsif ($command) {
 5518: 	    $request->print("Access Denied ($command)");
 5519: 	}
 5520:     }
 5521:     &send_footer($request);
 5522:     return '';
 5523: }
 5524: 
 5525: sub send_header {
 5526:     my ($request)= @_;
 5527:     $request->print(&Apache::lontexconvert::header());
 5528: #  $request->print("
 5529: #<script>
 5530: #remotewindow=open('','homeworkremote');
 5531: #remotewindow.close();
 5532: #</script>"); 
 5533:     $request->print(&Apache::loncommon::bodytag('Grading'));
 5534:     $request->rflush();
 5535: }
 5536: 
 5537: sub send_footer {
 5538:     my ($request)= @_;
 5539:     $request->print('</body></html>');
 5540: }
 5541: 
 5542: 1;
 5543: 
 5544: __END__;

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