File:  [LON-CAPA] / loncom / homework / grades.pm
Revision 1.438: download - view: text, annotated - select for diffs
Sun Sep 2 02:10:31 2007 UTC (16 years, 8 months ago) by www
Branches: MAIN
CVS tags: HEAD
Argh. And the reason that more than one student in the same class had the
same clicker is that the first student dropped and gave the clicker to the
next student who enrolled. Only go through the active classlist.

    1: # The LearningOnline Network with CAPA
    2: # The LON-CAPA Grading handler
    3: #
    4: # $Id: grades.pm,v 1.438 2007/09/02 02:10:31 www 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();
   40: use Apache::Constants qw(:common);
   41: use Apache::lonlocal;
   42: use Apache::lonenc;
   43: use String::Similarity;
   44: use LONCAPA;
   45: 
   46: use POSIX qw(floor);
   47: 
   48: 
   49: my %perm=();
   50: my %bubble_lines_per_response;     # no. bubble lines for each response.
   51:                                    # index is "symb.part_id"
   52: 
   53: 
   54: # ----- These first few routines are general use routines.----
   55: #
   56: # --- Retrieve the parts from the metadata file.---
   57: sub getpartlist {
   58:     my ($symb) = @_;
   59:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
   60:     my $partorder = &Apache::lonnet::metadata($url, 'partorder');
   61:     my @parts;
   62:     if ($partorder) {
   63: 	for my $part (split (/,/,$partorder)) {
   64: 	    if (!&Apache::loncommon::check_if_partid_hidden($part,$symb)) {
   65: 		push(@parts, $part);
   66: 	    }
   67: 	}	    
   68:     } else {
   69: 	my $metadata = &Apache::lonnet::metadata($url, 'packages');
   70: 	foreach (split(/\,/,$metadata)) {
   71: 	    if ($_ =~ /^part_(.*)$/) {
   72: 		if (!&Apache::loncommon::check_if_partid_hidden($1,$symb)) {
   73: 		    push(@parts, $1);
   74: 		}
   75: 	    }
   76: 	}
   77:     }
   78:     my @stores;
   79:     foreach my $part (@parts) {
   80: 	my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys'));
   81: 	foreach my $key (@metakeys) {
   82: 	    if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }
   83: 	}
   84:     }
   85:     return @stores;
   86: }
   87: 
   88: # --- Get the symbolic name of a problem and the url
   89: sub get_symb {
   90:     my ($request,$silent) = @_;
   91:     (my $url=$env{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
   92:     my $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url)));
   93:     if ($symb eq '') { 
   94: 	if (!$silent) {
   95: 	    $request->print("Unable to handle ambiguous references:$url:.");
   96: 	    return ();
   97: 	}
   98:     }
   99:     &Apache::lonenc::check_decrypt(\$symb);
  100:     return ($symb);
  101: }
  102: 
  103: #--- Format fullname, username:domain if different for display
  104: #--- Use anywhere where the student names are listed
  105: sub nameUserString {
  106:     my ($type,$fullname,$uname,$udom) = @_;
  107:     if ($type eq 'header') {
  108: 	return '<b>&nbsp;Fullname&nbsp;</b><span class="LC_internal_info">(Username)</span>';
  109:     } else {
  110: 	return '&nbsp;'.$fullname.'<span class="LC_internal_info">&nbsp;('.$uname.
  111: 	    ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</span>';
  112:     }
  113: }
  114: 
  115: #--- Get the partlist and the response type for a given problem. ---
  116: #--- Indicate if a response type is coded handgraded or not. ---
  117: sub response_type {
  118:     my ($symb) = shift;
  119: 
  120:     my $navmap = Apache::lonnavmaps::navmap->new();
  121:     my $res = $navmap->getBySymb($symb);
  122:     my $partlist = $res->parts();
  123:     my %vPart = 
  124: 	map { $_ => 1 } (&Apache::loncommon::get_env_multiple('form.vPart'));
  125:     my (%response_types,%handgrade);
  126:     foreach my $part (@{ $partlist }) {
  127: 	next if (%vPart && !exists($vPart{$part}));
  128: 
  129: 	my @types = $res->responseType($part);
  130: 	my @ids = $res->responseIds($part);
  131: 	for (my $i=0; $i < scalar(@ids); $i++) {
  132: 	    $response_types{$part}{$ids[$i]} = $types[$i];
  133: 	    $handgrade{$part.'_'.$ids[$i]} = 
  134: 		&Apache::lonnet::EXT('resource.'.$part.'_'.$ids[$i].
  135: 				     '.handgrade',$symb);
  136: 	}
  137:     }
  138:     return ($partlist,\%handgrade,\%response_types);
  139: }
  140: 
  141: sub flatten_responseType {
  142:     my ($responseType) = @_;
  143:     my @part_response_id =
  144: 	map { 
  145: 	    my $part = $_;
  146: 	    map {
  147: 		[$part,$_]
  148: 		} sort(keys(%{ $responseType->{$part} }));
  149: 	} sort(keys(%$responseType));
  150:     return @part_response_id;
  151: }
  152: 
  153: sub get_display_part {
  154:     my ($partID,$symb)=@_;
  155:     my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);
  156:     if (defined($display) and $display ne '') {
  157: 	$display.= " (<span class=\"LC_internal_info\">id $partID</span>)";
  158:     } else {
  159: 	$display=$partID;
  160:     }
  161:     return $display;
  162: }
  163: 
  164: #--- Show resource title
  165: #--- and parts and response type
  166: sub showResourceInfo {
  167:     my ($symb,$probTitle,$checkboxes) = @_;
  168:     my $col=3;
  169:     if ($checkboxes) { $col=4; }
  170:     my $result = '<h3>'.&mt('Current Resource').': '.$probTitle.'</h3>'."\n";
  171:     $result .='<table border="0">';
  172:     my ($partlist,$handgrade,$responseType) = &response_type($symb);
  173:     my %resptype = ();
  174:     my $hdgrade='no';
  175:     my %partsseen;
  176:     foreach my $partID (sort keys(%$responseType)) {
  177: 	foreach my $resID (sort keys(%{ $responseType->{$partID} })) {
  178: 	    my $handgrade=$$handgrade{$partID.'_'.$resID};
  179: 	    my $responsetype = $responseType->{$partID}->{$resID};
  180: 	    $hdgrade = $handgrade if ($handgrade eq 'yes');
  181: 	    $result.='<tr>';
  182: 	    if ($checkboxes) {
  183: 		if (exists($partsseen{$partID})) {
  184: 		    $result.="<td>&nbsp;</td>";
  185: 		} else {
  186: 		    $result.="<td><input type='checkbox' name='vPart' value='$partID' checked='checked' /></td>";
  187: 		}
  188: 		$partsseen{$partID}=1;
  189: 	    }
  190: 	    my $display_part=&get_display_part($partID,$symb);
  191: 	    $result.='<td><b>Part: </b>'.$display_part.' <span class="LC_internal_info">'.
  192: 		$resID.'</span></td>'.
  193: 		'<td><b>Type: </b>'.$responsetype.'</td></tr>';
  194: #	    '<td><b>Handgrade: </b>'.$handgrade.'</td></tr>';
  195: 	}
  196:     }
  197:     $result.='</table>'."\n";
  198:     return $result,$responseType,$hdgrade,$partlist,$handgrade;
  199: }
  200: 
  201: sub reset_caches {
  202:     &reset_analyze_cache();
  203:     &reset_perm();
  204: }
  205: 
  206: {
  207:     my %analyze_cache;
  208: 
  209:     sub reset_analyze_cache {
  210: 	undef(%analyze_cache);
  211:     }
  212: 
  213:     sub get_analyze {
  214: 	my ($symb,$uname,$udom)=@_;
  215: 	my $key = "$symb\0$uname\0$udom";
  216: 	return $analyze_cache{$key} if (exists($analyze_cache{$key}));
  217: 
  218: 	my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
  219: 	$url=&Apache::lonnet::clutter($url);
  220: 	my $subresult=&Apache::lonnet::ssi($url,
  221: 					   ('grade_target' => 'analyze'),
  222: 					   ('grade_domain' => $udom),
  223: 					   ('grade_symb' => $symb),
  224: 					   ('grade_courseid' => 
  225: 					    $env{'request.course.id'}),
  226: 					   ('grade_username' => $uname));
  227: 	(undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
  228: 	my %analyze=&Apache::lonnet::str2hash($subresult);
  229: 	return $analyze_cache{$key} = \%analyze;
  230:     }
  231: 
  232:     sub get_order {
  233: 	my ($partid,$respid,$symb,$uname,$udom)=@_;
  234: 	my $analyze = &get_analyze($symb,$uname,$udom);
  235: 	return $analyze->{"$partid.$respid.shown"};
  236:     }
  237: 
  238:     sub get_radiobutton_correct_foil {
  239: 	my ($partid,$respid,$symb,$uname,$udom)=@_;
  240: 	my $analyze = &get_analyze($symb,$uname,$udom);
  241: 	foreach my $foil (@{&get_order($partid,$respid,$symb,$uname,$udom)}) {
  242: 	    if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {
  243: 		return $foil;
  244: 	    }
  245: 	}
  246:     }
  247: }
  248: 
  249: #--- Clean response type for display
  250: #--- Currently filters option/rank/radiobutton/match/essay/Task
  251: #        response types only.
  252: sub cleanRecord {
  253:     my ($answer,$response,$symb,$partid,$respid,$record,$order,$version,
  254: 	$uname,$udom) = @_;
  255:     my $grayFont = '<span class="LC_internal_info">';
  256:     if ($response =~ /^(option|rank)$/) {
  257: 	my %answer=&Apache::lonnet::str2hash($answer);
  258: 	my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
  259: 	my ($toprow,$bottomrow);
  260: 	foreach my $foil (@$order) {
  261: 	    if ($grading{$foil} == 1) {
  262: 		$toprow.='<td><b>'.$answer{$foil}.'&nbsp;</b></td>';
  263: 	    } else {
  264: 		$toprow.='<td><i>'.$answer{$foil}.'&nbsp;</i></td>';
  265: 	    }
  266: 	    $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
  267: 	}
  268: 	return '<blockquote><table border="1">'.
  269: 	    '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'.
  270: 	    '<tr valign="top"><td>'.$grayFont.'Option ID</span></td>'.
  271: 	    $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';
  272:     } elsif ($response eq 'match') {
  273: 	my %answer=&Apache::lonnet::str2hash($answer);
  274: 	my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
  275: 	my @items=&Apache::lonnet::str2array($record->{$version."resource.$partid.$respid.submissionitems"});
  276: 	my ($toprow,$middlerow,$bottomrow);
  277: 	foreach my $foil (@$order) {
  278: 	    my $item=shift(@items);
  279: 	    if ($grading{$foil} == 1) {
  280: 		$toprow.='<td><b>'.$item.'&nbsp;</b></td>';
  281: 		$middlerow.='<td><b>'.$grayFont.$answer{$foil}.'&nbsp;</span></b></td>';
  282: 	    } else {
  283: 		$toprow.='<td><i>'.$item.'&nbsp;</i></td>';
  284: 		$middlerow.='<td><i>'.$grayFont.$answer{$foil}.'&nbsp;</span></i></td>';
  285: 	    }
  286: 	    $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
  287: 	}
  288: 	return '<blockquote><table border="1">'.
  289: 	    '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'.
  290: 	    '<tr valign="top"><td>'.$grayFont.'Item ID</span></td>'.
  291: 	    $middlerow.'</tr>'.
  292: 	    '<tr valign="top"><td>'.$grayFont.'Option ID</span></td>'.
  293: 	    $bottomrow.'</tr>'.'</table></blockquote>';
  294:     } elsif ($response eq 'radiobutton') {
  295: 	my %answer=&Apache::lonnet::str2hash($answer);
  296: 	my ($toprow,$bottomrow);
  297: 	my $correct = 
  298: 	    &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom);
  299: 	foreach my $foil (@$order) {
  300: 	    if (exists($answer{$foil})) {
  301: 		if ($foil eq $correct) {
  302: 		    $toprow.='<td><b>true</b></td>';
  303: 		} else {
  304: 		    $toprow.='<td><i>true</i></td>';
  305: 		}
  306: 	    } else {
  307: 		$toprow.='<td>false</td>';
  308: 	    }
  309: 	    $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
  310: 	}
  311: 	return '<blockquote><table border="1">'.
  312: 	    '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'.
  313: 	    '<tr valign="top"><td>'.$grayFont.'Option ID</span></td>'.
  314: 	    $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';
  315:     } elsif ($response eq 'essay') {
  316: 	if (! exists ($env{'form.'.$symb})) {
  317: 	    my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade',
  318: 						  $env{'course.'.$env{'request.course.id'}.'.domain'},
  319: 						  $env{'course.'.$env{'request.course.id'}.'.num'});
  320: 
  321: 	    my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
  322: 	    $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
  323: 	    $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
  324: 	    $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
  325: 	    $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
  326: 	    $env{'form.'.$symb} = 1; # so that we don't have to read it from disk for multiple sub of the same prob.
  327: 	}
  328: 	$answer =~ s-\n-<br />-g;
  329: 	return '<br /><br /><blockquote><tt>'.&keywords_highlight($answer).'</tt></blockquote>';
  330:     } elsif ( $response eq 'organic') {
  331: 	my $result='Smile representation: "<tt>'.$answer.'</tt>"';
  332: 	my $jme=$record->{$version."resource.$partid.$respid.molecule"};
  333: 	$result.=&Apache::chemresponse::jme_img($jme,$answer,400);
  334: 	return $result;
  335:     } elsif ( $response eq 'Task') {
  336: 	if ( $answer eq 'SUBMITTED') {
  337: 	    my $files = $record->{$version."resource.$respid.$partid.bridgetask.portfiles"};
  338: 	    my $result = &Apache::bridgetask::file_list($files,$uname,$udom);
  339: 	    return $result;
  340: 	} elsif ( grep(/^\Q$version\E.*?\.instance$/, keys(%{$record})) ) {
  341: 	    my @matches = grep(/^\Q$version\E.*?\.instance$/,
  342: 			       keys(%{$record}));
  343: 	    return join('<br />',($version,@matches));
  344: 			       
  345: 			       
  346: 	} else {
  347: 	    my $result =
  348: 		'<p>'
  349: 		.&mt('Overall result: [_1]',
  350: 		     $record->{$version."resource.$respid.$partid.status"})
  351: 		.'</p>';
  352: 	    
  353: 	    $result .= '<ul>';
  354: 	    my @grade = grep(/^\Q${version}resource.$respid.$partid.\E[^.]*[.]status$/,
  355: 			     keys(%{$record}));
  356: 	    foreach my $grade (sort(@grade)) {
  357: 		my ($dim) = ($grade =~/[.]([^.]+)[.]status$/);
  358: 		$result.= '<li>'.&mt("Dimension: [_1], status [_2] ",
  359: 				     $dim, $record->{$grade}).
  360: 			  '</li>';
  361: 	    }
  362: 	    $result.='</ul>';
  363: 	    return $result;
  364: 	}
  365:        
  366:     }
  367:     return $answer;
  368: }
  369: 
  370: #-- A couple of common js functions
  371: sub commonJSfunctions {
  372:     my $request = shift;
  373:     $request->print(<<COMMONJSFUNCTIONS);
  374: <script type="text/javascript" language="javascript">
  375:     function radioSelection(radioButton) {
  376: 	var selection=null;
  377: 	if (radioButton.length > 1) {
  378: 	    for (var i=0; i<radioButton.length; i++) {
  379: 		if (radioButton[i].checked) {
  380: 		    return radioButton[i].value;
  381: 		}
  382: 	    }
  383: 	} else {
  384: 	    if (radioButton.checked) return radioButton.value;
  385: 	}
  386: 	return selection;
  387:     }
  388: 
  389:     function pullDownSelection(selectOne) {
  390: 	var selection="";
  391: 	if (selectOne.length > 1) {
  392: 	    for (var i=0; i<selectOne.length; i++) {
  393: 		if (selectOne[i].selected) {
  394: 		    return selectOne[i].value;
  395: 		}
  396: 	    }
  397: 	} else {
  398:             // only one value it must be the selected one
  399: 	    return selectOne.value;
  400: 	}
  401:     }
  402: </script>
  403: COMMONJSFUNCTIONS
  404: }
  405: 
  406: #--- Dumps the class list with usernames,list of sections,
  407: #--- section, ids and fullnames for each user.
  408: sub getclasslist {
  409:     my ($getsec,$filterlist) = @_;
  410:     my @getsec;
  411:     if (!ref($getsec)) {
  412: 	if ($getsec ne '' && $getsec ne 'all') {
  413: 	    @getsec=($getsec);
  414: 	}
  415:     } else {
  416: 	@getsec=@{$getsec};
  417:     }
  418:     if (grep(/^all$/,@getsec)) { undef(@getsec); }
  419: 
  420:     my $classlist=&Apache::loncoursedata::get_classlist();
  421:     # Bail out if we were unable to get the classlist
  422:     return if (! defined($classlist));
  423:     #
  424:     my %sections;
  425:     my %fullnames;
  426:     foreach my $student (keys(%$classlist)) {
  427:         my $end      = 
  428:             $classlist->{$student}->[&Apache::loncoursedata::CL_END()];
  429:         my $start    = 
  430:             $classlist->{$student}->[&Apache::loncoursedata::CL_START()];
  431:         my $id       = 
  432:             $classlist->{$student}->[&Apache::loncoursedata::CL_ID()];
  433:         my $section  = 
  434:             $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
  435:         my $fullname = 
  436:             $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()];
  437:         my $status   = 
  438:             $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()];
  439: 	# filter students according to status selected
  440: 	if ($filterlist && $env{'form.Status'} ne 'Any') {
  441: 	    if ($env{'form.Status'} ne $status) {
  442: 		delete ($classlist->{$student});
  443: 		next;
  444: 	    }
  445: 	}
  446: 	$section = ($section ne '' ? $section : 'none');
  447: 	if (&canview($section)) {
  448: 	    if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {
  449: 		$sections{$section}++;
  450: 		$fullnames{$student}=$fullname;
  451: 	    } else {
  452: 		delete($classlist->{$student});
  453: 	    }
  454: 	} else {
  455: 	    delete($classlist->{$student});
  456: 	}
  457:     }
  458:     my %seen = ();
  459:     my @sections = sort(keys(%sections));
  460:     return ($classlist,\@sections,\%fullnames);
  461: }
  462: 
  463: sub canmodify {
  464:     my ($sec)=@_;
  465:     if ($perm{'mgr'}) {
  466: 	if (!defined($perm{'mgr_section'})) {
  467: 	    # can modify whole class
  468: 	    return 1;
  469: 	} else {
  470: 	    if ($sec eq $perm{'mgr_section'}) {
  471: 		#can modify the requested section
  472: 		return 1;
  473: 	    } else {
  474: 		# can't modify the request section
  475: 		return 0;
  476: 	    }
  477: 	}
  478:     }
  479:     #can't modify
  480:     return 0;
  481: }
  482: 
  483: sub canview {
  484:     my ($sec)=@_;
  485:     if ($perm{'vgr'}) {
  486: 	if (!defined($perm{'vgr_section'})) {
  487: 	    # can modify whole class
  488: 	    return 1;
  489: 	} else {
  490: 	    if ($sec eq $perm{'vgr_section'}) {
  491: 		#can modify the requested section
  492: 		return 1;
  493: 	    } else {
  494: 		# can't modify the request section
  495: 		return 0;
  496: 	    }
  497: 	}
  498:     }
  499:     #can't modify
  500:     return 0;
  501: }
  502: 
  503: #--- Retrieve the grade status of a student for all the parts
  504: sub student_gradeStatus {
  505:     my ($symb,$udom,$uname,$partlist) = @_;
  506:     my %record     = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
  507:     my %partstatus = ();
  508:     foreach (@$partlist) {
  509: 	my ($status,undef)   = split(/_/,$record{"resource.$_.solved"},2);
  510: 	$status              = 'nothing' if ($status eq '');
  511: 	$partstatus{$_}      = $status;
  512: 	my $subkey           = "resource.$_.submitted_by";
  513: 	$partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
  514:     }
  515:     return %partstatus;
  516: }
  517: 
  518: # hidden form and javascript that calls the form
  519: # Use by verifyscript and viewgrades
  520: # Shows a student's view of problem and submission
  521: sub jscriptNform {
  522:     my ($symb) = @_;
  523:     my $jscript='<script type="text/javascript" language="javascript">'."\n".
  524: 	'    function viewOneStudent(user,domain) {'."\n".
  525: 	'	document.onestudent.student.value = user;'."\n".
  526: 	'	document.onestudent.userdom.value = domain;'."\n".
  527: 	'	document.onestudent.submit();'."\n".
  528: 	'    }'."\n".
  529: 	'</script>'."\n";
  530:     $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\n".
  531: 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
  532: 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
  533: 	'<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n".
  534: 	'<input type="hidden" name="Status"  value="'.$env{'form.Status'}.'" />'."\n".
  535: 	'<input type="hidden" name="command" value="submission" />'."\n".
  536: 	'<input type="hidden" name="student" value="" />'."\n".
  537: 	'<input type="hidden" name="userdom" value="" />'."\n".
  538: 	'</form>'."\n";
  539:     return $jscript;
  540: }
  541: 
  542: # Given the score (as a number [0-1] and the weight) what is the final
  543: # point value? This function will round to the nearest tenth, third,
  544: # or quarter if one of those is within the tolerance of .00001.
  545: sub compute_points {
  546:     my ($score, $weight) = @_;
  547:     
  548:     my $tolerance = .00001;
  549:     my $points = $score * $weight;
  550: 
  551:     # Check for nearness to 1/x.
  552:     my $check_for_nearness = sub {
  553:         my ($factor) = @_;
  554:         my $num = ($points * $factor) + $tolerance;
  555:         my $floored_num = floor($num);
  556:         if ($num - $floored_num < 2 * $tolerance * $factor) {
  557:             return $floored_num / $factor;
  558:         }
  559:         return $points;
  560:     };
  561: 
  562:     $points = $check_for_nearness->(10);
  563:     $points = $check_for_nearness->(3);
  564:     $points = $check_for_nearness->(4);
  565:     
  566:     return $points;
  567: }
  568: 
  569: #------------------ End of general use routines --------------------
  570: 
  571: #
  572: # Find most similar essay
  573: #
  574: 
  575: sub most_similar {
  576:     my ($uname,$udom,$uessay,$old_essays)=@_;
  577: 
  578: # ignore spaces and punctuation
  579: 
  580:     $uessay=~s/\W+/ /gs;
  581: 
  582: # ignore empty submissions (occuring when only files are sent)
  583: 
  584:     unless ($uessay=~/\w+/) { return ''; }
  585: 
  586: # these will be returned. Do not care if not at least 50 percent similar
  587:     my $limit=0.6;
  588:     my $sname='';
  589:     my $sdom='';
  590:     my $scrsid='';
  591:     my $sessay='';
  592: # go through all essays ...
  593:     foreach my $tkey (keys(%$old_essays)) {
  594: 	my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey));
  595: # ... except the same student
  596:         next if (($tname eq $uname) && ($tdom eq $udom));
  597: 	my $tessay=$old_essays->{$tkey};
  598: 	$tessay=~s/\W+/ /gs;
  599: # String similarity gives up if not even limit
  600: 	my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit);
  601: # Found one
  602: 	if ($tsimilar>$limit) {
  603: 	    $limit=$tsimilar;
  604: 	    $sname=$tname;
  605: 	    $sdom=$tdom;
  606: 	    $scrsid=$tcrsid;
  607: 	    $sessay=$old_essays->{$tkey};
  608: 	}
  609:     }
  610:     if ($limit>0.6) {
  611:        return ($sname,$sdom,$scrsid,$sessay,$limit);
  612:     } else {
  613:        return ('','','','',0);
  614:     }
  615: }
  616: 
  617: #-------------------------------------------------------------------
  618: 
  619: #------------------------------------ Receipt Verification Routines
  620: #
  621: #--- Check whether a receipt number is valid.---
  622: sub verifyreceipt {
  623:     my $request  = shift;
  624: 
  625:     my $courseid = $env{'request.course.id'};
  626:     my $receipt  = &Apache::lonnet::recprefix($courseid).'-'.
  627: 	$env{'form.receipt'};
  628:     $receipt     =~ s/[^\-\d]//g;
  629:     my ($symb)   = &get_symb($request);
  630: 
  631:     my $title.='<h3><span class="LC_info">Verifying Submission Receipt '.
  632: 	$receipt.'</h3></span>'."\n".
  633: 	'<h4><b>Resource: </b>'.$env{'form.probTitle'}.'</h4><br /><br />'."\n";
  634: 
  635:     my ($string,$contents,$matches) = ('','',0);
  636:     my (undef,undef,$fullname) = &getclasslist('all','0');
  637:     
  638:     my $receiptparts=0;
  639:     if ($env{"course.$courseid.receiptalg"} eq 'receipt2' ||
  640: 	$env{"course.$courseid.receiptalg"} eq 'receipt3') { $receiptparts=1; }
  641:     my $parts=['0'];
  642:     if ($receiptparts) { ($parts)=&response_type($symb); }
  643:     foreach (sort 
  644: 	     {
  645: 		 if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
  646: 		     return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
  647: 		 }
  648: 		 return $a cmp $b;
  649: 	     } (keys(%$fullname))) {
  650: 	my ($uname,$udom)=split(/\:/);
  651: 	foreach my $part (@$parts) {
  652: 	    if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) {
  653: 		$contents.='<tr bgcolor="#ffffe6"><td>&nbsp;'."\n".
  654: 		    '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
  655: 		    '\');" target="_self">'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".
  656: 		    '<td>&nbsp;'.$uname.'&nbsp;</td>'.
  657: 		    '<td>&nbsp;'.$udom.'&nbsp;</td>';
  658: 		if ($receiptparts) {
  659: 		    $contents.='<td>&nbsp;'.$part.'&nbsp;</td>';
  660: 		}
  661: 		$contents.='</tr>'."\n";
  662: 		
  663: 		$matches++;
  664: 	    }
  665: 	}
  666:     }
  667:     if ($matches == 0) {
  668: 	$string = $title.'No match found for the above receipt.';
  669:     } else {
  670: 	$string = &jscriptNform($symb).$title.
  671: 	    'The above receipt matches the following student'.
  672: 	    ($matches <= 1 ? '.' : 's.')."\n".
  673: 	    '<table border="0"><tr><td bgcolor="#777777">'."\n".
  674: 	    '<table border="0"><tr bgcolor="#e6ffff">'."\n".
  675: 	    '<td><b>&nbsp;Fullname&nbsp;</b></td>'."\n".
  676: 	    '<td><b>&nbsp;Username&nbsp;</b></td>'."\n".
  677: 	    '<td><b>&nbsp;Domain&nbsp;</b></td>';
  678: 	if ($receiptparts) {
  679: 	    $string.='<td>&nbsp;Problem Part&nbsp;</td>';
  680: 	}
  681: 	$string.='</tr>'."\n".$contents.
  682: 	    '</table></td></tr></table>'."\n";
  683:     }
  684:     return $string.&show_grading_menu_form($symb);
  685: }
  686: 
  687: #--- This is called by a number of programs.
  688: #--- Called from the Grading Menu - View/Grade an individual student
  689: #--- Also called directly when one clicks on the subm button 
  690: #    on the problem page.
  691: sub listStudents {
  692:     my ($request) = shift;
  693: 
  694:     my ($symb) = &get_symb($request);
  695:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
  696:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
  697:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
  698:     my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
  699: 
  700:     my $viewgrade = $env{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View';
  701:     $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 
  702: 	&Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
  703: 
  704:     my $result='<h3><span class="LC_info">&nbsp;'.$viewgrade.
  705: 	' Submissions for a Student or a Group of Students</span></h3>';
  706: 
  707:     my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($symb,$env{'form.probTitle'},($env{'form.showgrading'} eq 'yes'));
  708: 
  709:     $request->print(<<LISTJAVASCRIPT);
  710: <script type="text/javascript" language="javascript">
  711:     function checkSelect(checkBox) {
  712: 	var ctr=0;
  713: 	var sense="";
  714: 	if (checkBox.length > 1) {
  715: 	    for (var i=0; i<checkBox.length; i++) {
  716: 		if (checkBox[i].checked) {
  717: 		    ctr++;
  718: 		}
  719: 	    }
  720: 	    sense = "a student or group of students";
  721: 	} else {
  722: 	    if (checkBox.checked) {
  723: 		ctr = 1;
  724: 	    }
  725: 	    sense = "the student";
  726: 	}
  727: 	if (ctr == 0) {
  728: 	    alert("Please select "+sense+" before clicking on the Next button.");
  729: 	    return false;
  730: 	}
  731: 	document.gradesub.submit();
  732:     }
  733: 
  734:     function reLoadList(formname) {
  735: 	if (formname.saveStatusOld.value == pullDownSelection(formname.Status)) {return;}
  736: 	formname.command.value = 'submission';
  737: 	formname.submit();
  738:     }
  739: </script>
  740: LISTJAVASCRIPT
  741: 
  742:     &commonJSfunctions($request);
  743:     $request->print($result);
  744: 
  745:     my $checkhdgrade = ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1 ) ? 'checked="checked"' : '';
  746:     my $checklastsub = $checkhdgrade eq '' ? 'checked="checked"' : '';
  747:     my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.
  748: 	"\n".$table.
  749: 	'&nbsp;<b>View Problem Text: </b><label><input type="radio" name="vProb" value="no" checked="checked" /> no </label>'."\n".
  750: 	'<label><input type="radio" name="vProb" value="yes" /> one student </label>'."\n".
  751: 	'<label><input type="radio" name="vProb" value="all" /> all students </label><br />'."\n".
  752: 	'&nbsp;<b>View Answer: </b><label><input type="radio" name="vAns" value="no"  /> no </label>'."\n".
  753: 	'<label><input type="radio" name="vAns" value="yes" /> one student </label>'."\n".
  754: 	'<label><input type="radio" name="vAns" value="all" checked="checked" /> all students </label><br />'."\n".
  755: 	'&nbsp;<b>Submissions: </b>'."\n";
  756:     if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {
  757: 	$gradeTable.='<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> essay part only </label>'."\n";
  758:     }
  759: 
  760:     my $saveStatus = $env{'form.Status'} eq '' ? 'Active' : $env{'form.Status'};
  761:     $env{'form.Status'} = $saveStatus;
  762:     $gradeTable.='<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> last submission only </label>'."\n".
  763: 	'<label><input type="radio" name="lastSub" value="last" /> last submission & parts info </label>'."\n".
  764: 	'<label><input type="radio" name="lastSub" value="datesub" /> by dates and submissions </label>'."\n".
  765: 	'<label><input type="radio" name="lastSub" value="all" /> all details</label><br />'."\n".
  766:         '&nbsp;<b>Grading Increments:</b> <select name="increment">'.
  767:         '<option value="1">Whole Points</option>'.
  768:         '<option value=".5">Half Points</option>'.
  769:         '<option value=".25">Quarter Points</option>'.
  770:         '<option value=".1">Tenths of a Point</option>'.
  771:         '</select>'.
  772:         &build_section_inputs().
  773: 	'<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".
  774: 	'<input type="hidden" name="handgrade"   value="'.$env{'form.handgrade'}.'" /><br />'."\n".
  775: 	'<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" /><br />'."\n".
  776: 	'<input type="hidden" name="saveState"   value="'.$env{'form.saveState'}.'" />'."\n".
  777: 	'<input type="hidden" name="probTitle"   value="'.$env{'form.probTitle'}.'" />'."\n".
  778: 	'<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
  779: 	'<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";
  780: 
  781:     if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) {
  782: 	$gradeTable.='<input type="hidden" name="Status"   value="'.$env{'form.Status'}.'" />'."\n";
  783:     } else {
  784: 	$gradeTable.='<b>Student Status:</b> '.
  785: 	    &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);').'<br />';
  786:     }
  787: 
  788:     $gradeTable.='To '.lc($viewgrade).' a submission or a group of submissions, click on the check box(es) '.
  789: 	'next to the student\'s name(s). Then click on the Next button.<br />'."\n".
  790: 	'<input type="hidden" name="command" value="processGroup" />'."\n";
  791: 
  792: # checkall buttons
  793:     $gradeTable.=&check_script('gradesub', 'stuinfo');
  794:     $gradeTable.='<input type="button" '."\n".
  795: 	'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n".
  796: 	'value="Next->" /> <br />'."\n";
  797:     $gradeTable.=&check_buttons();
  798:     $gradeTable.='<label><input type="checkbox" name="checkPlag" checked="checked" />Check For Plagiarism</label>';
  799:     my ($classlist, undef, $fullname) = &getclasslist($getsec,'1');
  800:     $gradeTable.='<table border="0"><tr><td bgcolor="#777777">'.
  801: 	'<table border="0"><tr bgcolor="#e6ffff">';
  802:     my $loop = 0;
  803:     while ($loop < 2) {
  804: 	$gradeTable.='<td><b>&nbsp;No.</b>&nbsp;</td><td><b>&nbsp;Select&nbsp;</b></td>'.
  805: 	    '<td>'.&nameUserString('header').'&nbsp;Section/Group</td>';
  806: 	if ($env{'form.showgrading'} eq 'yes' 
  807: 	    && $submitonly ne 'queued'
  808: 	    && $submitonly ne 'all') {
  809: 	    foreach (sort(@$partlist)) {
  810: 		my $display_part=&get_display_part((split(/_/))[0],$symb);
  811: 		$gradeTable.='<td><b>&nbsp;Part: '.$display_part.
  812: 		    ' Status&nbsp;</b></td>';
  813: 	    }
  814: 	} elsif ($submitonly eq 'queued') {
  815: 	    $gradeTable.='<td><b>&nbsp;'.&mt('Queue Status').'&nbsp;</b></td>';
  816: 	}
  817: 	$loop++;
  818: #	$gradeTable.='<td></td>' if ($loop%2 ==1);
  819:     }
  820:     $gradeTable.='</tr>'."\n";
  821: 
  822:     my $ctr = 0;
  823:     foreach my $student (sort 
  824: 			 {
  825: 			     if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
  826: 				 return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
  827: 			     }
  828: 			     return $a cmp $b;
  829: 			 }
  830: 			 (keys(%$fullname))) {
  831: 	my ($uname,$udom) = split(/:/,$student);
  832: 
  833: 	my %status = ();
  834: 
  835: 	if ($submitonly eq 'queued') {
  836: 	    my %queue_status = 
  837: 		&Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
  838: 							$udom,$uname);
  839: 	    next if (!defined($queue_status{'gradingqueue'}));
  840: 	    $status{'gradingqueue'} = $queue_status{'gradingqueue'};
  841: 	}
  842: 
  843: 	if ($env{'form.showgrading'} eq 'yes' 
  844: 	    && $submitonly ne 'queued'
  845: 	    && $submitonly ne 'all') {
  846: 	    (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist);
  847: 	    my $submitted = 0;
  848: 	    my $graded = 0;
  849: 	    my $incorrect = 0;
  850: 	    foreach (keys(%status)) {
  851: 		$submitted = 1 if ($status{$_} ne 'nothing');
  852: 		$graded = 1 if ($status{$_} =~ /^ungraded/);
  853: 		$incorrect = 1 if ($status{$_} =~ /^incorrect/);
  854: 		
  855: 		my ($foo,$partid,$foo1) = split(/\./,$_);
  856: 		if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
  857: 		    $submitted = 0;
  858: 		    my ($part)=split(/\./,$partid);
  859: 		    $gradeTable.='<input type="hidden" name="'.
  860: 			$student.':'.$part.':submitted_by" value="'.
  861: 			$status{'resource.'.$partid.'.submitted_by'}.'" />';
  862: 		}
  863: 	    }
  864: 	    
  865: 	    next if (!$submitted && ($submitonly eq 'yes' ||
  866: 				     $submitonly eq 'incorrect' ||
  867: 				     $submitonly eq 'graded'));
  868: 	    next if (!$graded && ($submitonly eq 'graded'));
  869: 	    next if (!$incorrect && $submitonly eq 'incorrect');
  870: 	}
  871: 
  872: 	$ctr++;
  873: 	my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
  874: 
  875: 	if ( $perm{'vgr'} eq 'F' ) {
  876: 	    $gradeTable.='<tr bgcolor="#ffffe6">' if ($ctr%2 ==1);
  877: 	    $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.
  878:                '<td align="center"><label><input type=checkbox name="stuinfo" value="'.
  879:                $student.':'.$$fullname{$student}.':::SECTION'.$section.
  880: 	       ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.
  881: 	       &nameUserString(undef,$$fullname{$student},$uname,$udom).
  882: 	       '&nbsp;'.$section.'</td>'."\n";
  883: 
  884: 	    if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {
  885: 		foreach (sort keys(%status)) {
  886: 		    next if (/^resource.*?submitted_by$/);
  887: 		    $gradeTable.='<td align="center">&nbsp;'.$status{$_}.'&nbsp;</td>'."\n";
  888: 		}
  889: 	    }
  890: #	    $gradeTable.='<td></td>' if ($ctr%2 ==1);
  891: 	    $gradeTable.='</tr>'."\n" if ($ctr%2 ==0);
  892: 	}
  893:     }
  894:     if ($ctr%2 ==1) {
  895: 	$gradeTable.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>';
  896: 	    if ($env{'form.showgrading'} eq 'yes' 
  897: 		&& $submitonly ne 'queued'
  898: 		&& $submitonly ne 'all') {
  899: 		foreach (@$partlist) {
  900: 		    $gradeTable.='<td>&nbsp;</td>';
  901: 		}
  902: 	    } elsif ($submitonly eq 'queued') {
  903: 		$gradeTable.='<td>&nbsp;</td>';
  904: 	    }
  905: 	$gradeTable.='</tr>';
  906:     }
  907: 
  908:     $gradeTable.='</table></td></tr></table>'."\n".
  909: 	'<input type="button" '.
  910: 	'onClick="javascript:checkSelect(this.form.stuinfo);" '.
  911: 	'value="Next->" /></form>'."\n";
  912:     if ($ctr == 0) {
  913: 	my $num_students=(scalar(keys(%$fullname)));
  914: 	if ($num_students eq 0) {
  915: 	    $gradeTable='<br />&nbsp;<span class="LC_warning">There are no students currently enrolled.</span>';
  916: 	} else {
  917: 	    my $submissions='submissions';
  918: 	    if ($submitonly eq 'incorrect') { $submissions = 'incorrect submissions'; }
  919: 	    if ($submitonly eq 'graded'   ) { $submissions = 'ungraded submissions'; }
  920: 	    if ($submitonly eq 'queued'   ) { $submissions = 'queued submissions'; }
  921: 	    $gradeTable='<br />&nbsp;<span class="LC_warning">'.
  922: 		'No '.$submissions.' found for this resource for any students. ('.$num_students.
  923: 		' students checked for '.$submissions.')</span><br />';
  924: 	}
  925:     } elsif ($ctr == 1) {
  926: 	$gradeTable =~ s/type=checkbox/type=checkbox checked/;
  927:     }
  928:     $gradeTable.=&show_grading_menu_form($symb);
  929:     $request->print($gradeTable);
  930:     return '';
  931: }
  932: 
  933: #---- Called from the listStudents routine
  934: 
  935: sub check_script {
  936:     my ($form, $type)=@_;
  937:     my $chkallscript='<script type="text/javascript">
  938:     function checkall() {
  939:         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
  940:             ele = document.forms.'.$form.'.elements[i];
  941:             if (ele.name == "'.$type.'") {
  942:             document.forms.'.$form.'.elements[i].checked=true;
  943:                                        }
  944:         }
  945:     }
  946: 
  947:     function checksec() {
  948:         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
  949:             ele = document.forms.'.$form.'.elements[i];
  950:            string = document.forms.'.$form.'.chksec.value;
  951:            if
  952:           (ele.value.indexOf(":::SECTION"+string)>0) {
  953:               document.forms.'.$form.'.elements[i].checked=true;
  954:             }
  955:         }
  956:     }
  957: 
  958: 
  959:     function uncheckall() {
  960:         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
  961:             ele = document.forms.'.$form.'.elements[i];
  962:             if (ele.name == "'.$type.'") {
  963:             document.forms.'.$form.'.elements[i].checked=false;
  964:                                        }
  965:         }
  966:     }
  967: 
  968: </script>'."\n";
  969:     return $chkallscript;
  970: }
  971: 
  972: sub check_buttons {
  973:     my $buttons.='<input type="button" onclick="checkall()" value="Check All" />';
  974:     $buttons.='<input type="button" onclick="uncheckall()" value="Uncheck All" />&nbsp;';
  975:     $buttons.='<input type="button" onclick="checksec()" value="Check Section/Group" />';
  976:     $buttons.='<input type="text" size="5" name="chksec" />&nbsp;';
  977:     return $buttons;
  978: }
  979: 
  980: #     Displays the submissions for one student or a group of students
  981: sub processGroup {
  982:     my ($request)  = shift;
  983:     my $ctr        = 0;
  984:     my @stuchecked = &Apache::loncommon::get_env_multiple('form.stuinfo');
  985:     my $total      = scalar(@stuchecked)-1;
  986: 
  987:     foreach my $student (@stuchecked) {
  988: 	my ($uname,$udom,$fullname) = split(/:/,$student);
  989: 	$env{'form.student'}        = $uname;
  990: 	$env{'form.userdom'}        = $udom;
  991: 	$env{'form.fullname'}       = $fullname;
  992: 	&submission($request,$ctr,$total);
  993: 	$ctr++;
  994:     }
  995:     return '';
  996: }
  997: 
  998: #------------------------------------------------------------------------------------
  999: #
 1000: #-------------------------- Next few routines handles grading by student, essentially
 1001: #                           handles essay response type problem/part
 1002: #
 1003: #--- Javascript to handle the submission page functionality ---
 1004: sub sub_page_js {
 1005:     my $request = shift;
 1006:     $request->print(<<SUBJAVASCRIPT);
 1007: <script type="text/javascript" language="javascript">
 1008:     function updateRadio(formname,id,weight) {
 1009: 	var gradeBox = formname["GD_BOX"+id];
 1010: 	var radioButton = formname["RADVAL"+id];
 1011: 	var oldpts = formname["oldpts"+id].value;
 1012: 	var pts = checkSolved(formname,id) == 'update' ? gradeBox.value : oldpts;
 1013: 	gradeBox.value = pts;
 1014: 	var resetbox = false;
 1015: 	if (isNaN(pts) || pts < 0) {
 1016: 	    alert("A number equal or greater than 0 is expected. Entered value = "+pts);
 1017: 	    for (var i=0; i<radioButton.length; i++) {
 1018: 		if (radioButton[i].checked) {
 1019: 		    gradeBox.value = i;
 1020: 		    resetbox = true;
 1021: 		}
 1022: 	    }
 1023: 	    if (!resetbox) {
 1024: 		formtextbox.value = "";
 1025: 	    }
 1026: 	    return;
 1027: 	}
 1028: 
 1029: 	if (pts > weight) {
 1030: 	    var resp = confirm("You entered a value ("+pts+
 1031: 			       ") greater than the weight for the part. Accept?");
 1032: 	    if (resp == false) {
 1033: 		gradeBox.value = oldpts;
 1034: 		return;
 1035: 	    }
 1036: 	}
 1037: 
 1038: 	for (var i=0; i<radioButton.length; i++) {
 1039: 	    radioButton[i].checked=false;
 1040: 	    if (pts == i && pts != "") {
 1041: 		radioButton[i].checked=true;
 1042: 	    }
 1043: 	}
 1044: 	updateSelect(formname,id);
 1045: 	formname["stores"+id].value = "0";
 1046:     }
 1047: 
 1048:     function writeBox(formname,id,pts) {
 1049: 	var gradeBox = formname["GD_BOX"+id];
 1050: 	if (checkSolved(formname,id) == 'update') {
 1051: 	    gradeBox.value = pts;
 1052: 	} else {
 1053: 	    var oldpts = formname["oldpts"+id].value;
 1054: 	    gradeBox.value = oldpts;
 1055: 	    var radioButton = formname["RADVAL"+id];
 1056: 	    for (var i=0; i<radioButton.length; i++) {
 1057: 		radioButton[i].checked=false;
 1058: 		if (i == oldpts) {
 1059: 		    radioButton[i].checked=true;
 1060: 		}
 1061: 	    }
 1062: 	}
 1063: 	formname["stores"+id].value = "0";
 1064: 	updateSelect(formname,id);
 1065: 	return;
 1066:     }
 1067: 
 1068:     function clearRadBox(formname,id) {
 1069: 	if (checkSolved(formname,id) == 'noupdate') {
 1070: 	    updateSelect(formname,id);
 1071: 	    return;
 1072: 	}
 1073: 	gradeSelect = formname["GD_SEL"+id];
 1074: 	for (var i=0; i<gradeSelect.length; i++) {
 1075: 	    if (gradeSelect[i].selected) {
 1076: 		var selectx=i;
 1077: 	    }
 1078: 	}
 1079: 	var stores = formname["stores"+id];
 1080: 	if (selectx == stores.value) { return };
 1081: 	var gradeBox = formname["GD_BOX"+id];
 1082: 	gradeBox.value = "";
 1083: 	var radioButton = formname["RADVAL"+id];
 1084: 	for (var i=0; i<radioButton.length; i++) {
 1085: 	    radioButton[i].checked=false;
 1086: 	}
 1087: 	stores.value = selectx;
 1088:     }
 1089: 
 1090:     function checkSolved(formname,id) {
 1091: 	if (formname["solved"+id].value == "correct_by_student" && formname.overRideScore.value == 'no') {
 1092: 	    var reply = confirm("This problem has been graded correct by the computer. Do you want to change the score?");
 1093: 	    if (!reply) {return "noupdate";}
 1094: 	    formname.overRideScore.value = 'yes';
 1095: 	}
 1096: 	return "update";
 1097:     }
 1098: 
 1099:     function updateSelect(formname,id) {
 1100: 	formname["GD_SEL"+id][0].selected = true;
 1101: 	return;
 1102:     }
 1103: 
 1104: //=========== Check that a point is assigned for all the parts  ============
 1105:     function checksubmit(formname,val,total,parttot) {
 1106: 	formname.gradeOpt.value = val;
 1107: 	if (val == "Save & Next") {
 1108: 	    for (i=0;i<=total;i++) {
 1109: 		for (j=0;j<parttot;j++) {
 1110: 		    var partid = formname["partid"+i+"_"+j].value;
 1111: 		    if (formname["GD_SEL"+i+"_"+partid][0].selected) {
 1112: 			var points = formname["GD_BOX"+i+"_"+partid].value;
 1113: 			if (points == "") {
 1114: 			    var name = formname["name"+i].value;
 1115: 			    var studentID = (name != '' ? name : formname["unamedom"+i].value);
 1116: 			    var resp = confirm("You did not assign a score for "+studentID+
 1117: 					       ", part "+partid+". Continue?");
 1118: 			    if (resp == false) {
 1119: 				formname["GD_BOX"+i+"_"+partid].focus();
 1120: 				return false;
 1121: 			    }
 1122: 			}
 1123: 		    }
 1124: 		    
 1125: 		}
 1126: 	    }
 1127: 	    
 1128: 	}
 1129: 	if (val == "Grade Student") {
 1130: 	    formname.showgrading.value = "yes";
 1131: 	    if (formname.Status.value == "") {
 1132: 		formname.Status.value = "Active";
 1133: 	    }
 1134: 	    formname.studentNo.value = total;
 1135: 	}
 1136: 	formname.submit();
 1137:     }
 1138: 
 1139: //======= Check that a score is assigned for all the problems (page/sequence grading only) =========
 1140:     function checkSubmitPage(formname,total) {
 1141: 	noscore = new Array(100);
 1142: 	var ptr = 0;
 1143: 	for (i=1;i<total;i++) {
 1144: 	    var partid = formname["q_"+i].value;
 1145: 	    if (formname["GD_SEL"+i+"_"+partid][0].selected) {
 1146: 		var points = formname["GD_BOX"+i+"_"+partid].value;
 1147: 		var status = formname["solved"+i+"_"+partid].value;
 1148: 		if (points == "" && status != "correct_by_student") {
 1149: 		    noscore[ptr] = i;
 1150: 		    ptr++;
 1151: 		}
 1152: 	    }
 1153: 	}
 1154: 	if (ptr != 0) {
 1155: 	    var sense = ptr == 1 ? ": " : "s: ";
 1156: 	    var prolist = "";
 1157: 	    if (ptr == 1) {
 1158: 		prolist = noscore[0];
 1159: 	    } else {
 1160: 		var i = 0;
 1161: 		while (i < ptr-1) {
 1162: 		    prolist += noscore[i]+", ";
 1163: 		    i++;
 1164: 		}
 1165: 		prolist += "and "+noscore[i];
 1166: 	    }
 1167: 	    var resp = confirm("You did not assign any score for the following problem"+sense+prolist+". Continue?");
 1168: 	    if (resp == false) {
 1169: 		return false;
 1170: 	    }
 1171: 	}
 1172: 
 1173: 	formname.submit();
 1174:     }
 1175: </script>
 1176: SUBJAVASCRIPT
 1177: }
 1178: 
 1179: #--- javascript for essay type problem --
 1180: sub sub_page_kw_js {
 1181:     my $request = shift;
 1182:     my $iconpath = $request->dir_config('lonIconsURL');
 1183:     &commonJSfunctions($request);
 1184: 
 1185:     my $inner_js_msg_central=<<INNERJS;
 1186:     <script text="text/javascript">
 1187:     function checkInput() {
 1188:       opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value);
 1189:       var nmsg   = opener.document.SCORE.savemsgN.value;
 1190:       var usrctr = document.msgcenter.usrctr.value;
 1191:       var newval = opener.document.SCORE["newmsg"+usrctr];
 1192:       newval.value = opener.checkEntities(document.msgcenter.newmsg.value);
 1193: 
 1194:       var msgchk = "";
 1195:       if (document.msgcenter.subchk.checked) {
 1196:          msgchk = "msgsub,";
 1197:       }
 1198:       var includemsg = 0;
 1199:       for (var i=1; i<=nmsg; i++) {
 1200:           var opnmsg = opener.document.SCORE["savemsg"+i];
 1201:           var frmmsg = document.msgcenter["msg"+i];
 1202:           opnmsg.value = opener.checkEntities(frmmsg.value);
 1203:           var showflg = opener.document.SCORE["shownOnce"+i];
 1204:           showflg.value = "1";
 1205:           var chkbox = document.msgcenter["msgn"+i];
 1206:           if (chkbox.checked) {
 1207:              msgchk += "savemsg"+i+",";
 1208:              includemsg = 1;
 1209:           }
 1210:       }
 1211:       if (document.msgcenter.newmsgchk.checked) {
 1212:          msgchk += "newmsg"+usrctr;
 1213:          includemsg = 1;
 1214:       }
 1215:       imgformname = opener.document.SCORE["mailicon"+usrctr];
 1216:       imgformname.src = "$iconpath/"+((includemsg) ? "mailto.gif" : "mailbkgrd.gif");
 1217:       var includemsg = opener.document.SCORE["includemsg"+usrctr];
 1218:       includemsg.value = msgchk;
 1219: 
 1220:       self.close()
 1221: 
 1222:     }
 1223:     </script>
 1224: INNERJS
 1225: 
 1226:     my $inner_js_highlight_central=<<INNERJS;
 1227:  <script type="text/javascript">
 1228:     function updateChoice(flag) {
 1229:       opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr);
 1230:       opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize);
 1231:       opener.document.SCORE.kwstyle.value = opener.radioSelection(document.hlCenter.kwdstyle);
 1232:       opener.document.SCORE.refresh.value = "on";
 1233:       if (opener.document.SCORE.keywords.value!=""){
 1234:          opener.document.SCORE.submit();
 1235:       }
 1236:       self.close()
 1237:     }
 1238: </script>
 1239: INNERJS
 1240: 
 1241:     my $start_page_msg_central = 
 1242:         &Apache::loncommon::start_page('Message Central',$inner_js_msg_central,
 1243: 				       {'js_ready'  => 1,
 1244: 					'only_body' => 1,
 1245: 					'bgcolor'   =>'#FFFFFF',});
 1246:     my $end_page_msg_central = 
 1247: 	&Apache::loncommon::end_page({'js_ready' => 1});
 1248: 
 1249: 
 1250:     my $start_page_highlight_central = 
 1251:         &Apache::loncommon::start_page('Highlight Central',
 1252: 				       $inner_js_highlight_central,
 1253: 				       {'js_ready'  => 1,
 1254: 					'only_body' => 1,
 1255: 					'bgcolor'   =>'#FFFFFF',});
 1256:     my $end_page_highlight_central = 
 1257: 	&Apache::loncommon::end_page({'js_ready' => 1});
 1258: 
 1259:     my $docopen=&Apache::lonhtmlcommon::javascript_docopen();
 1260:     $docopen=~s/^document\.//;
 1261:     $request->print(<<SUBJAVASCRIPT);
 1262: <script type="text/javascript" language="javascript">
 1263: 
 1264: //===================== Show list of keywords ====================
 1265:   function keywords(formname) {
 1266:     var nret = prompt("Keywords list, separated by a space. Add/delete to list if desired.",formname.keywords.value);
 1267:     if (nret==null) return;
 1268:     formname.keywords.value = nret;
 1269: 
 1270:     if (formname.keywords.value != "") {
 1271: 	formname.refresh.value = "on";
 1272: 	formname.submit();
 1273:     }
 1274:     return;
 1275:   }
 1276: 
 1277: //===================== Script to view submitted by ==================
 1278:   function viewSubmitter(submitter) {
 1279:     document.SCORE.refresh.value = "on";
 1280:     document.SCORE.NCT.value = "1";
 1281:     document.SCORE.unamedom0.value = submitter;
 1282:     document.SCORE.submit();
 1283:     return;
 1284:   }
 1285: 
 1286: //===================== Script to add keyword(s) ==================
 1287:   function getSel() {
 1288:     if (document.getSelection) txt = document.getSelection();
 1289:     else if (document.selection) txt = document.selection.createRange().text;
 1290:     else return;
 1291:     var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");
 1292:     if (cleantxt=="") {
 1293: 	alert("Please select a word or group of words from document and then click this link.");
 1294: 	return;
 1295:     }
 1296:     var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);
 1297:     if (nret==null) return;
 1298:     document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret;
 1299:     if (document.SCORE.keywords.value != "") {
 1300: 	document.SCORE.refresh.value = "on";
 1301: 	document.SCORE.submit();
 1302:     }
 1303:     return;
 1304:   }
 1305: 
 1306: //====================== Script for composing message ==============
 1307:    // preload images
 1308:    img1 = new Image();
 1309:    img1.src = "$iconpath/mailbkgrd.gif";
 1310:    img2 = new Image();
 1311:    img2.src = "$iconpath/mailto.gif";
 1312: 
 1313:   function msgCenter(msgform,usrctr,fullname) {
 1314:     var Nmsg  = msgform.savemsgN.value;
 1315:     savedMsgHeader(Nmsg,usrctr,fullname);
 1316:     var subject = msgform.msgsub.value;
 1317:     var msgchk = document.SCORE["includemsg"+usrctr].value;
 1318:     re = /msgsub/;
 1319:     var shwsel = "";
 1320:     if (re.test(msgchk)) { shwsel = "checked" }
 1321:     subject = (document.SCORE.shownSub.value == 0 ? checkEntities(subject) : subject);
 1322:     displaySubject(checkEntities(subject),shwsel);
 1323:     for (var i=1; i<=Nmsg; i++) {
 1324: 	var testmsg = "savemsg"+i+",";
 1325: 	re = new RegExp(testmsg,"g");
 1326: 	shwsel = "";
 1327: 	if (re.test(msgchk)) { shwsel = "checked" }
 1328: 	var message = document.SCORE["savemsg"+i].value;
 1329: 	message = (document.SCORE["shownOnce"+i].value == 0 ? checkEntities(message) : message);
 1330: 	displaySavedMsg(i,message,shwsel); //I do not get it. w/o checkEntities on saved messages,
 1331: 	                                   //any &lt; is already converted to <, etc. However, only once!!
 1332:     }
 1333:     newmsg = document.SCORE["newmsg"+usrctr].value;
 1334:     shwsel = "";
 1335:     re = /newmsg/;
 1336:     if (re.test(msgchk)) { shwsel = "checked" }
 1337:     newMsg(newmsg,shwsel);
 1338:     msgTail(); 
 1339:     return;
 1340:   }
 1341: 
 1342:   function checkEntities(strx) {
 1343:     if (strx.length == 0) return strx;
 1344:     var orgStr = ["&", "<", ">", '"']; 
 1345:     var newStr = ["&amp;", "&lt;", "&gt;", "&quot;"];
 1346:     var counter = 0;
 1347:     while (counter < 4) {
 1348: 	strx = strReplace(strx,orgStr[counter],newStr[counter]);
 1349: 	counter++;
 1350:     }
 1351:     return strx;
 1352:   }
 1353: 
 1354:   function strReplace(strx, orgStr, newStr) {
 1355:     return strx.split(orgStr).join(newStr);
 1356:   }
 1357: 
 1358:   function savedMsgHeader(Nmsg,usrctr,fullname) {
 1359:     var height = 70*Nmsg+250;
 1360:     var scrollbar = "no";
 1361:     if (height > 600) {
 1362: 	height = 600;
 1363: 	scrollbar = "yes";
 1364:     }
 1365:     var xpos = (screen.width-600)/2;
 1366:     xpos = (xpos < 0) ? '0' : xpos;
 1367:     var ypos = (screen.height-height)/2-30;
 1368:     ypos = (ypos < 0) ? '0' : ypos;
 1369: 
 1370:     pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars='+scrollbar+',screenx='+xpos+',screeny='+ypos+',width=600,height='+height);
 1371:     pWin.focus();
 1372:     pDoc = pWin.document;
 1373:     pDoc.$docopen;
 1374:     pDoc.write('$start_page_msg_central');
 1375: 
 1376:     pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");
 1377:     pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");
 1378:     pDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Compose Message for \"+fullname+\"</span></h3><br /><br />");
 1379: 
 1380:     pDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
 1381:     pDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
 1382:     pDoc.write("<td><b>Type</b></td><td><b>Include</b></td><td><b>Message</td></tr>");
 1383: }
 1384:     function displaySubject(msg,shwsel) {
 1385:     pDoc = pWin.document;
 1386:     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
 1387:     pDoc.write("<td>Subject</td>");
 1388:     pDoc.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"></td>");
 1389:     pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"60\\" maxlength=\\"80\\"></td></tr>");
 1390: }
 1391: 
 1392:   function displaySavedMsg(ctr,msg,shwsel) {
 1393:     pDoc = pWin.document;
 1394:     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
 1395:     pDoc.write("<td align=\\"center\\">"+ctr+"</td>");
 1396:     pDoc.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"></td>");
 1397:     pDoc.write("<td><textarea name=\\"msg"+ctr+"\\" cols=\\"60\\" rows=\\"3\\">"+msg+"</textarea></td></tr>");
 1398: }
 1399: 
 1400:   function newMsg(newmsg,shwsel) {
 1401:     pDoc = pWin.document;
 1402:     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
 1403:     pDoc.write("<td align=\\"center\\">New</td>");
 1404:     pDoc.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"></td>");
 1405:     pDoc.write("<td><textarea name=\\"newmsg\\" cols=\\"60\\" rows=\\"3\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" >"+newmsg+"</textarea></td></tr>");
 1406: }
 1407: 
 1408:   function msgTail() {
 1409:     pDoc = pWin.document;
 1410:     pDoc.write("</table>");
 1411:     pDoc.write("</td></tr></table>&nbsp;");
 1412:     pDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");
 1413:     pDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");
 1414:     pDoc.write("</form>");
 1415:     pDoc.write('$end_page_msg_central');
 1416:     pDoc.close();
 1417: }
 1418: 
 1419: //====================== Script for keyword highlight options ==============
 1420:   function kwhighlight() {
 1421:     var kwclr    = document.SCORE.kwclr.value;
 1422:     var kwsize   = document.SCORE.kwsize.value;
 1423:     var kwstyle  = document.SCORE.kwstyle.value;
 1424:     var redsel = "";
 1425:     var grnsel = "";
 1426:     var blusel = "";
 1427:     if (kwclr=="red")   {var redsel="checked"};
 1428:     if (kwclr=="green") {var grnsel="checked"};
 1429:     if (kwclr=="blue")  {var blusel="checked"};
 1430:     var sznsel = "";
 1431:     var sz1sel = "";
 1432:     var sz2sel = "";
 1433:     if (kwsize=="0")  {var sznsel="checked"};
 1434:     if (kwsize=="+1") {var sz1sel="checked"};
 1435:     if (kwsize=="+2") {var sz2sel="checked"};
 1436:     var synsel = "";
 1437:     var syisel = "";
 1438:     var sybsel = "";
 1439:     if (kwstyle=="")    {var synsel="checked"};
 1440:     if (kwstyle=="<i>") {var syisel="checked"};
 1441:     if (kwstyle=="<b>") {var sybsel="checked"};
 1442:     highlightCentral();
 1443:     highlightbody('red','red',redsel,'0','normal',sznsel,'','normal',synsel);
 1444:     highlightbody('green','green',grnsel,'+1','+1',sz1sel,'<i>','italic',syisel);
 1445:     highlightbody('blue','blue',blusel,'+2','+2',sz2sel,'<b>','bold',sybsel);
 1446:     highlightend();
 1447:     return;
 1448:   }
 1449: 
 1450:   function highlightCentral() {
 1451: //    if (window.hwdWin) window.hwdWin.close();
 1452:     var xpos = (screen.width-400)/2;
 1453:     xpos = (xpos < 0) ? '0' : xpos;
 1454:     var ypos = (screen.height-330)/2-30;
 1455:     ypos = (ypos < 0) ? '0' : ypos;
 1456: 
 1457:     hwdWin = window.open('', 'KeywordHighlightCentral', 'resizeable=yes,toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx='+xpos+',screeny='+ypos);
 1458:     hwdWin.focus();
 1459:     var hDoc = hwdWin.document;
 1460:     hDoc.$docopen;
 1461:     hDoc.write('$start_page_highlight_central');
 1462:     hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");
 1463:     hDoc.write("<h3><span class=\\"LC_info\\">&nbsp;Keyword Highlight Options</span></h3><br /><br />");
 1464: 
 1465:     hDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
 1466:     hDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
 1467:     hDoc.write("<td><b>Text Color</b></td><td><b>Font Size</b></td><td><b>Font Style</td></tr>");
 1468:   }
 1469: 
 1470:   function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) { 
 1471:     var hDoc = hwdWin.document;
 1472:     hDoc.write("<tr bgcolor=\\"#ffffdd\\">");
 1473:     hDoc.write("<td align=\\"left\\">");
 1474:     hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+">&nbsp;"+clrtxt+"</td>");
 1475:     hDoc.write("<td align=\\"left\\">");
 1476:     hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+">&nbsp;"+sztxt+"</td>");
 1477:     hDoc.write("<td align=\\"left\\">");
 1478:     hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+">&nbsp;"+sytxt+"</td>");
 1479:     hDoc.write("</tr>");
 1480:   }
 1481: 
 1482:   function highlightend() { 
 1483:     var hDoc = hwdWin.document;
 1484:     hDoc.write("</table>");
 1485:     hDoc.write("</td></tr></table>&nbsp;");
 1486:     hDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:updateChoice(1)\\">&nbsp;&nbsp;");
 1487:     hDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");
 1488:     hDoc.write("</form>");
 1489:     hDoc.write('$end_page_highlight_central');
 1490:     hDoc.close();
 1491:   }
 1492: 
 1493: </script>
 1494: SUBJAVASCRIPT
 1495: }
 1496: 
 1497: sub get_increment {
 1498:     my $increment = $env{'form.increment'};
 1499:     if ($increment != 1 && $increment != .5 && $increment != .25 &&
 1500:         $increment != .1) {
 1501:         $increment = 1;
 1502:     }
 1503:     return $increment;
 1504: }
 1505: 
 1506: #--- displays the grading box, used in essay type problem and grading by page/sequence
 1507: sub gradeBox {
 1508:     my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;
 1509:     my $checkIcon = '<img alt="'.&mt('Check Mark').
 1510: 	'" src="'.$request->dir_config('lonIconsURL').
 1511: 	'/check.gif" height="16" border="0" />';
 1512:     my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);
 1513:     my $wgtmsg = ($wgt > 0 ? '(problem weight)' : 
 1514: 		  '<span class="LC_info">problem weight assigned by computer</span>');
 1515:     $wgt       = ($wgt > 0 ? $wgt : '1');
 1516:     my $score  = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
 1517: 		  '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));
 1518:     my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
 1519:     my $display_part=&get_display_part($partid,$symb);
 1520:     my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
 1521: 				       [$partid]);
 1522:     my $aggtries = $$record{'resource.'.$partid.'.tries'};
 1523:     if ($last_resets{$partid}) {
 1524:         $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
 1525:     }
 1526:     $result.='<table border="0"><tr><td>'.
 1527: 	'<b>Part: </b>'.$display_part.' <b>Points: </b></td><td>'."\n";
 1528:     my $ctr = 0;
 1529:     my $thisweight = 0;
 1530:     my $increment = &get_increment();
 1531:     $result.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across
 1532:     while ($thisweight<=$wgt) {
 1533: 	$result.= '<td><span style="white-space: nowrap;"><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
 1534: 	    'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.
 1535: 	    $thisweight.')" value="'.$thisweight.'" '.
 1536: 	    ($score eq $thisweight ? 'checked="checked"':'').' /> '.$thisweight."</label></span></td>\n";
 1537: 	$result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
 1538:         $thisweight += $increment;
 1539: 	$ctr++;
 1540:     }
 1541:     $result.='</tr></table>';
 1542:     $result.='</td><td>&nbsp;<b>or</b>&nbsp;</td>'."\n";
 1543:     $result.='<td><input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
 1544: 	($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
 1545: 	'onChange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','.
 1546: 	$wgt.')" /></td>'."\n";
 1547:     $result.='<td>/'.$wgt.' '.$wgtmsg.
 1548: 	($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? '&nbsp;'.$checkIcon : '').
 1549: 	' </td><td>'."\n";
 1550:     $result.='<select name="GD_SEL'.$counter.'_'.$partid.'" '.
 1551: 	'onChange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";
 1552:     if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
 1553: 	$result.='<option></option>'.
 1554: 	    '<option selected="selected">excused</option>';
 1555:     } else {
 1556: 	$result.='<option selected="selected"></option>'.
 1557: 	    '<option>excused</option>';
 1558:     }
 1559:     $result.='<option>reset status</option></select>'."\n";
 1560:     $result.="&nbsp;&nbsp;\n";
 1561:     $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n".
 1562: 	'<input type="hidden" name="oldpts'.$counter.'_'.$partid.'" value="'.$score.'" />'."\n".
 1563: 	'<input type="hidden" name="solved'.$counter.'_'.$partid.'" value="'.
 1564: 	$$record{'resource.'.$partid.'.solved'}.'" />'."\n".
 1565:         '<input type="hidden" name="totaltries'.$counter.'_'.$partid.'" value="'.
 1566:         $$record{'resource.'.$partid.'.tries'}.'" />'."\n".
 1567:         '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.
 1568:         $aggtries.'" />'."\n";
 1569:     $result.='</td></tr></table>'."\n";
 1570:     $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record);
 1571:     return $result;
 1572: }
 1573: 
 1574: sub handback_box {
 1575:     my ($symb,$uname,$udom,$counter,$partid,$record) = @_;
 1576:     my ($partlist,$handgrade,$responseType) = &response_type($symb);
 1577:     my (@respids);
 1578:      my @part_response_id = &flatten_responseType($responseType);
 1579:     foreach my $part_response_id (@part_response_id) {
 1580:     	my ($part,$resp) = @{ $part_response_id };
 1581:         if ($part eq $partid) {
 1582:             push(@respids,$resp);
 1583:         }
 1584:     }
 1585:     my $result;
 1586:     foreach my $respid (@respids) {
 1587: 	my $prefix = $counter.'_'.$partid.'_'.$respid.'_';
 1588: 	my $files=&get_submitted_files($udom,$uname,$partid,$respid,$record);
 1589: 	next if (!@$files);
 1590: 	my $file_counter = 1;
 1591: 	foreach my $file (@$files) {
 1592: 	    if ($file =~ /\/portfolio\//) {
 1593:     	        my ($file_path, $file_disp) = ($file =~ m|(.+/)(.+)$|);
 1594:     	        my ($name,$version,$ext) = &file_name_version_ext($file_disp);
 1595:     	        $file_disp = "$name.$ext";
 1596:     	        $file = $file_path.$file_disp;
 1597:     	        $result.=&mt('Return commented version of [_1] to student.',
 1598:     			 '<span class="LC_filename">'.$file_disp.'</span>');
 1599:     	        $result.='<input type="file"   name="'.$prefix.'returndoc'.$file_counter.'" />'."\n";
 1600:     	        $result.='<input type="hidden" name="'.$prefix.'origdoc'.$file_counter.'" value="'.$file.'" /><br />';
 1601:     	        $result.='(File will be uploaded when you click on Save & Next below.)<br />';
 1602:     	        $file_counter++;
 1603: 	    }
 1604: 	}
 1605:     }
 1606:     return $result;    
 1607: }
 1608: 
 1609: sub show_problem {
 1610:     my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode,$form) = @_;
 1611:     my $rendered;
 1612:     my %form = ((ref($form) eq 'HASH')? %{$form} : ());
 1613:     &Apache::lonxml::remember_problem_counter();
 1614:     if ($mode eq 'both' or $mode eq 'text') {
 1615: 	$rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,
 1616: 						       $env{'request.course.id'},
 1617: 						       undef,\%form);
 1618:     }
 1619:     if ($removeform) {
 1620: 	$rendered=~s|<form(.*?)>||g;
 1621: 	$rendered=~s|</form>||g;
 1622: 	$rendered=~s|(<input[^>]*name\s*=\s*"?)(\w+)("?)|$1would_have_been_$2$3|g;
 1623:     }
 1624:     my $companswer;
 1625:     if ($mode eq 'both' or $mode eq 'answer') {
 1626: 	&Apache::lonxml::restore_problem_counter();
 1627: 	$companswer=
 1628: 	    &Apache::loncommon::get_student_answers($symb,$uname,$udom,
 1629: 						    $env{'request.course.id'},
 1630: 						    %form);
 1631:     }
 1632:     if ($removeform) {
 1633: 	$companswer=~s|<form(.*?)>||g;
 1634: 	$companswer=~s|</form>||g;
 1635: 	$companswer=~s|name="submit"|name="would_have_been_submit"|g;
 1636:     }
 1637:     my $result.='<table border="0" width="100%"><tr><td bgcolor="#777777">';
 1638:     $result.='<table border="0" width="100%">';
 1639:     if ($viewon) {
 1640: 	$result.='<tr><td bgcolor="#e6ffff"><b> ';
 1641: 	if ($mode eq 'both' or $mode eq 'text') {
 1642: 	    $result.='View of the problem - ';
 1643: 	} else {
 1644: 	    $result.='Correct answer: ';
 1645: 	}
 1646: 	$result.=$env{'form.fullname'}.'</b></td></tr>';
 1647:     }
 1648:     if ($mode eq 'both') {
 1649: 	$result.='<tr><td bgcolor="#ffffff">'.$rendered.'<br />';
 1650: 	$result.='<b>Correct answer:</b><br />'.$companswer;
 1651:     } elsif ($mode eq 'text') {
 1652: 	$result.='<tr><td bgcolor="#ffffff">'.$rendered;
 1653:     } elsif ($mode eq 'answer') {
 1654: 	$result.='<tr><td bgcolor="#ffffff">'.$companswer;
 1655:     }
 1656:     $result.='</td></tr></table>';
 1657:     $result.='</td></tr></table><br />';
 1658:     return $result;
 1659: }
 1660: 
 1661: sub files_exist {
 1662:     my ($r, $symb) = @_;
 1663:     my @students = &Apache::loncommon::get_env_multiple('form.stuinfo');
 1664: 
 1665:     foreach my $student (@students) {
 1666:         my ($uname,$udom,$fullname) = split(/:/,$student);
 1667:         my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
 1668: 					      $udom,$uname);
 1669:         my ($string,$timestamp)= &get_last_submission(\%record);
 1670:         foreach my $submission (@$string) {
 1671:             my ($partid,$respid) =
 1672: 		($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
 1673:             my $files=&get_submitted_files($udom,$uname,$partid,$respid,
 1674: 					   \%record);
 1675:             return 1 if (@$files);
 1676:         }
 1677:     }
 1678:     return 0;
 1679: }
 1680: 
 1681: sub download_all_link {
 1682:     my ($r,$symb) = @_;
 1683:     my $all_students = 
 1684: 	join("\n", &Apache::loncommon::get_env_multiple('form.stuinfo'));
 1685: 
 1686:     my $parts =
 1687: 	join("\n",&Apache::loncommon::get_env_multiple('form.vPart'));
 1688: 
 1689:     my $identifier = &Apache::loncommon::get_cgi_id();
 1690:     &Apache::lonnet::appenv('cgi.'.$identifier.'.students' => $all_students,
 1691:                             'cgi.'.$identifier.'.symb' => $symb,
 1692:                             'cgi.'.$identifier.'.parts' => $parts,);
 1693:     $r->print('<a href="/cgi-bin/multidownload.pl?'.$identifier.'">'.
 1694: 	      &mt('Download All Submitted Documents').'</a>');
 1695:     return
 1696: }
 1697: 
 1698: sub build_section_inputs {
 1699:     my $section_inputs;
 1700:     if ($env{'form.section'} eq '') {
 1701:         $section_inputs .= '<input type="hidden" name="section" value="all" />'."\n";
 1702:     } else {
 1703:         my @sections = &Apache::loncommon::get_env_multiple('form.section');
 1704:         foreach my $section (@sections) {
 1705:             $section_inputs .= '<input type="hidden" name="section" value="'.$section.'" />'."\n";
 1706:         }
 1707:     }
 1708:     return $section_inputs;
 1709: }
 1710: 
 1711: # --------------------------- show submissions of a student, option to grade 
 1712: sub submission {
 1713:     my ($request,$counter,$total) = @_;
 1714: 
 1715:     my ($uname,$udom)     = ($env{'form.student'},$env{'form.userdom'});
 1716:     $udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student?
 1717:     my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
 1718:     $env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq '';
 1719: 
 1720:     my $symb = &get_symb($request); 
 1721:     if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; }
 1722: 
 1723:     if (!&canview($usec)) {
 1724: 	$request->print('<span class="LC_warning">Unable to view requested student.('.
 1725: 			$uname.':'.$udom.' in section '.$usec.' in course id '.
 1726: 			$env{'request.course.id'}.')</span>');
 1727: 	$request->print(&show_grading_menu_form($symb));
 1728: 	return;
 1729:     }
 1730: 
 1731:     if (!$env{'form.lastSub'}) { $env{'form.lastSub'} = 'datesub'; }
 1732:     if (!$env{'form.vProb'}) { $env{'form.vProb'} = 'yes'; }
 1733:     if (!$env{'form.vAns'}) { $env{'form.vAns'} = 'yes'; }
 1734:     my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
 1735:     my $checkIcon = '<img alt="'.&mt('Check Mark').
 1736: 	'" src="'.$request->dir_config('lonIconsURL').
 1737: 	'/check.gif" height="16" border="0" />';
 1738: 
 1739:     my %old_essays;
 1740:     # header info
 1741:     if ($counter == 0) {
 1742: 	&sub_page_js($request);
 1743: 	&sub_page_kw_js($request) if ($env{'form.handgrade'} eq 'yes');
 1744: 	$env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 
 1745: 	    &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
 1746: 	if ($env{'form.handgrade'} eq 'yes' && &files_exist($request, $symb)) {
 1747: 	    &download_all_link($request, $symb);
 1748: 	}
 1749: 	$request->print('<h3>&nbsp;<span class="LC_info">Submission Record</span></h3>'."\n".
 1750: 			'<h4>&nbsp;<b>Resource: </b>'.$env{'form.probTitle'}.'</h4>'."\n");
 1751: 
 1752: 	if ($env{'form.handgrade'} eq 'no') {
 1753: 	    my $checkMark='<br /><br />&nbsp;<b>Note:</b> Part(s) graded correct by the computer is marked with a '.
 1754: 		$checkIcon.' symbol.'."\n";
 1755: 	    $request->print($checkMark);
 1756: 	}
 1757: 
 1758: 	# option to display problem, only once else it cause problems 
 1759:         # with the form later since the problem has a form.
 1760: 	if ($env{'form.vProb'} eq 'yes' or $env{'form.vAns'} eq 'yes') {
 1761: 	    my $mode;
 1762: 	    if ($env{'form.vProb'} eq 'yes' && $env{'form.vAns'} eq 'yes') {
 1763: 		$mode='both';
 1764: 	    } elsif ($env{'form.vProb'} eq 'yes') {
 1765: 		$mode='text';
 1766: 	    } elsif ($env{'form.vAns'} eq 'yes') {
 1767: 		$mode='answer';
 1768: 	    }
 1769: 	    &Apache::lonxml::clear_problem_counter();
 1770: 	    $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode));
 1771: 	}
 1772: 	
 1773: 	# kwclr is the only variable that is guaranteed to be non blank 
 1774:         # if this subroutine has been called once.
 1775: 	my %keyhash = ();
 1776: 	if ($env{'form.kwclr'} eq '' && $env{'form.handgrade'} eq 'yes') {
 1777: 	    %keyhash = &Apache::lonnet::dump('nohist_handgrade',
 1778: 					     $env{'course.'.$env{'request.course.id'}.'.domain'},
 1779: 					     $env{'course.'.$env{'request.course.id'}.'.num'});
 1780: 
 1781: 	    my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
 1782: 	    $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
 1783: 	    $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
 1784: 	    $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
 1785: 	    $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
 1786: 	    $env{'form.msgsub'}   = $keyhash{$symb.'_subject'} ne '' ? 
 1787: 		$keyhash{$symb.'_subject'} : $env{'form.probTitle'};
 1788: 	    $env{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0';
 1789: 	}
 1790: 	my $overRideScore = $env{'form.overRideScore'} eq '' ? 'no' : $env{'form.overRideScore'};
 1791: 	$request->print('<form action="/adm/grades" method="post" name="SCORE" enctype="multipart/form-data">'."\n".
 1792: 			'<input type="hidden" name="command"    value="handgrade" />'."\n".
 1793: 			'<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
 1794: 			'<input type="hidden" name="Status"     value="'.$env{'form.Status'}.'" />'."\n".
 1795: 			'<input type="hidden" name="overRideScore" value="'.$overRideScore.'" />'."\n".
 1796: 			'<input type="hidden" name="probTitle"  value="'.$env{'form.probTitle'}.'" />'."\n".
 1797: 			'<input type="hidden" name="refresh"    value="off" />'."\n".
 1798: 			'<input type="hidden" name="studentNo"  value="" />'."\n".
 1799: 			'<input type="hidden" name="gradeOpt"   value="" />'."\n".
 1800: 			'<input type="hidden" name="symb"       value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 1801: 			'<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" />'."\n".
 1802: 			'<input type="hidden" name="vProb"      value="'.$env{'form.vProb'}.'" />'."\n".
 1803: 			'<input type="hidden" name="vAns"       value="'.$env{'form.vAns'}.'" />'."\n".
 1804: 			'<input type="hidden" name="lastSub"    value="'.$env{'form.lastSub'}.'" />'."\n".
 1805: 			&build_section_inputs().
 1806: 			'<input type="hidden" name="submitonly" value="'.$env{'form.submitonly'}.'" />'."\n".
 1807: 			'<input type="hidden" name="handgrade"  value="'.$env{'form.handgrade'}.'" />'."\n".
 1808: 			'<input type="hidden" name="NCT"'.
 1809: 			' value="'.($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : $total+1).'" />'."\n");
 1810: 	if ($env{'form.handgrade'} eq 'yes') {
 1811: 	    $request->print('<input type="hidden" name="keywords" value="'.$env{'form.keywords'}.'" />'."\n".
 1812: 			    '<input type="hidden" name="kwclr"    value="'.$env{'form.kwclr'}.'" />'."\n".
 1813: 			    '<input type="hidden" name="kwsize"   value="'.$env{'form.kwsize'}.'" />'."\n".
 1814: 			    '<input type="hidden" name="kwstyle"  value="'.$env{'form.kwstyle'}.'" />'."\n".
 1815: 			    '<input type="hidden" name="msgsub"   value="'.$env{'form.msgsub'}.'" />'."\n".
 1816: 			    '<input type="hidden" name="shownSub" value="0" />'."\n".
 1817: 			    '<input type="hidden" name="savemsgN" value="'.$env{'form.savemsgN'}.'" />'."\n");
 1818: 	    foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) {
 1819: 		$request->print('<input type="hidden" name="vPart" value="'.$partid.'" />'."\n");
 1820: 	    }
 1821: 	}
 1822: 	
 1823: 	my ($cts,$prnmsg) = (1,'');
 1824: 	while ($cts <= $env{'form.savemsgN'}) {
 1825: 	    $prnmsg.='<input type="hidden" name="savemsg'.$cts.'" value="'.
 1826: 		(!exists($keyhash{$symb.'_savemsg'.$cts}) ? 
 1827: 		 &Apache::lonfeedback::clear_out_html($env{'form.savemsg'.$cts}) :
 1828: 		 &Apache::lonfeedback::clear_out_html($keyhash{$symb.'_savemsg'.$cts})).
 1829: 		'" />'."\n".
 1830: 		'<input type="hidden" name="shownOnce'.$cts.'" value="0" />'."\n";
 1831: 	    $cts++;
 1832: 	}
 1833: 	$request->print($prnmsg);
 1834: 
 1835: 	if ($env{'form.handgrade'} eq 'yes' && $env{'form.showgrading'} eq 'yes') {
 1836: #
 1837: # Print out the keyword options line
 1838: #
 1839: 	    $request->print(<<KEYWORDS);
 1840: &nbsp;<b>Keyword Options:</b>&nbsp;
 1841: <a href="javascript:keywords(document.SCORE);" target="_self">List</a>&nbsp; &nbsp;
 1842: <a href="#" onMouseDown="javascript:getSel(); return false"
 1843:  CLASS="page">Paste Selection to List</a>&nbsp; &nbsp;
 1844: <a href="javascript:kwhighlight();" target="_self">Highlight Attribute</a><br /><br />
 1845: KEYWORDS
 1846: #
 1847: # Load the other essays for similarity check
 1848: #
 1849:             my (undef,undef,$essayurl) = &Apache::lonnet::decode_symb($symb);
 1850: 	    my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);
 1851: 	    $apath=&escape($apath);
 1852: 	    $apath=~s/\W/\_/gs;
 1853: 	    %old_essays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
 1854:         }
 1855:     }
 1856: 
 1857:     if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {
 1858: 	$request->print('<br /><br /><br />') if ($counter > 0);
 1859: 	my $mode;
 1860: 	if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {
 1861: 	    $mode='both';
 1862: 	} elsif ($env{'form.vProb'} eq 'all' ) {
 1863: 	    $mode='text';
 1864: 	} elsif ($env{'form.vAns'} eq 'all') {
 1865: 	    $mode='answer';
 1866: 	}
 1867: 	&Apache::lonxml::clear_problem_counter();
 1868: 	$request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode));
 1869:     }
 1870: 
 1871:     my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
 1872:     my ($partlist,$handgrade,$responseType) = &response_type($symb);
 1873: 
 1874:     # Display student info
 1875:     $request->print(($counter == 0 ? '' : '<br />'));
 1876:     my $result='<table border="0" width="100%"><tr><td bgcolor="#777777">'."\n".
 1877: 	'<table border="0" width="100%"><tr bgcolor="#edffff"><td>'."\n";
 1878: 
 1879:     $result.='<b>Fullname: </b>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'<br />'."\n";
 1880:     $result.='<input type="hidden" name="name'.$counter.
 1881: 	'" value="'.$env{'form.fullname'}.'" />'."\n";
 1882: 
 1883:     # If any part of the problem is an essay-response (handgraded), then check for collaborators
 1884:     my @col_fullnames;
 1885:     my ($classlist,$fullname);
 1886:     if ($env{'form.handgrade'} eq 'yes') {
 1887: 	($classlist,undef,$fullname) = &getclasslist('all','0');
 1888: 	for (keys (%$handgrade)) {
 1889: 	    my $ncol = &Apache::lonnet::EXT('resource.'.$_.
 1890: 					    '.maxcollaborators',
 1891:                                             $symb,$udom,$uname);
 1892: 	    next if ($ncol <= 0);
 1893:             s/\_/\./g;
 1894:             next if ($record{'resource.'.$_.'.collaborators'} eq '');
 1895:             my @goodcollaborators = ();
 1896:             my @badcollaborators  = ();
 1897: 	    foreach (split(/,?\s+/,$record{'resource.'.$_.'.collaborators'})) { 
 1898: 		$_ =~ s/[\$\^\(\)]//g;
 1899: 		next if ($_ eq '');
 1900: 		my ($co_name,$co_dom) = split /\@|:/,$_;
 1901: 		$co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i);
 1902: 		next if ($co_name eq $uname && $co_dom eq $udom);
 1903: 		# Doing this grep allows 'fuzzy' specification
 1904: 		my @Matches = grep /^$co_name:$co_dom$/i,keys %$classlist;
 1905: 		if (! scalar(@Matches)) {
 1906: 		    push @badcollaborators,$_;
 1907: 		} else {
 1908: 		    push @goodcollaborators, @Matches;
 1909: 		}
 1910: 	    }
 1911:             if (scalar(@goodcollaborators) != 0) {
 1912:                 $result.='<b>Collaborators: </b>';
 1913:                 foreach (@goodcollaborators) {
 1914: 		    my ($lastname,$givenn) = split(/,/,$$fullname{$_});
 1915: 		    push @col_fullnames, $givenn.' '.$lastname;
 1916: 		    $result.=$$fullname{$_}.'&nbsp; &nbsp; &nbsp;';
 1917: 		}
 1918:                 $result.='<br />'."\n";
 1919: 		my ($part)=split(/\./,$_);
 1920: 		$result.='<input type="hidden" name="collaborator'.$counter.
 1921: 		    '" value="'.$part.':'.(join ':',@goodcollaborators).'" />'.
 1922: 		    "\n";
 1923: 	    }
 1924: 	    if (scalar(@badcollaborators) > 0) {
 1925: 		$result.='<table border="0"><tr bgcolor="#ffbbbb"><td>';
 1926: 		$result.='This student has submitted ';
 1927: 		$result.=(scalar(@badcollaborators) == 1) ? 'an invalid collaborator' : 'invalid collaborators';
 1928: 		$result .= ': '.join(', ',@badcollaborators);
 1929: 		$result .= '</td></tr></table>';
 1930: 	    }         
 1931: 	    if (scalar(@badcollaborators > $ncol)) {
 1932: 		$result .= '<table border="0"><tr bgcolor="#ffbbbb"><td>';
 1933: 		$result .= 'This student has submitted too many '.
 1934: 		    'collaborators.  Maximum is '.$ncol.'.';
 1935: 		$result .= '</td></tr></table>';
 1936: 	    }
 1937: 	}
 1938:     }
 1939:     $request->print($result."\n");
 1940: 
 1941:     # print student answer/submission
 1942:     # Options are (1) Handgaded submission only
 1943:     #             (2) Last submission, includes submission that is not handgraded 
 1944:     #                  (for multi-response type part)
 1945:     #             (3) Last submission plus the parts info
 1946:     #             (4) The whole record for this student
 1947:     if ($env{'form.lastSub'} =~ /^(lastonly|hdgrade)$/) {
 1948: 	my ($string,$timestamp)= &get_last_submission(\%record);
 1949: 	my $lastsubonly=''.
 1950: 	    ($$timestamp eq '' ? '' : '<b>Date Submitted:</b> '.
 1951: 	     $$timestamp)."</td></tr>\n";
 1952: 	if ($$timestamp eq '') {
 1953: 	    $lastsubonly.='<tr><td bgcolor="#ffffe6">'.$$string[0]; 
 1954: 	} else {
 1955: 	    my %seenparts;
 1956: 	    my @part_response_id = &flatten_responseType($responseType);
 1957: 	    foreach my $part (@part_response_id) {
 1958: 		next if ($env{'form.lastSub'} eq 'hdgrade' 
 1959: 			 && $$handgrade{$$part[0].'_'.$$part[1]} ne 'yes');
 1960: 
 1961: 		my ($partid,$respid) = @{ $part };
 1962: 		my $display_part=&get_display_part($partid,$symb);
 1963: 		if ($env{"form.$uname:$udom:$partid:submitted_by"}) {
 1964: 		    if (exists($seenparts{$partid})) { next; }
 1965: 		    $seenparts{$partid}=1;
 1966: 		    my $submitby='<b>Part:</b> '.$display_part.
 1967: 			' <b>Collaborative submission by:</b> '.
 1968: 			'<a href="javascript:viewSubmitter(\''.
 1969: 			$env{"form.$uname:$udom:$partid:submitted_by"}.
 1970: 			'\');" target="_self">'.
 1971: 			$$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a><br />';
 1972: 		    $request->print($submitby);
 1973: 		    next;
 1974: 		}
 1975: 		my $responsetype = $responseType->{$partid}->{$respid};
 1976: 		if (!exists($record{"resource.$partid.$respid.submission"})) {
 1977: 		    $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '.
 1978: 			$display_part.' <span class="LC_internal_info">( ID '.$respid.
 1979: 			' )</span>&nbsp; &nbsp;'.
 1980: 			'<span class="LC_warning">Nothing submitted - no attempts</span><br /><br />';
 1981: 		    next;
 1982: 		}
 1983: 		foreach (@$string) {
 1984: 		    my ($partid,$respid) = /^resource\.([^\.]*)\.([^\.]*)\.submission/;
 1985: 		    if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }
 1986: 		    my ($ressub,$subval) = split(/:/,$_,2);
 1987: 		    # Similarity check
 1988: 		    my $similar='';
 1989: 		    if($env{'form.checkPlag'}){
 1990: 			my ($oname,$odom,$ocrsid,$oessay,$osim)=
 1991: 			    &most_similar($uname,$udom,$subval,\%old_essays);
 1992: 			if ($osim) {
 1993: 			    $osim=int($osim*100.0);
 1994: 			    my %old_course_desc = 
 1995: 				&Apache::lonnet::coursedescription($ocrsid,
 1996: 								   {'one_time' => 1});
 1997: 
 1998: 			    $similar="<hr /><h3><span class=\"LC_warning\">".
 1999: 				&mt('Essay is [_1]% similar to an essay by [_2] ([_3]:[_4]) in course [_5] (course id [_6]:[_7])',
 2000: 				    $osim,
 2001: 				    &Apache::loncommon::plainname($oname,$odom),
 2002: 				    $oname,$odom,
 2003: 				    $old_course_desc{'description'},
 2004: 				    $old_course_desc{'num'},
 2005: 				    $old_course_desc{'domain'}).
 2006: 				'</span></h3><blockquote><i>'.
 2007: 				&keywords_highlight($oessay).
 2008: 				'</i></blockquote><hr />';
 2009: 			}
 2010: 		    }
 2011: 		    my $order=&get_order($partid,$respid,$symb,$uname,$udom);
 2012: 		    if ($env{'form.lastSub'} eq 'lastonly' || 
 2013: 			($env{'form.lastSub'} eq 'hdgrade' && 
 2014: 			 $$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) {
 2015: 			my $display_part=&get_display_part($partid,$symb);
 2016: 			$lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '.
 2017: 			    $display_part.' <span class="LC_internal_info">( ID '.$respid.
 2018: 			    ' )</span>&nbsp; &nbsp;';
 2019: 			my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
 2020: 			if (@$files) {
 2021: 			    $lastsubonly.='<br /><span class="LC_warning">Like all files provided by users, this file may contain virusses</span><br />';
 2022: 			    my $file_counter = 0;
 2023: 			    foreach my $file (@$files) {
 2024: 			        $file_counter ++;
 2025: 				&Apache::lonnet::allowuploaded('/adm/grades',$file);
 2026: 				$lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border=0"> '.$file.'</a>';
 2027: 			    }
 2028: 			    $lastsubonly.='<br />';
 2029: 			}
 2030: 			$lastsubonly.='<b>Submitted Answer: </b>'.
 2031: 			    &cleanRecord($subval,$responsetype,$symb,$partid,
 2032: 					 $respid,\%record,$order);
 2033: 			if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
 2034: 		    }
 2035: 		}
 2036: 	    }
 2037: 	}
 2038: 	$lastsubonly.='</td></tr><tr bgcolor="#ffffff"><td>'."\n";
 2039: 	$request->print($lastsubonly);
 2040:     } elsif ($env{'form.lastSub'} eq 'datesub') {
 2041: 	my (undef,$responseType,undef,$parts) = &showResourceInfo($symb);
 2042: 	$request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom));
 2043:     } elsif ($env{'form.lastSub'} =~ /^(last|all)$/) {
 2044: 	$request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,
 2045: 								 $env{'request.course.id'},
 2046: 								 $last,'.submission',
 2047: 								 'Apache::grades::keywords_highlight'));
 2048:     }
 2049: 
 2050:     $request->print('<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'
 2051: 	.$udom.'" />'."\n");
 2052:     
 2053:     # return if view submission with no grading option
 2054:     if ($env{'form.showgrading'} eq '' || (!&canmodify($usec))) {
 2055: 	my $toGrade.='<input type="button" value="Grade Student" '.
 2056: 	    'onClick="javascript:checksubmit(this.form,\'Grade Student\',\''
 2057: 	    .$counter.'\');" target="_self" /> &nbsp;'."\n" if (&canmodify($usec));
 2058: 	$toGrade.='</td></tr></table></td></tr></table>'."\n";
 2059: 	if (($env{'form.command'} eq 'submission') || 
 2060: 	    ($env{'form.command'} eq 'processGroup' && $counter == $total)) {
 2061: 	    $toGrade.='</form>'.&show_grading_menu_form($symb); 
 2062: 	}
 2063: 	$request->print($toGrade);
 2064: 	return;
 2065:     } else {
 2066: 	$request->print('</td></tr></table></td></tr></table>'."\n");
 2067:     }
 2068: 
 2069:     # essay grading message center
 2070:     if ($env{'form.handgrade'} eq 'yes') {
 2071: 	my ($lastname,$givenn) = split(/,/,$env{'form.fullname'});
 2072: 	my $msgfor = $givenn.' '.$lastname;
 2073: 	if (scalar(@col_fullnames) > 0) {
 2074: 	    my $lastone = pop @col_fullnames;
 2075: 	    $msgfor .= ', '.(join ', ',@col_fullnames).' and '.$lastone.'.';
 2076: 	}
 2077: 	$msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript
 2078: 	$result='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".
 2079: 	    '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n";
 2080: 	$result.='&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.
 2081: 	    ',\''.$msgfor.'\');" target="_self">'.
 2082: 	    &mt('Compose message to student').(scalar(@col_fullnames) >= 1 ? 's' : '').'</a><label> ('.
 2083: 	    &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" /></label>)'.
 2084: 	    '<img src="'.$request->dir_config('lonIconsURL').
 2085: 	    '/mailbkgrd.gif" width="14" height="10" name="mailicon'.$counter.'" />'."\n".
 2086: 	    '<br />&nbsp;('.
 2087: 	    &mt('Message will be sent when you click on Save & Next below.').")\n";
 2088: 	$request->print($result);
 2089:     }
 2090:     if ($perm{'vgr'}) {
 2091: 	$request->print('<br />'.
 2092: 	    &Apache::loncommon::track_student_link(&mt('View recent activity'),
 2093: 						   $uname,$udom,'check'));
 2094:     }
 2095:     if ($perm{'opa'}) {
 2096: 	$request->print('<br />'.
 2097: 	    &Apache::loncommon::pprmlink(&mt('Set/Change parameters'),
 2098: 					 $uname,$udom,$symb,'check'));
 2099:     }
 2100: 
 2101:     my %seen = ();
 2102:     my @partlist;
 2103:     my @gradePartRespid;
 2104:     my @part_response_id = &flatten_responseType($responseType);
 2105:     foreach my $part_response_id (@part_response_id) {
 2106:     	my ($partid,$respid) = @{ $part_response_id };
 2107: 	my $part_resp = join('_',@{ $part_response_id });
 2108: 	next if ($seen{$partid} > 0);
 2109: 	$seen{$partid}++;
 2110: 	next if ($$handgrade{$part_resp} ne 'yes' 
 2111: 		 && $env{'form.lastSub'} eq 'hdgrade');
 2112: 	push @partlist,$partid;
 2113: 	push @gradePartRespid,$partid.'.'.$respid;
 2114: 	$request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
 2115:     }
 2116:     $result='<input type="hidden" name="partlist'.$counter.
 2117: 	'" value="'.(join ":",@partlist).'" />'."\n";
 2118:     $result.='<input type="hidden" name="gradePartRespid'.
 2119: 	'" value="'.(join ":",@gradePartRespid).'" />'."\n" if ($counter == 0);
 2120:     my $ctr = 0;
 2121:     while ($ctr < scalar(@partlist)) {
 2122: 	$result.='<input type="hidden" name="partid'.$counter.'_'.$ctr.'" value="'.
 2123: 	    $partlist[$ctr].'" />'."\n";
 2124: 	$ctr++;
 2125:     }
 2126:     $request->print($result.'</td></tr></table></td></tr></table>'."\n");
 2127: 
 2128:     # print end of form
 2129:     if ($counter == $total) {
 2130: 	my $endform='<table border="0"><tr><td>'."\n";
 2131: 	$endform.='<input type="button" value="Save & Next" '.
 2132: 	    'onClick="javascript:checksubmit(this.form,\'Save & Next\','.
 2133: 	    $total.','.scalar(@partlist).');" target="_self" /> &nbsp;'."\n";
 2134: 	my $ntstu ='<select name="NTSTU">'.
 2135: 	    '<option>1</option><option>2</option>'.
 2136: 	    '<option>3</option><option>5</option>'.
 2137: 	    '<option>7</option><option>10</option></select>'."\n";
 2138: 	my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1');
 2139: 	$ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</;
 2140: 	$endform.=$ntstu.'student(s) &nbsp;&nbsp;';
 2141: 	$endform.='<input type="button" value="Previous" '.
 2142: 	    'onClick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> &nbsp;'."\n".
 2143: 	    '<input type="button" value="Next" '.
 2144: 	    'onClick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> &nbsp;';
 2145: 	$endform.='(Next and Previous (student) do not save the scores.)'."\n" ;
 2146:         $endform.="<input type='hidden' value='".&get_increment().
 2147:             "' name='increment' />";
 2148: 	$endform.='</td><tr></table></form>';
 2149: 	$endform.=&show_grading_menu_form($symb);
 2150: 	$request->print($endform);
 2151:     }
 2152:     return '';
 2153: }
 2154: 
 2155: #--- Retrieve the last submission for all the parts
 2156: sub get_last_submission {
 2157:     my ($returnhash)=@_;
 2158:     my (@string,$timestamp);
 2159:     if ($$returnhash{'version'}) {
 2160: 	my %lasthash=();
 2161: 	my ($version);
 2162: 	for ($version=1;$version<=$$returnhash{'version'};$version++) {
 2163: 	    foreach my $key (sort(split(/\:/,
 2164: 					$$returnhash{$version.':keys'}))) {
 2165: 		$lasthash{$key}=$$returnhash{$version.':'.$key};
 2166: 		$timestamp = 
 2167: 		    scalar(localtime($$returnhash{$version.':timestamp'}));
 2168: 	    }
 2169: 	}
 2170: 	foreach my $key (keys(%lasthash)) {
 2171: 	    next if ($key !~ /\.submission$/);
 2172: 
 2173: 	    my ($partid,$foo) = split(/submission$/,$key);
 2174: 	    my $draft  = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ?
 2175: 		'<span class="LC_warning">Draft Copy</span> ' : '';
 2176: 	    push(@string, join(':', $key, $draft.$lasthash{$key}));
 2177: 	}
 2178:     }
 2179:     if (!@string) {
 2180: 	$string[0] =
 2181: 	    '<span class="LC_warning">Nothing submitted - no attempts.</span>';
 2182:     }
 2183:     return (\@string,\$timestamp);
 2184: }
 2185: 
 2186: #--- High light keywords, with style choosen by user.
 2187: sub keywords_highlight {
 2188:     my $string    = shift;
 2189:     my $size      = $env{'form.kwsize'} eq '0' ? '' : 'size='.$env{'form.kwsize'};
 2190:     my $styleon   = $env{'form.kwstyle'} eq ''  ? '' : $env{'form.kwstyle'};
 2191:     (my $styleoff = $styleon) =~ s/\</\<\//;
 2192:     my @keylist   = split(/[,\s+]/,$env{'form.keywords'});
 2193:     foreach my $keyword (@keylist) {
 2194: 	$string =~ s/\b\Q$keyword\E(\b|\.)/<font color\=$env{'form.kwclr'} $size\>$styleon$keyword$styleoff<\/font>/gi;
 2195:     }
 2196:     return $string;
 2197: }
 2198: 
 2199: #--- Called from submission routine
 2200: sub processHandGrade {
 2201:     my ($request) = shift;
 2202:     my $symb   = &get_symb($request);
 2203:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
 2204:     my $button = $env{'form.gradeOpt'};
 2205:     my $ngrade = $env{'form.NCT'};
 2206:     my $ntstu  = $env{'form.NTSTU'};
 2207:     my $cdom   = $env{'course.'.$env{'request.course.id'}.'.domain'};
 2208:     my $cnum   = $env{'course.'.$env{'request.course.id'}.'.num'};
 2209: 
 2210:     if ($button eq 'Save & Next') {
 2211: 	my $ctr = 0;
 2212: 	while ($ctr < $ngrade) {
 2213: 	    my ($uname,$udom) = split(/:/,$env{'form.unamedom'.$ctr});
 2214: 	    my ($errorflag,$pts,$wgt) = &saveHandGrade($request,$symb,$uname,$udom,$ctr);
 2215: 	    if ($errorflag eq 'no_score') {
 2216: 		$ctr++;
 2217: 		next;
 2218: 	    }
 2219: 	    if ($errorflag eq 'not_allowed') {
 2220: 		$request->print("<span class=\"LC_warning\">Not allowed to modify grades for $uname:$udom</span>");
 2221: 		$ctr++;
 2222: 		next;
 2223: 	    }
 2224: 	    my $includemsg = $env{'form.includemsg'.$ctr};
 2225: 	    my ($subject,$message,$msgstatus) = ('','','');
 2226: 	    my $restitle = &Apache::lonnet::gettitle($symb);
 2227:             my ($feedurl,$showsymb) =
 2228: 		&get_feedurl_and_symb($symb,$uname,$udom);
 2229: 	    my $messagetail;
 2230: 	    if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) {
 2231: 		$subject = $env{'form.msgsub'} if ($includemsg =~ /msgsub/);
 2232: 		unless ($subject=~/\w/) { $subject=&mt('Grading Feedback'); }
 2233: 		$subject.=' ['.$restitle.']';
 2234: 		my (@msgnum) = split(/,/,$includemsg);
 2235: 		foreach (@msgnum) {
 2236: 		    $message.=$env{'form.'.$_} if ($_ =~ /savemsg|newmsg/ && $_ ne '');
 2237: 		}
 2238: 		$message =&Apache::lonfeedback::clear_out_html($message);
 2239: 		if ($env{'form.withgrades'.$ctr}) {
 2240: 		    $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt;
 2241: 		    $messagetail = " for <a href=\"".
 2242: 		                   $feedurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
 2243: 		}
 2244: 		$msgstatus = 
 2245:                     &Apache::lonmsg::user_normal_msg($uname,$udom,$subject,
 2246: 						     $message.$messagetail,
 2247:                                                      undef,$feedurl,undef,
 2248:                                                      undef,undef,$showsymb,
 2249:                                                      $restitle);
 2250: 		$request->print('<br />'.&mt('Sending message to [_1]:[_2]',$uname,$udom).': '.
 2251: 				$msgstatus);
 2252: 	    }
 2253: 	    if ($env{'form.collaborator'.$ctr}) {
 2254: 		my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr");
 2255: 		foreach my $collabstr (@collabstrs) {
 2256: 		    my ($part,@collaborators) = split(/:/,$collabstr);
 2257: 		    foreach my $collaborator (@collaborators) {
 2258: 			my ($errorflag,$pts,$wgt) = 
 2259: 			    &saveHandGrade($request,$symb,$collaborator,$udom,$ctr,
 2260: 					   $env{'form.unamedom'.$ctr},$part);
 2261: 			if ($errorflag eq 'not_allowed') {
 2262: 			    $request->print("<span class=\"LC_error\">".&mt('Not allowed to modify grades for [_1]',"$collaborator:$udom")."</span>");
 2263: 			    next;
 2264: 			} elsif ($message ne '') {
 2265: 			    my ($baseurl,$showsymb) = 
 2266: 				&get_feedurl_and_symb($symb,$collaborator,
 2267: 						      $udom);
 2268: 			    if ($env{'form.withgrades'.$ctr}) {
 2269: 				$messagetail = " for <a href=\"".
 2270:                                     $baseurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
 2271: 			    }
 2272: 			    $msgstatus = 
 2273: 				&Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
 2274: 			}
 2275: 		    }
 2276: 		}
 2277: 	    }
 2278: 	    $ctr++;
 2279: 	}
 2280:     }
 2281: 
 2282:     if ($env{'form.handgrade'} eq 'yes') {
 2283: 	# Keywords sorted in alphabatical order
 2284: 	my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
 2285: 	my %keyhash = ();
 2286: 	$env{'form.keywords'}           =~ s/,\s{0,}|\s+/ /g;
 2287: 	$env{'form.keywords'}           =~ s/^\s+|\s+$//;
 2288: 	my (@keywords) = sort(split(/\s+/,$env{'form.keywords'}));
 2289: 	$env{'form.keywords'} = join(' ',@keywords);
 2290: 	$keyhash{$symb.'_keywords'}     = $env{'form.keywords'};
 2291: 	$keyhash{$symb.'_subject'}      = $env{'form.msgsub'};
 2292: 	$keyhash{$loginuser.'_kwclr'}   = $env{'form.kwclr'};
 2293: 	$keyhash{$loginuser.'_kwsize'}  = $env{'form.kwsize'};
 2294: 	$keyhash{$loginuser.'_kwstyle'} = $env{'form.kwstyle'};
 2295: 
 2296: 	# message center - Order of message gets changed. Blank line is eliminated.
 2297: 	# New messages are saved in env for the next student.
 2298: 	# All messages are saved in nohist_handgrade.db
 2299: 	my ($ctr,$idx) = (1,1);
 2300: 	while ($ctr <= $env{'form.savemsgN'}) {
 2301: 	    if ($env{'form.savemsg'.$ctr} ne '') {
 2302: 		$keyhash{$symb.'_savemsg'.$idx} = $env{'form.savemsg'.$ctr};
 2303: 		$idx++;
 2304: 	    }
 2305: 	    $ctr++;
 2306: 	}
 2307: 	$ctr = 0;
 2308: 	while ($ctr < $ngrade) {
 2309: 	    if ($env{'form.newmsg'.$ctr} ne '') {
 2310: 		$keyhash{$symb.'_savemsg'.$idx} = $env{'form.newmsg'.$ctr};
 2311: 		$env{'form.savemsg'.$idx} = $env{'form.newmsg'.$ctr};
 2312: 		$idx++;
 2313: 	    }
 2314: 	    $ctr++;
 2315: 	}
 2316: 	$env{'form.savemsgN'} = --$idx;
 2317: 	$keyhash{$symb.'_savemsgN'} = $env{'form.savemsgN'};
 2318: 	my $putresult = &Apache::lonnet::put
 2319: 	    ('nohist_handgrade',\%keyhash,$cdom,$cnum);
 2320:     }
 2321:     # Called by Save & Refresh from Highlight Attribute Window
 2322:     my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
 2323:     if ($env{'form.refresh'} eq 'on') {
 2324: 	my ($ctr,$total) = (0,0);
 2325: 	while ($ctr < $ngrade) {
 2326: 	    $total++ if  $env{'form.unamedom'.$ctr} ne '';
 2327: 	    $ctr++;
 2328: 	}
 2329: 	$env{'form.NTSTU'}=$ngrade;
 2330: 	$ctr = 0;
 2331: 	while ($ctr < $total) {
 2332: 	    my $processUser = $env{'form.unamedom'.$ctr};
 2333: 	    ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
 2334: 	    $env{'form.fullname'} = $$fullname{$processUser};
 2335: 	    &submission($request,$ctr,$total-1);
 2336: 	    $ctr++;
 2337: 	}
 2338: 	return '';
 2339:     }
 2340: 
 2341: # Go directly to grade student - from submission or link from chart page
 2342:     if ($button eq 'Grade Student') {
 2343: 	(undef,undef,$env{'form.handgrade'},undef,undef) = &showResourceInfo($symb);
 2344: 	my $processUser = $env{'form.unamedom'.$env{'form.studentNo'}};
 2345: 	($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
 2346: 	$env{'form.fullname'} = $$fullname{$processUser};
 2347: 	&submission($request,0,0);
 2348: 	return '';
 2349:     }
 2350: 
 2351:     # Get the next/previous one or group of students
 2352:     my $firststu = $env{'form.unamedom0'};
 2353:     my $laststu = $env{'form.unamedom'.($ngrade-1)};
 2354:     my $ctr = 2;
 2355:     while ($laststu eq '') {
 2356: 	$laststu  = $env{'form.unamedom'.($ngrade-$ctr)};
 2357: 	$ctr++;
 2358: 	$laststu = $firststu if ($ctr > $ngrade);
 2359:     }
 2360: 
 2361:     my (@parsedlist,@nextlist);
 2362:     my ($nextflg) = 0;
 2363:     foreach (sort 
 2364: 	     {
 2365: 		 if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
 2366: 		     return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
 2367: 		 }
 2368: 		 return $a cmp $b;
 2369: 	     } (keys(%$fullname))) {
 2370: 	if ($nextflg == 1 && $button =~ /Next$/) {
 2371: 	    push @parsedlist,$_;
 2372: 	}
 2373: 	$nextflg = 1 if ($_ eq $laststu);
 2374: 	if ($button eq 'Previous') {
 2375: 	    last if ($_ eq $firststu);
 2376: 	    push @parsedlist,$_;
 2377: 	}
 2378:     }
 2379:     $ctr = 0;
 2380:     @parsedlist = reverse @parsedlist if ($button eq 'Previous');
 2381:     my ($partlist) = &response_type($symb);
 2382:     foreach my $student (@parsedlist) {
 2383: 	my $submitonly=$env{'form.submitonly'};
 2384: 	my ($uname,$udom) = split(/:/,$student);
 2385: 	
 2386: 	if ($submitonly eq 'queued') {
 2387: 	    my %queue_status = 
 2388: 		&Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
 2389: 							$udom,$uname);
 2390: 	    next if (!defined($queue_status{'gradingqueue'}));
 2391: 	}
 2392: 
 2393: 	if ($submitonly =~ /^(yes|graded|incorrect)$/) {
 2394: #	    my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
 2395: 	    my %status=&student_gradeStatus($symb,$udom,$uname,$partlist);
 2396: 	    my $submitted = 0;
 2397: 	    my $ungraded = 0;
 2398: 	    my $incorrect = 0;
 2399: 	    foreach (keys(%status)) {
 2400: 		$submitted = 1 if ($status{$_} ne 'nothing');
 2401: 		$ungraded = 1 if ($status{$_} =~ /^ungraded/);
 2402: 		$incorrect = 1 if ($status{$_} =~ /^incorrect/);
 2403: 		my ($foo,$partid,$foo1) = split(/\./,$_);
 2404: 		if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
 2405: 		    $submitted = 0;
 2406: 		}
 2407: 	    }
 2408: 	    next if (!$submitted && ($submitonly eq 'yes' ||
 2409: 				     $submitonly eq 'incorrect' ||
 2410: 				     $submitonly eq 'graded'));
 2411: 	    next if (!$ungraded && ($submitonly eq 'graded'));
 2412: 	    next if (!$incorrect && $submitonly eq 'incorrect');
 2413: 	}
 2414: 	push @nextlist,$student if ($ctr < $ntstu);
 2415: 	last if ($ctr == $ntstu);
 2416: 	$ctr++;
 2417:     }
 2418: 
 2419:     $ctr = 0;
 2420:     my $total = scalar(@nextlist)-1;
 2421: 
 2422:     foreach (sort @nextlist) {
 2423: 	my ($uname,$udom,$submitter) = split(/:/);
 2424: 	$env{'form.student'}  = $uname;
 2425: 	$env{'form.userdom'}  = $udom;
 2426: 	$env{'form.fullname'} = $$fullname{$_};
 2427: 	&submission($request,$ctr,$total);
 2428: 	$ctr++;
 2429:     }
 2430:     if ($total < 0) {
 2431: 	my $the_end = '<h3><span class="LC_info">LON-CAPA User Message</span></h3><br />'."\n";
 2432: 	$the_end.='<b>Message: </b> No more students for this section or class.<br /><br />'."\n";
 2433: 	$the_end.='Click on the button below to return to the grading menu.<br /><br />'."\n";
 2434: 	$the_end.=&show_grading_menu_form($symb);
 2435: 	$request->print($the_end);
 2436:     }
 2437:     return '';
 2438: }
 2439: 
 2440: #---- Save the score and award for each student, if changed
 2441: sub saveHandGrade {
 2442:     my ($request,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_;
 2443:     my @version_parts;
 2444:     my $usec = &Apache::lonnet::getsection($domain,$stuname,
 2445: 					   $env{'request.course.id'});
 2446:     if (!&canmodify($usec)) { return('not_allowed'); }
 2447:     my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$stuname);
 2448:     my @parts_graded;
 2449:     my %newrecord  = ();
 2450:     my ($pts,$wgt) = ('','');
 2451:     my %aggregate = ();
 2452:     my $aggregateflag = 0;
 2453:     my @parts = split(/:/,$env{'form.partlist'.$newflg});
 2454:     foreach my $new_part (@parts) {
 2455: 	#collaborator ($submi may vary for different parts
 2456: 	if ($submitter && $new_part ne $part) { next; }
 2457: 	my $dropMenu = $env{'form.GD_SEL'.$newflg.'_'.$new_part};
 2458: 	if ($dropMenu eq 'excused') {
 2459: 	    if ($record{'resource.'.$new_part.'.solved'} ne 'excused') {
 2460: 		$newrecord{'resource.'.$new_part.'.solved'} = 'excused';
 2461: 		if (exists($record{'resource.'.$new_part.'.awarded'})) {
 2462: 		    $newrecord{'resource.'.$new_part.'.awarded'} = '';
 2463: 		}
 2464: 	        $newrecord{'resource.'.$new_part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
 2465: 	    }
 2466: 	} elsif ($dropMenu eq 'reset status'
 2467: 		 && exists($record{'resource.'.$new_part.'.solved'})) { #don't bother if no old records -> no attempts
 2468: 	    foreach my $key (keys (%record)) {
 2469: 		if ($key=~/^resource\.\Q$new_part\E\./) { $newrecord{$key} = ''; }
 2470: 	    }
 2471: 	    $newrecord{'resource.'.$new_part.'.regrader'}=
 2472: 		"$env{'user.name'}:$env{'user.domain'}";
 2473:             my $totaltries = $record{'resource.'.$part.'.tries'};
 2474: 
 2475:             my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
 2476: 					       [$new_part]);
 2477:             my $aggtries =$totaltries;
 2478:             if ($last_resets{$new_part}) {
 2479:                 $aggtries = &get_num_tries(\%record,$last_resets{$new_part},
 2480: 					   $new_part);
 2481:             }
 2482: 
 2483:             my $solvedstatus = $record{'resource.'.$new_part.'.solved'};
 2484:             if ($aggtries > 0) {
 2485:                 &decrement_aggs($symb,$new_part,\%aggregate,$aggtries,$totaltries,$solvedstatus);
 2486:                 $aggregateflag = 1;
 2487:             }
 2488: 	} elsif ($dropMenu eq '') {
 2489: 	    $pts = ($env{'form.GD_BOX'.$newflg.'_'.$new_part} ne '' ? 
 2490: 		    $env{'form.GD_BOX'.$newflg.'_'.$new_part} : 
 2491: 		    $env{'form.RADVAL'.$newflg.'_'.$new_part});
 2492: 	    if ($pts eq '' && $env{'form.GD_SEL'.$newflg.'_'.$new_part} eq '') {
 2493: 		next;
 2494: 	    }
 2495: 	    $wgt = $env{'form.WGT'.$newflg.'_'.$new_part} eq '' ? 1 : 
 2496: 		$env{'form.WGT'.$newflg.'_'.$new_part};
 2497: 	    my $partial= $pts/$wgt;
 2498: 	    if ($partial eq $record{'resource.'.$new_part.'.awarded'}) {
 2499: 		#do not update score for part if not changed.
 2500:                 &handback_files($request,$symb,$stuname,$domain,$newflg,$new_part,\%newrecord);
 2501: 		next;
 2502: 	    } else {
 2503: 	        push @parts_graded, $new_part;
 2504: 	    }
 2505: 	    if ($record{'resource.'.$new_part.'.awarded'} ne $partial) {
 2506: 		$newrecord{'resource.'.$new_part.'.awarded'}  = $partial;
 2507: 	    }
 2508: 	    my $reckey = 'resource.'.$new_part.'.solved';
 2509: 	    if ($partial == 0) {
 2510: 		if ($record{$reckey} ne 'incorrect_by_override') {
 2511: 		    $newrecord{$reckey} = 'incorrect_by_override';
 2512: 		}
 2513: 	    } else {
 2514: 		if ($record{$reckey} ne 'correct_by_override') {
 2515: 		    $newrecord{$reckey} = 'correct_by_override';
 2516: 		}
 2517: 	    }	    
 2518: 	    if ($submitter && 
 2519: 		($record{'resource.'.$new_part.'.submitted_by'} ne $submitter)) {
 2520: 		$newrecord{'resource.'.$new_part.'.submitted_by'} = $submitter;
 2521: 	    }
 2522: 	    $newrecord{'resource.'.$new_part.'.regrader'}=
 2523: 		"$env{'user.name'}:$env{'user.domain'}";
 2524: 	}
 2525: 	# unless problem has been graded, set flag to version the submitted files
 2526: 	unless ($record{'resource.'.$new_part.'.solved'} =~ /^correct_/  || 
 2527: 	        $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override' ||
 2528: 	        $dropMenu eq 'reset status')
 2529: 	   {
 2530: 	    push (@version_parts,$new_part);
 2531: 	}
 2532:     }
 2533:     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
 2534:     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
 2535: 
 2536:     if (%newrecord) {
 2537:         if (@version_parts) {
 2538:             my @changed_keys = &version_portfiles(\%record, \@parts_graded, 
 2539:                                 $env{'request.course.id'}, $symb, $domain, $stuname, \@version_parts);
 2540: 	    @newrecord{@changed_keys} = @record{@changed_keys};
 2541: 	    foreach my $new_part (@version_parts) {
 2542: 		&handback_files($request,$symb,$stuname,$domain,$newflg,
 2543: 				$new_part,\%newrecord);
 2544: 	    }
 2545:         }
 2546: 	&Apache::lonnet::cstore(\%newrecord,$symb,
 2547: 				$env{'request.course.id'},$domain,$stuname);
 2548: 	&check_and_remove_from_queue(\@parts,\%record,\%newrecord,$symb,
 2549: 				     $cdom,$cnum,$domain,$stuname);
 2550:     }
 2551:     if ($aggregateflag) {
 2552:         &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
 2553: 			      $cdom,$cnum);
 2554:     }
 2555:     return ('',$pts,$wgt);
 2556: }
 2557: 
 2558: sub check_and_remove_from_queue {
 2559:     my ($parts,$record,$newrecord,$symb,$cdom,$cnum,$domain,$stuname) = @_;
 2560:     my @ungraded_parts;
 2561:     foreach my $part (@{$parts}) {
 2562: 	if (    $record->{   'resource.'.$part.'.awarded'} eq ''
 2563: 	     && $record->{   'resource.'.$part.'.solved' } ne 'excused'
 2564: 	     && $newrecord->{'resource.'.$part.'.awarded'} eq ''
 2565: 	     && $newrecord->{'resource.'.$part.'.solved' } ne 'excused'
 2566: 		) {
 2567: 	    push(@ungraded_parts, $part);
 2568: 	}
 2569:     }
 2570:     if ( !@ungraded_parts ) {
 2571: 	&Apache::bridgetask::remove_from_queue('gradingqueue',$symb,$cdom,
 2572: 					       $cnum,$domain,$stuname);
 2573:     }
 2574: }
 2575: 
 2576: sub handback_files {
 2577:     my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_;
 2578:     my $portfolio_root = &propath($domain,$stuname).'/userfiles/portfolio';
 2579:     my ($partlist,$handgrade,$responseType) = &response_type($symb);
 2580: 
 2581:     my @part_response_id = &flatten_responseType($responseType);
 2582:     foreach my $part_response_id (@part_response_id) {
 2583:     	my ($part_id,$resp_id) = @{ $part_response_id };
 2584: 	my $part_resp = join('_',@{ $part_response_id });
 2585:             if (($env{'form.'.$newflg.'_'.$part_resp.'_returndoc1'}) && ($new_part == $part_id)) {
 2586:                 # if multiple files are uploaded names will be 'returndoc2','returndoc3'
 2587:                 my $file_counter = 1;
 2588: 		my $file_msg;
 2589:                 while ($env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter}) {
 2590:                     my $fname=$env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'.filename'};
 2591:                     my ($directory,$answer_file) = 
 2592:                         ($env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$file_counter} =~ /^(.*?)([^\/]*)$/);
 2593:                     my ($answer_name,$answer_ver,$answer_ext) =
 2594: 		        &file_name_version_ext($answer_file);
 2595: 		    my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/);
 2596: 		    my @dir_list = &Apache::lonnet::dirlist($portfolio_path,$domain,$stuname,$portfolio_root);
 2597: 		    my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
 2598:                     # fix file name
 2599:                     my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/);
 2600:                     my $result=&Apache::lonnet::finishuserfileupload($stuname,$domain,
 2601:             	                                $newflg.'_'.$part_resp.'_returndoc'.$file_counter,
 2602:             	                                $save_file_name);
 2603:                     if ($result !~ m|^/uploaded/|) {
 2604:                         $request->print('<span class="LC_error">An error occurred ('.$result.
 2605:                         ') while trying to upload '.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'</span><br />');
 2606:                     } else {
 2607:                         # mark the file as read only
 2608:                         my @files = ($save_file_name);
 2609:                         my @what = ($symb,$env{'request.course.id'},'handback');
 2610:                         &Apache::lonnet::mark_as_readonly($domain,$stuname,\@files,\@what);
 2611: 			if (exists($$newrecord{"resource.$new_part.$resp_id.handback"})) {
 2612: 			    $$newrecord{"resource.$new_part.$resp_id.handback"}.=',';
 2613: 			}
 2614:                         $$newrecord{"resource.$new_part.$resp_id.handback"} .= $save_file_name;
 2615: 			$file_msg.= "\n".'<br /><span class="LC_filename"><a href="/uploaded/'."$domain/$stuname/".$save_file_name.'">'.$save_file_name."</a></span><br />";
 2616: 
 2617:                     }
 2618:                     $request->print("<br />".$fname." will be the uploaded file name");
 2619:                     $request->print(" ".$env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$file_counter});
 2620:                     $file_counter++;
 2621:                 }
 2622: 		my $subject = "File Handed Back by Instructor ";
 2623: 		my $message = "A file has been returned that was originally submitted in reponse to: <br />";
 2624: 		$message .= "<strong>".&Apache::lonnet::gettitle($symb)."</strong><br />";
 2625: 		$message .= ' The returned file(s) are named: '. $file_msg;
 2626: 		$message .= " and can be found in your portfolio space.";
 2627: 		my ($feedurl,$showsymb) = 
 2628: 		    &get_feedurl_and_symb($symb,$domain,$stuname);
 2629:                 my $restitle = &Apache::lonnet::gettitle($symb);
 2630: 		my $msgstatus = 
 2631:                    &Apache::lonmsg::user_normal_msg($stuname,$domain,$subject.
 2632: 			 ' (File Returned) ['.$restitle.']',$message,undef,
 2633:                          $feedurl,undef,undef,undef,$showsymb,$restitle);
 2634:             }
 2635:         }
 2636:     return;
 2637: }
 2638: 
 2639: sub get_feedurl_and_symb {
 2640:     my ($symb,$uname,$udom) = @_;
 2641:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
 2642:     $url = &Apache::lonnet::clutter($url);
 2643:     my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl',
 2644: 					$symb,$udom,$uname);
 2645:     if ($encrypturl =~ /^yes$/i) {
 2646: 	&Apache::lonenc::encrypted(\$url,1);
 2647: 	&Apache::lonenc::encrypted(\$symb,1);
 2648:     }
 2649:     return ($url,$symb);
 2650: }
 2651: 
 2652: sub get_submitted_files {
 2653:     my ($udom,$uname,$partid,$respid,$record) = @_;
 2654:     my @files;
 2655:     if ($$record{"resource.$partid.$respid.portfiles"}) {
 2656:         my $file_url = '/uploaded/'.$udom.'/'.$uname.'/portfolio';
 2657:         foreach my $file (split(',',$$record{"resource.$partid.$respid.portfiles"})) {
 2658:     	    push(@files,$file_url.$file);
 2659:         }
 2660:     }
 2661:     if ($$record{"resource.$partid.$respid.uploadedurl"}) {
 2662:         push(@files,$$record{"resource.$partid.$respid.uploadedurl"});
 2663:     }
 2664:     return (\@files);
 2665: }
 2666: 
 2667: # ----------- Provides number of tries since last reset.
 2668: sub get_num_tries {
 2669:     my ($record,$last_reset,$part) = @_;
 2670:     my $timestamp = '';
 2671:     my $num_tries = 0;
 2672:     if ($$record{'version'}) {
 2673:         for (my $version=$$record{'version'};$version>=1;$version--) {
 2674:             if (exists($$record{$version.':resource.'.$part.'.solved'})) {
 2675:                 $timestamp = $$record{$version.':timestamp'};
 2676:                 if ($timestamp > $last_reset) {
 2677:                     $num_tries ++;
 2678:                 } else {
 2679:                     last;
 2680:                 }
 2681:             }
 2682:         }
 2683:     }
 2684:     return $num_tries;
 2685: }
 2686: 
 2687: # ----------- Determine decrements required in aggregate totals 
 2688: sub decrement_aggs {
 2689:     my ($symb,$part,$aggregate,$aggtries,$totaltries,$solvedstatus) = @_;
 2690:     my %decrement = (
 2691:                         attempts => 0,
 2692:                         users => 0,
 2693:                         correct => 0
 2694:                     );
 2695:     $decrement{'attempts'} = $aggtries;
 2696:     if ($solvedstatus =~ /^correct/) {
 2697:         $decrement{'correct'} = 1;
 2698:     }
 2699:     if ($aggtries == $totaltries) {
 2700:         $decrement{'users'} = 1;
 2701:     }
 2702:     foreach my $type (keys (%decrement)) {
 2703:         $$aggregate{$symb."\0".$part."\0".$type} = -$decrement{$type};
 2704:     }
 2705:     return;
 2706: }
 2707: 
 2708: # ----------- Determine timestamps for last reset of aggregate totals for parts  
 2709: sub get_last_resets {
 2710:     my ($symb,$courseid,$partids) =@_;
 2711:     my %last_resets;
 2712:     my $cdom = $env{'course.'.$courseid.'.domain'};
 2713:     my $cname = $env{'course.'.$courseid.'.num'};
 2714:     my @keys;
 2715:     foreach my $part (@{$partids}) {
 2716: 	push(@keys,"$symb\0$part\0resettime");
 2717:     }
 2718:     my %results=&Apache::lonnet::get('nohist_resourcetracker',\@keys,
 2719: 				     $cdom,$cname);
 2720:     foreach my $part (@{$partids}) {
 2721: 	$last_resets{$part}=$results{"$symb\0$part\0resettime"};
 2722:     }
 2723:     return %last_resets;
 2724: }
 2725: 
 2726: # ----------- Handles creating versions for portfolio files as answers
 2727: sub version_portfiles {
 2728:     my ($record, $parts_graded, $courseid, $symb, $domain, $stu_name, $v_flag) = @_;
 2729:     my $version_parts = join('|',@$v_flag);
 2730:     my @returned_keys;
 2731:     my $parts = join('|', @$parts_graded);
 2732:     my $portfolio_root = &propath($domain,$stu_name).
 2733: 	'/userfiles/portfolio';
 2734:     foreach my $key (keys(%$record)) {
 2735:         my $new_portfiles;
 2736:         if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {
 2737:             my @versioned_portfiles;
 2738:             my @portfiles = split(/\s*,\s*/,$$record{$key});
 2739:             foreach my $file (@portfiles) {
 2740:                 &Apache::lonnet::unmark_as_readonly($domain,$stu_name,[$symb,$env{'request.course.id'}],$file);
 2741:                 my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*)$/);
 2742: 		my ($answer_name,$answer_ver,$answer_ext) =
 2743: 		    &file_name_version_ext($answer_file);
 2744:                 my @dir_list = &Apache::lonnet::dirlist($directory,$domain,$stu_name,$portfolio_root);
 2745:                 my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
 2746:                 my $new_answer = &version_selected_portfile($domain, $stu_name, $directory, $answer_file, $version);
 2747:                 if ($new_answer ne 'problem getting file') {
 2748:                     push(@versioned_portfiles, $directory.$new_answer);
 2749:                     &Apache::lonnet::mark_as_readonly($domain,$stu_name,
 2750:                         [$directory.$new_answer],
 2751:                         [$symb,$env{'request.course.id'},'graded']);
 2752:                 }
 2753:             }
 2754:             $$record{$key} = join(',',@versioned_portfiles);
 2755:             push(@returned_keys,$key);
 2756:         }
 2757:     } 
 2758:     return (@returned_keys);   
 2759: }
 2760: 
 2761: sub get_next_version {
 2762:     my ($answer_name, $answer_ext, $dir_list) = @_;
 2763:     my $version;
 2764:     foreach my $row (@$dir_list) {
 2765:         my ($file) = split(/\&/,$row,2);
 2766:         my ($file_name,$file_version,$file_ext) =
 2767: 	    &file_name_version_ext($file);
 2768:         if (($file_name eq $answer_name) && 
 2769: 	    ($file_ext eq $answer_ext)) {
 2770:                 # gets here if filename and extension match, regardless of version
 2771:                 if ($file_version ne '') {
 2772:                 # a versioned file is found  so save it for later
 2773:                 if ($file_version > $version) {
 2774: 		    $version = $file_version;
 2775: 	        }
 2776:             }
 2777:         }
 2778:     } 
 2779:     $version ++;
 2780:     return($version);
 2781: }
 2782: 
 2783: sub version_selected_portfile {
 2784:     my ($domain,$stu_name,$directory,$file_name,$version) = @_;
 2785:     my ($answer_name,$answer_ver,$answer_ext) =
 2786:         &file_name_version_ext($file_name);
 2787:     my $new_answer;
 2788:     $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stu_name/portfolio$directory$file_name");
 2789:     if($env{'form.copy'} eq '-1') {
 2790:         &Apache::lonnet::logthis('problem getting file '.$file_name);
 2791:         $new_answer = 'problem getting file';
 2792:     } else {
 2793:         $new_answer = $answer_name.'.'.$version.'.'.$answer_ext;
 2794:         my $copy_result = &Apache::lonnet::finishuserfileupload(
 2795:                             $stu_name,$domain,'copy',
 2796: 		        '/portfolio'.$directory.$new_answer);
 2797:     }    
 2798:     return ($new_answer);
 2799: }
 2800: 
 2801: sub file_name_version_ext {
 2802:     my ($file)=@_;
 2803:     my @file_parts = split(/\./, $file);
 2804:     my ($name,$version,$ext);
 2805:     if (@file_parts > 1) {
 2806: 	$ext=pop(@file_parts);
 2807: 	if (@file_parts > 1 && $file_parts[-1] =~ /^\d+$/) {
 2808: 	    $version=pop(@file_parts);
 2809: 	}
 2810: 	$name=join('.',@file_parts);
 2811:     } else {
 2812: 	$name=join('.',@file_parts);
 2813:     }
 2814:     return($name,$version,$ext);
 2815: }
 2816: 
 2817: #--------------------------------------------------------------------------------------
 2818: #
 2819: #-------------------------- Next few routines handles grading by section or whole class
 2820: #
 2821: #--- Javascript to handle grading by section or whole class
 2822: sub viewgrades_js {
 2823:     my ($request) = shift;
 2824: 
 2825:     $request->print(<<VIEWJAVASCRIPT);
 2826: <script type="text/javascript" language="javascript">
 2827:    function writePoint(partid,weight,point) {
 2828: 	var radioButton = document.classgrade["RADVAL_"+partid];
 2829: 	var textbox = document.classgrade["TEXTVAL_"+partid];
 2830: 	if (point == "textval") {
 2831: 	    point = document.classgrade["TEXTVAL_"+partid].value;
 2832: 	    if (isNaN(point) || parseFloat(point) < 0) {
 2833: 		alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point));
 2834: 		var resetbox = false;
 2835: 		for (var i=0; i<radioButton.length; i++) {
 2836: 		    if (radioButton[i].checked) {
 2837: 			textbox.value = i;
 2838: 			resetbox = true;
 2839: 		    }
 2840: 		}
 2841: 		if (!resetbox) {
 2842: 		    textbox.value = "";
 2843: 		}
 2844: 		return;
 2845: 	    }
 2846: 	    if (parseFloat(point) > parseFloat(weight)) {
 2847: 		var resp = confirm("You entered a value ("+parseFloat(point)+
 2848: 				   ") greater than the weight for the part. Accept?");
 2849: 		if (resp == false) {
 2850: 		    textbox.value = "";
 2851: 		    return;
 2852: 		}
 2853: 	    }
 2854: 	    for (var i=0; i<radioButton.length; i++) {
 2855: 		radioButton[i].checked=false;
 2856: 		if (parseFloat(point) == i) {
 2857: 		    radioButton[i].checked=true;
 2858: 		}
 2859: 	    }
 2860: 
 2861: 	} else {
 2862: 	    textbox.value = parseFloat(point);
 2863: 	}
 2864: 	for (i=0;i<document.classgrade.total.value;i++) {
 2865: 	    var user = document.classgrade["ctr"+i].value;
 2866: 	    user = user.replace(new RegExp(':', 'g'),"_");
 2867: 	    var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
 2868: 	    var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
 2869: 	    var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
 2870: 	    if (saveval != "correct") {
 2871: 		scorename.value = point;
 2872: 		if (selname[0].selected != true) {
 2873: 		    selname[0].selected = true;
 2874: 		}
 2875: 	    }
 2876: 	}
 2877: 	document.classgrade["SELVAL_"+partid][0].selected = true;
 2878:     }
 2879: 
 2880:     function writeRadText(partid,weight) {
 2881: 	var selval   = document.classgrade["SELVAL_"+partid];
 2882: 	var radioButton = document.classgrade["RADVAL_"+partid];
 2883:         var override = document.classgrade["FORCE_"+partid].checked;
 2884: 	var textbox = document.classgrade["TEXTVAL_"+partid];
 2885: 	if (selval[1].selected || selval[2].selected) {
 2886: 	    for (var i=0; i<radioButton.length; i++) {
 2887: 		radioButton[i].checked=false;
 2888: 
 2889: 	    }
 2890: 	    textbox.value = "";
 2891: 
 2892: 	    for (i=0;i<document.classgrade.total.value;i++) {
 2893: 		var user = document.classgrade["ctr"+i].value;
 2894: 		user = user.replace(new RegExp(':', 'g'),"_");
 2895: 		var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
 2896: 		var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
 2897: 		var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
 2898: 		if ((saveval != "correct") || override) {
 2899: 		    scorename.value = "";
 2900: 		    if (selval[1].selected) {
 2901: 			selname[1].selected = true;
 2902: 		    } else {
 2903: 			selname[2].selected = true;
 2904: 			if (Number(document.classgrade["GD_"+user+"_"+partid+"_tries"].value)) 
 2905: 			{document.classgrade["GD_"+user+"_"+partid+"_tries"].value = '0';}
 2906: 		    }
 2907: 		}
 2908: 	    }
 2909: 	} else {
 2910: 	    for (i=0;i<document.classgrade.total.value;i++) {
 2911: 		var user = document.classgrade["ctr"+i].value;
 2912: 		user = user.replace(new RegExp(':', 'g'),"_");
 2913: 		var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
 2914: 		var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
 2915: 		var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
 2916: 		if ((saveval != "correct") || override) {
 2917: 		    scorename.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
 2918: 		    selname[0].selected = true;
 2919: 		}
 2920: 	    }
 2921: 	}	    
 2922:     }
 2923: 
 2924:     function changeSelect(partid,user) {
 2925: 	var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
 2926: 	var textbox = document.classgrade["GD_"+user+'_'+partid+"_awarded"];
 2927: 	var point  = textbox.value;
 2928: 	var weight = document.classgrade["weight_"+partid].value;
 2929: 
 2930: 	if (isNaN(point) || parseFloat(point) < 0) {
 2931: 	    alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point));
 2932: 	    textbox.value = "";
 2933: 	    return;
 2934: 	}
 2935: 	if (parseFloat(point) > parseFloat(weight)) {
 2936: 	    var resp = confirm("You entered a value ("+parseFloat(point)+
 2937: 			       ") greater than the weight of the part. Accept?");
 2938: 	    if (resp == false) {
 2939: 		textbox.value = "";
 2940: 		return;
 2941: 	    }
 2942: 	}
 2943: 	selval[0].selected = true;
 2944:     }
 2945: 
 2946:     function changeOneScore(partid,user) {
 2947: 	var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
 2948: 	if (selval[1].selected || selval[2].selected) {
 2949: 	    document.classgrade["GD_"+user+'_'+partid+"_awarded"].value = "";
 2950: 	    if (selval[2].selected) {
 2951: 		document.classgrade["GD_"+user+'_'+partid+"_tries"].value = "0";
 2952: 	    }
 2953:         }
 2954:     }
 2955: 
 2956:     function resetEntry(numpart) {
 2957: 	for (ctpart=0;ctpart<numpart;ctpart++) {
 2958: 	    var partid = document.classgrade["partid_"+ctpart].value;
 2959: 	    var radioButton = document.classgrade["RADVAL_"+partid];
 2960: 	    var textbox = document.classgrade["TEXTVAL_"+partid];
 2961: 	    var selval  = document.classgrade["SELVAL_"+partid];
 2962: 	    for (var i=0; i<radioButton.length; i++) {
 2963: 		radioButton[i].checked=false;
 2964: 
 2965: 	    }
 2966: 	    textbox.value = "";
 2967: 	    selval[0].selected = true;
 2968: 
 2969: 	    for (i=0;i<document.classgrade.total.value;i++) {
 2970: 		var user = document.classgrade["ctr"+i].value;
 2971: 		user = user.replace(new RegExp(':', 'g'),"_");
 2972: 		var resetscore = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
 2973: 		resetscore.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
 2974: 		var resettries = document.classgrade["GD_"+user+"_"+partid+"_tries"];
 2975: 		resettries.value = document.classgrade["GD_"+user+"_"+partid+"_tries_s"].value;
 2976: 		var saveselval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
 2977: 		var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
 2978: 		if (saveselval == "excused") {
 2979: 		    if (selname[1].selected == false) { selname[1].selected = true;}
 2980: 		} else {
 2981: 		    if (selname[0].selected == false) {selname[0].selected = true};
 2982: 		}
 2983: 	    }
 2984: 	}
 2985:     }
 2986: 
 2987: </script>
 2988: VIEWJAVASCRIPT
 2989: }
 2990: 
 2991: #--- show scores for a section or whole class w/ option to change/update a score
 2992: sub viewgrades {
 2993:     my ($request) = shift;
 2994:     &viewgrades_js($request);
 2995: 
 2996:     my ($symb) = &get_symb($request);
 2997:     #need to make sure we have the correct data for later EXT calls, 
 2998:     #thus invalidate the cache
 2999:     &Apache::lonnet::devalidatecourseresdata(
 3000:                  $env{'course.'.$env{'request.course.id'}.'.num'},
 3001:                  $env{'course.'.$env{'request.course.id'}.'.domain'});
 3002:     &Apache::lonnet::clear_EXT_cache_status();
 3003: 
 3004:     my $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>';
 3005:     $result.='<h4><b>Current Resource: </b>'.$env{'form.probTitle'}.'</h4>'."\n";
 3006: 
 3007:     #view individual student submission form - called using Javascript viewOneStudent
 3008:     $result.=&jscriptNform($symb);
 3009: 
 3010:     #beginning of class grading form
 3011:     $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n".
 3012: 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 3013: 	'<input type="hidden" name="command" value="editgrades" />'."\n".
 3014: 	&build_section_inputs().
 3015: 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
 3016: 	'<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n".
 3017: 	'<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
 3018: 
 3019:     my $sectionClass;
 3020:     my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
 3021:     if ($env{'form.section'} eq 'all') {
 3022: 	$sectionClass='Class </h3>';
 3023:     } elsif ($env{'form.section'} eq 'none') {
 3024: 	$sectionClass=&mt('Students in no Section').'</h3>';
 3025:     } else {
 3026: 	$sectionClass=&mt('Students in Section(s) [_1]',$section_display).'</h3>';
 3027:     }
 3028:     $result.='<h3>'.&mt('Assign Common Grade To [_1]',$sectionClass);
 3029:     $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".
 3030: 	'<table border=0><tr bgcolor="#ffffdd"><td>';
 3031:     #radio buttons/text box for assigning points for a section or class.
 3032:     #handles different parts of a problem
 3033:     my ($partlist,$handgrade,$responseType) = &response_type($symb);
 3034:     my %weight = ();
 3035:     my $ctsparts = 0;
 3036:     $result.='<table border="0">';
 3037:     my %seen = ();
 3038:     my @part_response_id = &flatten_responseType($responseType);
 3039:     foreach my $part_response_id (@part_response_id) {
 3040:     	my ($partid,$respid) = @{ $part_response_id };
 3041: 	my $part_resp = join('_',@{ $part_response_id });
 3042: 	next if $seen{$partid};
 3043: 	$seen{$partid}++;
 3044: 	my $handgrade=$$handgrade{$part_resp};
 3045: 	my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb);
 3046: 	$weight{$partid} = $wgt eq '' ? '1' : $wgt;
 3047: 
 3048: 	$result.='<input type="hidden" name="partid_'.
 3049: 	    $ctsparts.'" value="'.$partid.'" />'."\n";
 3050: 	$result.='<input type="hidden" name="weight_'.
 3051: 	    $partid.'" value="'.$weight{$partid}.'" />'."\n";
 3052: 	my $display_part=&get_display_part($partid,$symb);
 3053: 	$result.='<tr><td><b>Part:</b> '.$display_part.'&nbsp; &nbsp;<b>Point:</b> </td><td>';
 3054: 	$result.='<table border="0"><tr>';  
 3055: 	my $ctr = 0;
 3056: 	while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across
 3057: 	    $result.= '<td><label><input type="radio" name="RADVAL_'.$partid.'" '.
 3058: 		'onclick="javascript:writePoint(\''.$partid.'\','.$weight{$partid}.
 3059: 		','.$ctr.')" />'.$ctr."</label></td>\n";
 3060: 	    $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
 3061: 	    $ctr++;
 3062: 	}
 3063: 	$result.='</tr></table>';
 3064: 	$result.= '</td><td><b> or </b><input type="text" name="TEXTVAL_'.
 3065: 	    $partid.'" size="4" '.'onChange="javascript:writePoint(\''.
 3066: 		$partid.'\','.$weight{$partid}.',\'textval\')" /> /'.
 3067: 	    $weight{$partid}.' (problem weight)</td>'."\n";
 3068: 	$result.= '</td><td><select name="SELVAL_'.$partid.'"'.
 3069: 	    'onChange="javascript:writeRadText(\''.$partid.'\','.
 3070: 		$weight{$partid}.')"> '.
 3071: 	    '<option selected="selected"> </option>'.
 3072: 	    '<option>excused</option>'.
 3073: 	    '<option>reset status</option></select></td>'.
 3074:             '<td><label><input type="checkbox" name="FORCE_'.$partid.'" /> Override "Correct"</label></td></tr>'."\n";
 3075: 	$ctsparts++;
 3076:     }
 3077:     $result.='</table>'.'</td></tr></table>'.'</td></tr></table>'."\n".
 3078: 	'<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';
 3079:     $result.='<input type="button" value="Revert to Default" '.
 3080: 	'onClick="javascript:resetEntry('.$ctsparts.');" target="_self" />';
 3081: 
 3082:     #table listing all the students in a section/class
 3083:     #header of table
 3084:     $result.= '<h3>Assign Grade to Specific Students in '.$sectionClass;
 3085:     $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".
 3086: 	'<table border=0><tr bgcolor="#deffff"><td>&nbsp;<b>No.</b>&nbsp;</td>'.
 3087: 	'<td>'.&nameUserString('header')."</td>\n";
 3088:     my (@parts) = sort(&getpartlist($symb));
 3089:     my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
 3090:     my @partids = ();
 3091:     foreach my $part (@parts) {
 3092: 	my $display=&Apache::lonnet::metadata($url,$part.'.display');
 3093: 	$display =~ s|^Number of Attempts|Tries<br />|; # makes the column narrower
 3094: 	if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }
 3095: 	my ($partid) = &split_part_type($part);
 3096:         push(@partids, $partid);
 3097: 	my $display_part=&get_display_part($partid,$symb);
 3098: 	if ($display =~ /^Partial Credit Factor/) {
 3099: 	    $result.='<td><b>Score Part:</b> '.$display_part.
 3100: 		' <br /><b>(weight = '.$weight{$partid}.')</b></td>'."\n";
 3101: 	    next;
 3102: 	} else {
 3103: 	    $display =~s/\[Part: \Q$partid\E\]/Part:<\/b> $display_part/;
 3104: 	}
 3105: 	$display =~ s|Problem Status|Grade Status<br />|;
 3106: 	$result.='<td><b>'.$display.'</td>'."\n";
 3107:     }
 3108:     $result.='</tr>';
 3109: 
 3110:     my %last_resets = 
 3111: 	&get_last_resets($symb,$env{'request.course.id'},\@partids);
 3112: 
 3113:     #get info for each student
 3114:     #list all the students - with points and grade status
 3115:     my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
 3116:     my $ctr = 0;
 3117:     foreach (sort 
 3118: 	     {
 3119: 		 if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
 3120: 		     return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
 3121: 		 }
 3122: 		 return $a cmp $b;
 3123: 	     } (keys(%$fullname))) {
 3124: 	$ctr++;
 3125: 	$result.=&viewstudentgrade($symb,$env{'request.course.id'},
 3126: 				   $_,$$fullname{$_},\@parts,\%weight,$ctr,\%last_resets);
 3127:     }
 3128:     $result.='</table></td></tr></table>';
 3129:     $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";
 3130:     $result.='<input type="button" value="Save" '.
 3131: 	'onClick="javascript:submit();" target="_self" /></form>'."\n";
 3132:     if (scalar(%$fullname) eq 0) {
 3133: 	my $colspan=3+scalar(@parts);
 3134: 	my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
 3135: 	$result='<span class="LC_warning">'.
 3136: 	    &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade',
 3137: 	        $section_display, $env{'form.Status'}).
 3138: 	    '</span>';
 3139:     }
 3140:     $result.=&show_grading_menu_form($symb);
 3141:     return $result;
 3142: }
 3143: 
 3144: #--- call by previous routine to display each student
 3145: sub viewstudentgrade {
 3146:     my ($symb,$courseid,$student,$fullname,$parts,$weight,$ctr,$last_resets) = @_;
 3147:     my ($uname,$udom) = split(/:/,$student);
 3148:     my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
 3149:     my %aggregates = (); 
 3150:     my $result='<tr bgcolor="#ffffdd"><td align="right">'.
 3151: 	'<input type="hidden" name="ctr'.($ctr-1).'" value="'.$student.'" />'.
 3152: 	"\n".$ctr.'&nbsp;</td><td>&nbsp;'.
 3153: 	'<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
 3154: 	'\');" target="_self">'.$fullname.'</a> '.
 3155: 	'<span class="LC_internal_info">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</span></td>'."\n";
 3156:     $student=~s/:/_/; # colon doen't work in javascript for names
 3157:     foreach my $apart (@$parts) {
 3158: 	my ($part,$type) = &split_part_type($apart);
 3159: 	my $score=$record{"resource.$part.$type"};
 3160:         $result.='<td align="center">';
 3161:         my ($aggtries,$totaltries);
 3162:         unless (exists($aggregates{$part})) {
 3163: 	    $totaltries = $record{'resource.'.$part.'.tries'};
 3164: 
 3165: 	    $aggtries = $totaltries;
 3166:             if ($$last_resets{$part}) {  
 3167:                 $aggtries = &get_num_tries(\%record,$$last_resets{$part},
 3168: 					   $part);
 3169:             }
 3170:             $result.='<input type="hidden" name="'.
 3171:                 'GD_'.$student.'_'.$part.'_aggtries" value="'.$aggtries.'" />'."\n";
 3172:             $result.='<input type="hidden" name="'.
 3173:                 'GD_'.$student.'_'.$part.'_totaltries" value="'.$totaltries.'" />'."\n";
 3174:             $aggregates{$part} = 1;
 3175:         }
 3176: 	if ($type eq 'awarded') {
 3177: 	    my $pts = $score eq '' ? '' : &compute_points($score,$$weight{$part});
 3178: 	    $result.='<input type="hidden" name="'.
 3179: 		'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";
 3180: 	    $result.='<input type="text" name="'.
 3181: 		'GD_'.$student.'_'.$part.'_awarded" '.
 3182: 		'onChange="javascript:changeSelect(\''.$part.'\',\''.$student.
 3183: 		'\')" value="'.$pts.'" size="4" /></td>'."\n";
 3184: 	} elsif ($type eq 'solved') {
 3185: 	    my ($status,$foo)=split(/_/,$score,2);
 3186: 	    $status = 'nothing' if ($status eq '');
 3187: 	    $result.='<input type="hidden" name="'.'GD_'.$student.'_'.
 3188: 		$part.'_solved_s" value="'.$status.'" />'."\n";
 3189: 	    $result.='&nbsp;<select name="'.
 3190: 		'GD_'.$student.'_'.$part.'_solved" '.
 3191: 		'onChange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n";
 3192: 	    $result.= (($status eq 'excused') ? '<option> </option><option selected="selected">excused</option>' 
 3193: 		: '<option selected="selected"> </option><option>excused</option>')."\n";
 3194: 	    $result.='<option>reset status</option>';
 3195: 	    $result.="</select>&nbsp;</td>\n";
 3196: 	} else {
 3197: 	    $result.='<input type="hidden" name="'.
 3198: 		'GD_'.$student.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'.
 3199: 		    "\n";
 3200: 	    $result.='<input type="text" name="'.
 3201: 		'GD_'.$student.'_'.$part.'_'.$type.'" '.
 3202: 		'value="'.$score.'" size="4" /></td>'."\n";
 3203: 	}
 3204:     }
 3205:     $result.='</tr>';
 3206:     return $result;
 3207: }
 3208: 
 3209: #--- change scores for all the students in a section/class
 3210: #    record does not get update if unchanged
 3211: sub editgrades {
 3212:     my ($request) = @_;
 3213: 
 3214:     my $symb=&get_symb($request);
 3215:     my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
 3216:     my $title='<h3><span class="LC_info">'.&mt('Current Grade Status').'</span></h3>';
 3217:     $title.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4><br />'."\n";
 3218:     $title.='<h4>'.&mt('<b>Section: </b>[_1]',$section_display).'</h4>'."\n";
 3219: 
 3220:     my $result= '<table border="0"><tr><td bgcolor="#777777">'."\n";
 3221:     $result.= '<table border="0"><tr bgcolor="#deffff">'.
 3222: 	'<td rowspan=2 valign="center">&nbsp;<b>No.</b>&nbsp;</td>'.
 3223: 	'<td rowspan=2 valign="center">'.&nameUserString('header')."</td>\n";
 3224: 
 3225:     my %scoreptr = (
 3226: 		    'correct'  =>'correct_by_override',
 3227: 		    'incorrect'=>'incorrect_by_override',
 3228: 		    'excused'  =>'excused',
 3229: 		    'ungraded' =>'ungraded_attempted',
 3230: 		    'nothing'  => '',
 3231: 		    );
 3232:     my ($classlist,undef,$fullname) = &getclasslist($env{'form.section'},'0');
 3233: 
 3234:     my (@partid);
 3235:     my %weight = ();
 3236:     my %columns = ();
 3237:     my ($i,$ctr,$count,$rec_update) = (0,0,0,0);
 3238: 
 3239:     my (@parts) = sort(&getpartlist($symb));
 3240:     my $header;
 3241:     while ($ctr < $env{'form.totalparts'}) {
 3242: 	my $partid = $env{'form.partid_'.$ctr};
 3243: 	push @partid,$partid;
 3244: 	$weight{$partid} = $env{'form.weight_'.$partid};
 3245: 	$ctr++;
 3246:     }
 3247:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
 3248:     foreach my $partid (@partid) {
 3249: 	$header .= '<td align="center">&nbsp;<b>Old Score</b>&nbsp;</td>'.
 3250: 	    '<td align="center">&nbsp;<b>New Score</b>&nbsp;</td>';
 3251: 	$columns{$partid}=2;
 3252: 	foreach my $stores (@parts) {
 3253: 	    my ($part,$type) = &split_part_type($stores);
 3254: 	    if ($part !~ m/^\Q$partid\E/) { next;}
 3255: 	    if ($type eq 'awarded' || $type eq 'solved') { next; }
 3256: 	    my $display=&Apache::lonnet::metadata($url,$stores.'.display');
 3257: 	    $display =~ s/\[Part: (\w)+\]//;
 3258: 	    $display =~ s/Number of Attempts/Tries/;
 3259: 	    $header .= '<td align="center">&nbsp;<b>Old '.$display.'</b>&nbsp;</td>'.
 3260: 		'<td align="center">&nbsp;<b>New '.$display.'</b>&nbsp;</td>';
 3261: 	    $columns{$partid}+=2;
 3262: 	}
 3263:     }
 3264:     foreach my $partid (@partid) {
 3265: 	my $display_part=&get_display_part($partid,$symb);
 3266: 	$result .= '<td colspan="'.$columns{$partid}.
 3267: 	    '" align="center"><b>Part:</b> '.$display_part.
 3268: 	    ' (Weight = '.$weight{$partid}.')</td>';
 3269: 
 3270:     }
 3271:     $result .= '</tr><tr bgcolor="#deffff">';
 3272:     $result .= $header;
 3273:     $result .= '</tr>'."\n";
 3274:     my $noupdate;
 3275:     my ($updateCtr,$noupdateCtr) = (1,1);
 3276:     for ($i=0; $i<$env{'form.total'}; $i++) {
 3277: 	my $line;
 3278: 	my $user = $env{'form.ctr'.$i};
 3279: 	my ($uname,$udom)=split(/:/,$user);
 3280: 	my %newrecord;
 3281: 	my $updateflag = 0;
 3282: 	$line .= '<td>'.&nameUserString(undef,$$fullname{$user},$uname,$udom).'</td>';
 3283: 	my $usec=$classlist->{"$uname:$udom"}[5];
 3284: 	if (!&canmodify($usec)) {
 3285: 	    my $numcols=scalar(@partid)*4+2;
 3286: 	    $noupdate.=$line."<td colspan=\"$numcols\"><span class=\"LC_warning\">Not allowed to modify student</span></td></tr>";
 3287: 	    next;
 3288: 	}
 3289:         my %aggregate = ();
 3290:         my $aggregateflag = 0;
 3291: 	$user=~s/:/_/; # colon doen't work in javascript for names
 3292: 	foreach (@partid) {
 3293: 	    my $old_aw    = $env{'form.GD_'.$user.'_'.$_.'_awarded_s'};
 3294: 	    my $old_part_pcr = $old_aw/($weight{$_} ne '0' ? $weight{$_}:1);
 3295: 	    my $old_part  = $old_aw eq '' ? '' : $old_part_pcr;
 3296: 	    my $old_score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
 3297: 	    my $awarded   = $env{'form.GD_'.$user.'_'.$_.'_awarded'};
 3298: 	    my $pcr       = $awarded/($weight{$_} ne '0' ? $weight{$_} : 1);
 3299: 	    my $partial   = $awarded eq '' ? '' : $pcr;
 3300: 	    my $score;
 3301: 	    if ($partial eq '') {
 3302: 		$score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
 3303: 	    } elsif ($partial > 0) {
 3304: 		$score = 'correct_by_override';
 3305: 	    } elsif ($partial == 0) {
 3306: 		$score = 'incorrect_by_override';
 3307: 	    }
 3308: 	    my $dropMenu = $env{'form.GD_'.$user.'_'.$_.'_solved'};
 3309: 	    $score = 'excused' if (($dropMenu eq 'excused') && ($score ne 'excused'));
 3310: 
 3311: 	    $newrecord{'resource.'.$_.'.regrader'}=
 3312: 		"$env{'user.name'}:$env{'user.domain'}";
 3313: 	    if ($dropMenu eq 'reset status' &&
 3314: 		$old_score ne '') { # ignore if no previous attempts => nothing to reset
 3315: 		$newrecord{'resource.'.$_.'.tries'} = '';
 3316: 		$newrecord{'resource.'.$_.'.solved'} = '';
 3317: 		$newrecord{'resource.'.$_.'.award'} = '';
 3318: 		$newrecord{'resource.'.$_.'.awarded'} = '';
 3319: 		$updateflag = 1;
 3320:                 if ($env{'form.GD_'.$user.'_'.$_.'_aggtries'} > 0) {
 3321:                     my $aggtries = $env{'form.GD_'.$user.'_'.$_.'_aggtries'};
 3322:                     my $totaltries = $env{'form.GD_'.$user.'_'.$_.'_totaltries'};
 3323:                     my $solvedstatus = $env{'form.GD_'.$user.'_'.$_.'_solved_s'};
 3324:                     &decrement_aggs($symb,$_,\%aggregate,$aggtries,$totaltries,$solvedstatus);
 3325:                     $aggregateflag = 1;
 3326:                 }
 3327: 	    } elsif (!($old_part eq $partial && $old_score eq $score)) {
 3328: 		$updateflag = 1;
 3329: 		$newrecord{'resource.'.$_.'.awarded'}  = $partial if $partial ne '';
 3330: 		$newrecord{'resource.'.$_.'.solved'}   = $score;
 3331: 		$rec_update++;
 3332: 	    }
 3333: 
 3334: 	    $line .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
 3335: 		'<td align="center">'.$awarded.
 3336: 		($score eq 'excused' ? $score : '').'&nbsp;</td>';
 3337: 
 3338: 
 3339: 	    my $partid=$_;
 3340: 	    foreach my $stores (@parts) {
 3341: 		my ($part,$type) = &split_part_type($stores);
 3342: 		if ($part !~ m/^\Q$partid\E/) { next;}
 3343: 		if ($type eq 'awarded' || $type eq 'solved') { next; }
 3344: 		my $old_aw    = $env{'form.GD_'.$user.'_'.$part.'_'.$type.'_s'};
 3345: 		my $awarded   = $env{'form.GD_'.$user.'_'.$part.'_'.$type};
 3346: 		if ($awarded ne '' && $awarded ne $old_aw) {
 3347: 		    $newrecord{'resource.'.$part.'.'.$type}= $awarded;
 3348: 		    $newrecord{'resource.'.$part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
 3349: 		    $updateflag=1;
 3350: 		}
 3351: 		$line .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
 3352: 		    '<td align="center">'.$awarded.'&nbsp;</td>';
 3353: 	    }
 3354: 	}
 3355: 	$line.='</tr>'."\n";
 3356: 
 3357: 	my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
 3358: 	my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
 3359: 
 3360: 	if ($updateflag) {
 3361: 	    $count++;
 3362: 	    &Apache::lonnet::cstore(\%newrecord,$symb,$env{'request.course.id'},
 3363: 				    $udom,$uname);
 3364: 
 3365: 	    if (&Apache::bridgetask::in_queue('gradingqueue',$symb,$cdom,
 3366: 					      $cnum,$udom,$uname)) {
 3367: 		# need to figure out if should be in queue.
 3368: 		my %record =  
 3369: 		    &Apache::lonnet::restore($symb,$env{'request.course.id'},
 3370: 					     $udom,$uname);
 3371: 		my $all_graded = 1;
 3372: 		my $none_graded = 1;
 3373: 		foreach my $part (@parts) {
 3374: 		    if ( $record{'resource.'.$part.'.awarded'} eq '' ) {
 3375: 			$all_graded = 0;
 3376: 		    } else {
 3377: 			$none_graded = 0;
 3378: 		    }
 3379: 		}
 3380: 
 3381: 		if ($all_graded || $none_graded) {
 3382: 		    &Apache::bridgetask::remove_from_queue('gradingqueue',
 3383: 							   $symb,$cdom,$cnum,
 3384: 							   $udom,$uname);
 3385: 		}
 3386: 	    }
 3387: 
 3388: 	    $result.='<tr bgcolor="#ffffde"><td align="right">&nbsp;'.$updateCtr.'&nbsp;</td>'.$line;
 3389: 	    $updateCtr++;
 3390: 	} else {
 3391: 	    $noupdate.='<tr bgcolor="#ffffde"><td align="right">&nbsp;'.$noupdateCtr.'&nbsp;</td>'.$line;
 3392: 	    $noupdateCtr++;
 3393: 	}
 3394:         if ($aggregateflag) {
 3395:             &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
 3396: 				  $cdom,$cnum);
 3397:         }
 3398:     }
 3399:     if ($noupdate) {
 3400: #	my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3;
 3401: 	my $numcols=scalar(@partid)*4+2;
 3402: 	$result .= '<tr bgcolor="#ffffff"><td align="center" colspan="'.$numcols.'">No Changes Occurred For the Students Below</td></tr><tr bgcolor="#ffffde">'.$noupdate;
 3403:     }
 3404:     $result .= '</table></td></tr></table>'."\n".
 3405: 	&show_grading_menu_form ($symb);
 3406:     my $msg = '<br /><b>Number of records updated = '.$rec_update.
 3407: 	' for '.$count.' student'.($count <= 1 ? '' : 's').'.</b><br />'.
 3408: 	'<b>Total number of students = '.$env{'form.total'}.'</b><br />';
 3409:     return $title.$msg.$result;
 3410: }
 3411: 
 3412: sub split_part_type {
 3413:     my ($partstr) = @_;
 3414:     my ($temp,@allparts)=split(/_/,$partstr);
 3415:     my $type=pop(@allparts);
 3416:     my $part=join('.',@allparts);
 3417:     return ($part,$type);
 3418: }
 3419: 
 3420: #------------- end of section for handling grading by section/class ---------
 3421: #
 3422: #----------------------------------------------------------------------------
 3423: 
 3424: 
 3425: #----------------------------------------------------------------------------
 3426: #
 3427: #-------------------------- Next few routines handles grading by csv upload
 3428: #
 3429: #--- Javascript to handle csv upload
 3430: sub csvupload_javascript_reverse_associate {
 3431:     my $error1=&mt('You need to specify the username or ID');
 3432:     my $error2=&mt('You need to specify at least one grading field');
 3433:   return(<<ENDPICK);
 3434:   function verify(vf) {
 3435:     var foundsomething=0;
 3436:     var founduname=0;
 3437:     var foundID=0;
 3438:     for (i=0;i<=vf.nfields.value;i++) {
 3439:       tw=eval('vf.f'+i+'.selectedIndex');
 3440:       if (i==0 && tw!=0) { foundID=1; }
 3441:       if (i==1 && tw!=0) { founduname=1; }
 3442:       if (i!=0 && i!=1 && i!=2 && tw!=0) { foundsomething=1; }
 3443:     }
 3444:     if (founduname==0 && foundID==0) {
 3445: 	alert('$error1');
 3446: 	return;
 3447:     }
 3448:     if (foundsomething==0) {
 3449: 	alert('$error2');
 3450: 	return;
 3451:     }
 3452:     vf.submit();
 3453:   }
 3454:   function flip(vf,tf) {
 3455:     var nw=eval('vf.f'+tf+'.selectedIndex');
 3456:     var i;
 3457:     for (i=0;i<=vf.nfields.value;i++) {
 3458:       //can not pick the same destination field for both name and domain
 3459:       if (((i ==0)||(i ==1)) && 
 3460:           ((tf==0)||(tf==1)) && 
 3461:           (i!=tf) &&
 3462:           (eval('vf.f'+i+'.selectedIndex')==nw)) {
 3463:         eval('vf.f'+i+'.selectedIndex=0;')
 3464:       }
 3465:     }
 3466:   }
 3467: ENDPICK
 3468: }
 3469: 
 3470: sub csvupload_javascript_forward_associate {
 3471:     my $error1=&mt('You need to specify the username or ID');
 3472:     my $error2=&mt('You need to specify at least one grading field');
 3473:   return(<<ENDPICK);
 3474:   function verify(vf) {
 3475:     var foundsomething=0;
 3476:     var founduname=0;
 3477:     var foundID=0;
 3478:     for (i=0;i<=vf.nfields.value;i++) {
 3479:       tw=eval('vf.f'+i+'.selectedIndex');
 3480:       if (tw==1) { foundID=1; }
 3481:       if (tw==2) { founduname=1; }
 3482:       if (tw>3) { foundsomething=1; }
 3483:     }
 3484:     if (founduname==0 && foundID==0) {
 3485: 	alert('$error1');
 3486: 	return;
 3487:     }
 3488:     if (foundsomething==0) {
 3489: 	alert('$error2');
 3490: 	return;
 3491:     }
 3492:     vf.submit();
 3493:   }
 3494:   function flip(vf,tf) {
 3495:     var nw=eval('vf.f'+tf+'.selectedIndex');
 3496:     var i;
 3497:     //can not pick the same destination field twice
 3498:     for (i=0;i<=vf.nfields.value;i++) {
 3499:       if ((i!=tf) && (eval('vf.f'+i+'.selectedIndex')==nw)) {
 3500:         eval('vf.f'+i+'.selectedIndex=0;')
 3501:       }
 3502:     }
 3503:   }
 3504: ENDPICK
 3505: }
 3506: 
 3507: sub csvuploadmap_header {
 3508:     my ($request,$symb,$datatoken,$distotal)= @_;
 3509:     my $javascript;
 3510:     if ($env{'form.upfile_associate'} eq 'reverse') {
 3511: 	$javascript=&csvupload_javascript_reverse_associate();
 3512:     } else {
 3513: 	$javascript=&csvupload_javascript_forward_associate();
 3514:     }
 3515: 
 3516:     my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
 3517:     my $checked=(($env{'form.noFirstLine'})?' checked="checked"':'');
 3518:     my $ignore=&mt('Ignore First Line');
 3519:     $symb = &Apache::lonenc::check_encrypt($symb);
 3520:     $request->print(<<ENDPICK);
 3521: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 3522: <h3><span class="LC_info">Uploading Class Grades</span></h3>
 3523: $result
 3524: <hr />
 3525: <h3>Identify fields</h3>
 3526: Total number of records found in file: $distotal <hr />
 3527: Enter as many fields as you can. The system will inform you and bring you back
 3528: to this page if the data selected is insufficient to run your class.<hr />
 3529: <input type="button" value="Reverse Association" onClick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" />
 3530: <label><input type="checkbox" name="noFirstLine" $checked />$ignore</label>
 3531: <input type="hidden" name="associate"  value="" />
 3532: <input type="hidden" name="phase"      value="three" />
 3533: <input type="hidden" name="datatoken"  value="$datatoken" />
 3534: <input type="hidden" name="fileupload" value="$env{'form.fileupload'}" />
 3535: <input type="hidden" name="upfiletype" value="$env{'form.upfiletype'}" />
 3536: <input type="hidden" name="upfile_associate" 
 3537:                                        value="$env{'form.upfile_associate'}" />
 3538: <input type="hidden" name="symb"       value="$symb" />
 3539: <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
 3540: <input type="hidden" name="probTitle"  value="$env{'form.probTitle'}" />
 3541: <input type="hidden" name="command"    value="csvuploadoptions" />
 3542: <hr />
 3543: <script type="text/javascript" language="Javascript">
 3544: $javascript
 3545: </script>
 3546: ENDPICK
 3547:     return '';
 3548: 
 3549: }
 3550: 
 3551: sub csvupload_fields {
 3552:     my ($symb) = @_;
 3553:     my (@parts) = &getpartlist($symb);
 3554:     my @fields=(['ID','Student ID'],
 3555: 		['username','Student Username'],
 3556: 		['domain','Student Domain']);
 3557:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
 3558:     foreach my $part (sort(@parts)) {
 3559: 	my @datum;
 3560: 	my $display=&Apache::lonnet::metadata($url,$part.'.display');
 3561: 	my $name=$part;
 3562: 	if  (!$display) { $display = $name; }
 3563: 	@datum=($name,$display);
 3564: 	if ($name=~/^stores_(.*)_awarded/) {
 3565: 	    push(@fields,['stores_'.$1.'_points',"Points [Part: $1]"]);
 3566: 	}
 3567: 	push(@fields,\@datum);
 3568:     }
 3569:     return (@fields);
 3570: }
 3571: 
 3572: sub csvuploadmap_footer {
 3573:     my ($request,$i,$keyfields) =@_;
 3574:     $request->print(<<ENDPICK);
 3575: </table>
 3576: <input type="hidden" name="nfields" value="$i" />
 3577: <input type="hidden" name="keyfields" value="$keyfields" />
 3578: <input type="button" onClick="javascript:verify(this.form)" value="Assign Grades" /><br />
 3579: </form>
 3580: ENDPICK
 3581: }
 3582: 
 3583: sub checkforfile_js {
 3584:     my $result =<<CSVFORMJS;
 3585: <script type="text/javascript" language="javascript">
 3586:     function checkUpload(formname) {
 3587: 	if (formname.upfile.value == "") {
 3588: 	    alert("Please use the browse button to select a file from your local directory.");
 3589: 	    return false;
 3590: 	}
 3591: 	formname.submit();
 3592:     }
 3593:     </script>
 3594: CSVFORMJS
 3595:     return $result;
 3596: }
 3597: 
 3598: sub upcsvScores_form {
 3599:     my ($request) = shift;
 3600:     my ($symb)=&get_symb($request);
 3601:     if (!$symb) {return '';}
 3602:     my $result=&checkforfile_js();
 3603:     $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
 3604:     my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});
 3605:     $result.=$table;
 3606:     $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";
 3607:     $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";
 3608:     $result.='&nbsp;<b>'.&mt('Specify a file containing the class scores for current resource').
 3609: 	'.</b></td></tr>'."\n";
 3610:     $result.='<tr bgcolor=#ffffe6><td>'."\n";
 3611:     my $upload=&mt("Upload Scores");
 3612:     my $upfile_select=&Apache::loncommon::upfile_select_html();
 3613:     my $ignore=&mt('Ignore First Line');
 3614:     $symb = &Apache::lonenc::check_encrypt($symb);
 3615:     $result.=<<ENDUPFORM;
 3616: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 3617: <input type="hidden" name="symb" value="$symb" />
 3618: <input type="hidden" name="command" value="csvuploadmap" />
 3619: <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
 3620: <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
 3621: $upfile_select
 3622: <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="$upload" />
 3623: <label><input type="checkbox" name="noFirstLine" />$ignore</label>
 3624: </form>
 3625: ENDUPFORM
 3626:     $result.=&Apache::loncommon::help_open_topic("Course_Convert_To_CSV",
 3627:                            &mt("How do I create a CSV file from a spreadsheet"))
 3628:     .'</td></tr></table>'."\n";
 3629:     $result.='</td></tr></table><br /><br />'."\n";
 3630:     $result.=&show_grading_menu_form($symb);
 3631:     return $result;
 3632: }
 3633: 
 3634: 
 3635: sub csvuploadmap {
 3636:     my ($request)= @_;
 3637:     my ($symb)=&get_symb($request);
 3638:     if (!$symb) {return '';}
 3639: 
 3640:     my $datatoken;
 3641:     if (!$env{'form.datatoken'}) {
 3642: 	$datatoken=&Apache::loncommon::upfile_store($request);
 3643:     } else {
 3644: 	$datatoken=$env{'form.datatoken'};
 3645: 	&Apache::loncommon::load_tmp_file($request);
 3646:     }
 3647:     my @records=&Apache::loncommon::upfile_record_sep();
 3648:     if ($env{'form.noFirstLine'}) { shift(@records); }
 3649:     &csvuploadmap_header($request,$symb,$datatoken,$#records+1);
 3650:     my ($i,$keyfields);
 3651:     if (@records) {
 3652: 	my @fields=&csvupload_fields($symb);
 3653: 
 3654: 	if ($env{'form.upfile_associate'} eq 'reverse') {	
 3655: 	    &Apache::loncommon::csv_print_samples($request,\@records);
 3656: 	    $i=&Apache::loncommon::csv_print_select_table($request,\@records,
 3657: 							  \@fields);
 3658: 	    foreach (@fields) { $keyfields.=$_->[0].','; }
 3659: 	    chop($keyfields);
 3660: 	} else {
 3661: 	    unshift(@fields,['none','']);
 3662: 	    $i=&Apache::loncommon::csv_samples_select_table($request,\@records,
 3663: 							    \@fields);
 3664:             foreach my $rec (@records) {
 3665:                 my %temp = &Apache::loncommon::record_sep($rec);
 3666:                 if (%temp) {
 3667:                     $keyfields=join(',',sort(keys(%temp)));
 3668:                     last;
 3669:                 }
 3670:             }
 3671: 	}
 3672:     }
 3673:     &csvuploadmap_footer($request,$i,$keyfields);
 3674:     $request->print(&show_grading_menu_form($symb));
 3675: 
 3676:     return '';
 3677: }
 3678: 
 3679: sub csvuploadoptions {
 3680:     my ($request)= @_;
 3681:     my ($symb)=&get_symb($request);
 3682:     my $checked=(($env{'form.noFirstLine'})?'1':'0');
 3683:     my $ignore=&mt('Ignore First Line');
 3684:     $request->print(<<ENDPICK);
 3685: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 3686: <h3><span class="LC_info">Uploading Class Grade Options</span></h3>
 3687: <input type="hidden" name="command"    value="csvuploadassign" />
 3688: <!--
 3689: <p>
 3690: <label>
 3691:    <input type="checkbox" name="show_full_results" />
 3692:    Show a table of all changes
 3693: </label>
 3694: </p>
 3695: -->
 3696: <p>
 3697: <label>
 3698:    <input type="checkbox" name="overwite_scores" checked="checked" />
 3699:    Overwrite any existing score
 3700: </label>
 3701: </p>
 3702: ENDPICK
 3703:     my %fields=&get_fields();
 3704:     if (!defined($fields{'domain'})) {
 3705: 	my $domform = &Apache::loncommon::select_dom_form($env{'request.role.domain'},'default_domain');
 3706: 	$request->print("\n<p> Users are in domain: ".$domform."</p>\n");
 3707:     }
 3708:     foreach my $key (sort(keys(%env))) {
 3709: 	if ($key !~ /^form\.(.*)$/) { next; }
 3710: 	my $cleankey=$1;
 3711: 	if ($cleankey eq 'command') { next; }
 3712: 	$request->print('<input type="hidden" name="'.$cleankey.
 3713: 			'"  value="'.$env{$key}.'" />'."\n");
 3714:     }
 3715:     # FIXME do a check for any duplicated user ids...
 3716:     # FIXME do a check for any invalid user ids?...
 3717:     $request->print('<input type="submit" value="Assign Grades" /><br />
 3718: <hr /></form>'."\n");
 3719:     $request->print(&show_grading_menu_form($symb));
 3720:     return '';
 3721: }
 3722: 
 3723: sub get_fields {
 3724:     my %fields;
 3725:     my @keyfields = split(/\,/,$env{'form.keyfields'});
 3726:     for (my $i=0; $i<=$env{'form.nfields'}; $i++) {
 3727: 	if ($env{'form.upfile_associate'} eq 'reverse') {
 3728: 	    if ($env{'form.f'.$i} ne 'none') {
 3729: 		$fields{$keyfields[$i]}=$env{'form.f'.$i};
 3730: 	    }
 3731: 	} else {
 3732: 	    if ($env{'form.f'.$i} ne 'none') {
 3733: 		$fields{$env{'form.f'.$i}}=$keyfields[$i];
 3734: 	    }
 3735: 	}
 3736:     }
 3737:     return %fields;
 3738: }
 3739: 
 3740: sub csvuploadassign {
 3741:     my ($request)= @_;
 3742:     my ($symb)=&get_symb($request);
 3743:     if (!$symb) {return '';}
 3744:     my $error_msg = '';
 3745:     &Apache::loncommon::load_tmp_file($request);
 3746:     my @gradedata = &Apache::loncommon::upfile_record_sep();
 3747:     if ($env{'form.noFirstLine'}) { shift(@gradedata); }
 3748:     my %fields=&get_fields();
 3749:     $request->print('<h3>Assigning Grades</h3>');
 3750:     my $courseid=$env{'request.course.id'};
 3751:     my ($classlist) = &getclasslist('all',0);
 3752:     my @notallowed;
 3753:     my @skipped;
 3754:     my $countdone=0;
 3755:     foreach my $grade (@gradedata) {
 3756: 	my %entries=&Apache::loncommon::record_sep($grade);
 3757: 	my $domain;
 3758: 	if ($entries{$fields{'domain'}}) {
 3759: 	    $domain=$entries{$fields{'domain'}};
 3760: 	} else {
 3761: 	    $domain=$env{'form.default_domain'};
 3762: 	}
 3763: 	$domain=~s/\s//g;
 3764: 	my $username=$entries{$fields{'username'}};
 3765: 	$username=~s/\s//g;
 3766: 	if (!$username) {
 3767: 	    my $id=$entries{$fields{'ID'}};
 3768: 	    $id=~s/\s//g;
 3769: 	    my %ids=&Apache::lonnet::idget($domain,$id);
 3770: 	    $username=$ids{$id};
 3771: 	}
 3772: 	if (!exists($$classlist{"$username:$domain"})) {
 3773: 	    my $id=$entries{$fields{'ID'}};
 3774: 	    $id=~s/\s//g;
 3775: 	    if ($id) {
 3776: 		push(@skipped,"$id:$domain");
 3777: 	    } else {
 3778: 		push(@skipped,"$username:$domain");
 3779: 	    }
 3780: 	    next;
 3781: 	}
 3782: 	my $usec=$classlist->{"$username:$domain"}[5];
 3783: 	if (!&canmodify($usec)) {
 3784: 	    push(@notallowed,"$username:$domain");
 3785: 	    next;
 3786: 	}
 3787: 	my %points;
 3788: 	my %grades;
 3789: 	foreach my $dest (keys(%fields)) {
 3790: 	    if ($dest eq 'ID' || $dest eq 'username' ||
 3791: 		$dest eq 'domain') { next; }
 3792: 	    if ($entries{$fields{$dest}} =~ /^\s*$/) { next; }
 3793: 	    if ($dest=~/stores_(.*)_points/) {
 3794: 		my $part=$1;
 3795: 		my $wgt =&Apache::lonnet::EXT('resource.'.$part.'.weight',
 3796: 					      $symb,$domain,$username);
 3797:                 if ($wgt) {
 3798:                     $entries{$fields{$dest}}=~s/\s//g;
 3799:                     my $pcr=$entries{$fields{$dest}} / $wgt;
 3800:                     my $award='correct_by_override';
 3801:                     $grades{"resource.$part.awarded"}=$pcr;
 3802:                     $grades{"resource.$part.solved"}=$award;
 3803:                     $points{$part}=1;
 3804:                 } else {
 3805:                     $error_msg = "<br />" .
 3806:                         &mt("Some point values were assigned"
 3807:                             ." for problems with a weight "
 3808:                             ."of zero. These values were "
 3809:                             ."ignored.");
 3810:                 }
 3811: 	    } else {
 3812: 		if ($dest=~/stores_(.*)_awarded/) { if ($points{$1}) {next;} }
 3813: 		if ($dest=~/stores_(.*)_solved/)  { if ($points{$1}) {next;} }
 3814: 		my $store_key=$dest;
 3815: 		$store_key=~s/^stores/resource/;
 3816: 		$store_key=~s/_/\./g;
 3817: 		$grades{$store_key}=$entries{$fields{$dest}};
 3818: 	    }
 3819: 	}
 3820: 	if (! %grades) { push(@skipped,"$username:$domain no data to save"); }
 3821: 	$grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
 3822: #	&Apache::lonnet::logthis(" storing ".(join('-',%grades)));
 3823: 	my $result=&Apache::lonnet::cstore(\%grades,$symb,
 3824: 					   $env{'request.course.id'},
 3825: 					   $domain,$username);
 3826: 	if ($result eq 'ok') {
 3827: 	    $request->print('.');
 3828: 	} else {
 3829: 	    $request->print("<p>
 3830:                               <span class=\"LC_error\">
 3831:                                  Failed to save student $username:$domain.
 3832:                                  Message when trying to save was ($result)
 3833:                               </span>
 3834:                              </p>" );
 3835: 	}
 3836: 	$request->rflush();
 3837: 	$countdone++;
 3838:     }
 3839:     $request->print("<br />Saved $countdone students\n");
 3840:     if (@skipped) {
 3841: 	$request->print('<p><h4><b>Skipped Students</b></h4></p>');
 3842: 	foreach my $student (@skipped) { $request->print("$student<br />\n"); }
 3843:     }
 3844:     if (@notallowed) {
 3845: 	$request->print('<p><span class="LC_error">Students Not Allowed to Modify</span></p>');
 3846: 	foreach my $student (@notallowed) { $request->print("$student<br />\n"); }
 3847:     }
 3848:     $request->print("<br />\n");
 3849:     $request->print(&show_grading_menu_form($symb));
 3850:     return $error_msg;
 3851: }
 3852: #------------- end of section for handling csv file upload ---------
 3853: #
 3854: #-------------------------------------------------------------------
 3855: #
 3856: #-------------- Next few routines handle grading by page/sequence
 3857: #
 3858: #--- Select a page/sequence and a student to grade
 3859: sub pickStudentPage {
 3860:     my ($request) = shift;
 3861: 
 3862:     $request->print(<<LISTJAVASCRIPT);
 3863: <script type="text/javascript" language="javascript">
 3864: 
 3865: function checkPickOne(formname) {
 3866:     if (radioSelection(formname.student) == null) {
 3867: 	alert("Please select the student you wish to grade.");
 3868: 	return;
 3869:     }
 3870:     ptr = pullDownSelection(formname.selectpage);
 3871:     formname.page.value = formname["page"+ptr].value;
 3872:     formname.title.value = formname["title"+ptr].value;
 3873:     formname.submit();
 3874: }
 3875: 
 3876: </script>
 3877: LISTJAVASCRIPT
 3878:     &commonJSfunctions($request);
 3879:     my ($symb) = &get_symb($request);
 3880:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
 3881:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
 3882:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
 3883: 
 3884:     my $result='<h3><span class="LC_info">&nbsp;'.
 3885: 	'Manual Grading by Page or Sequence</span></h3>';
 3886: 
 3887:     $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";
 3888:     $result.='&nbsp;<b>Problems from:</b> <select name="selectpage">'."\n";
 3889:     my ($titles,$symbx) = &getSymbMap();
 3890:     my ($curpage) =&Apache::lonnet::decode_symb($symb); 
 3891: #    my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb); 
 3892: #    my $type=($curpage =~ /\.(page|sequence)/);
 3893:     my $ctr=0;
 3894:     foreach (@$titles) {
 3895: 	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
 3896: 	$result.='<option value="'.$ctr.'" '.
 3897: 	    ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
 3898: 	    '>'.$showtitle.'</option>'."\n";
 3899: 	$ctr++;
 3900:     }
 3901:     $result.= '</select>'."<br />\n";
 3902:     $ctr=0;
 3903:     foreach (@$titles) {
 3904: 	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
 3905: 	$result.='<input type="hidden" name="page'.$ctr.'" value="'.$$symbx{$_}.'" />'."\n";
 3906: 	$result.='<input type="hidden" name="title'.$ctr.'" value="'.$showtitle.'" />'."\n";
 3907: 	$ctr++;
 3908:     }
 3909:     $result.='<input type="hidden" name="page" />'."\n".
 3910: 	'<input type="hidden" name="title" />'."\n";
 3911: 
 3912:     $result.='&nbsp;<b>View Problems Text: </b><label><input type="radio" name="vProb" value="no" checked="checked" /> no </label>'."\n".
 3913: 	'<label><input type="radio" name="vProb" value="yes" /> yes </label>'."<br />\n";
 3914: 
 3915:     $result.='&nbsp;<b>Submission Details: </b>'.
 3916: 	'<label><input type="radio" name="lastSub" value="none" /> none</label>'."\n".
 3917: 	'<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> by dates and submissions</label>'."\n".
 3918: 	'<label><input type="radio" name="lastSub" value="all" /> all details</label>'."\n";
 3919:     
 3920:     $result.=&build_section_inputs();
 3921:     $result.='<input type="hidden" name="Status"  value="'.$env{'form.Status'}.'" />'."\n".
 3922: 	'<input type="hidden" name="command" value="displayPage" />'."\n".
 3923: 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 3924: 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n";
 3925: 
 3926:     $result.='&nbsp;<b>'.&mt('Use CODE:').' </b>'.
 3927: 	'<input type="text" name="CODE" value="" /><br />'."\n";
 3928: 
 3929:     $result.='&nbsp;<input type="button" '.
 3930: 	'onClick="javascript:checkPickOne(this.form);"value="Next->" /><br />'."\n";
 3931: 
 3932:     $request->print($result);
 3933: 
 3934:     my $studentTable.='&nbsp;<b>Select a student you wish to grade and then click on the Next button.</b><br />'.
 3935: 	'<table border="0"><tr><td bgcolor="#777777">'.
 3936: 	'<table border="0"><tr bgcolor="#e6ffff">'.
 3937: 	'<td align="right">&nbsp;<b>No.</b></td>'.
 3938: 	'<td>'.&nameUserString('header').'</td>'.
 3939: 	'<td align="right">&nbsp;<b>No.</b></td>'.
 3940: 	'<td>'.&nameUserString('header').'</td></tr>';
 3941:  
 3942:     my (undef,undef,$fullname) = &getclasslist($getsec,'1');
 3943:     my $ptr = 1;
 3944:     foreach my $student (sort 
 3945: 			 {
 3946: 			     if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
 3947: 				 return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
 3948: 			     }
 3949: 			     return $a cmp $b;
 3950: 			 } (keys(%$fullname))) {
 3951: 	my ($uname,$udom) = split(/:/,$student);
 3952: 	$studentTable.=($ptr%2 == 1 ? '<tr bgcolor="#ffffe6">' : '</td>');
 3953: 	$studentTable.='<td align="right">'.$ptr.'&nbsp;</td>';
 3954: 	$studentTable.='<td>&nbsp;<label><input type="radio" name="student" value="'.$student.'" /> '
 3955: 	    .&nameUserString(undef,$$fullname{$student},$uname,$udom)."</label>\n";
 3956: 	$studentTable.=($ptr%2 == 0 ? '</td></tr>' : '');
 3957: 	$ptr++;
 3958:     }
 3959:     $studentTable.='</td><td>&nbsp;</td><td>&nbsp;</td></tr>' if ($ptr%2 == 0);
 3960:     $studentTable.='</table></td></tr></table>'."\n";
 3961:     $studentTable.='<input type="button" '.
 3962: 	'onClick="javascript:checkPickOne(this.form);"value="Next->" /></form>'."\n";
 3963: 
 3964:     $studentTable.=&show_grading_menu_form($symb);
 3965:     $request->print($studentTable);
 3966: 
 3967:     return '';
 3968: }
 3969: 
 3970: sub getSymbMap {
 3971:     my $navmap = Apache::lonnavmaps::navmap->new();
 3972: 
 3973:     my %symbx = ();
 3974:     my @titles = ();
 3975:     my $minder = 0;
 3976: 
 3977:     # Gather every sequence that has problems.
 3978:     my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); },
 3979: 					       1,0,1);
 3980:     for my $sequence ($navmap->getById('0.0'), @sequences) {
 3981: 	if ($navmap->hasResource($sequence, sub { shift->is_problem(); }, 0) ) {
 3982: 	    my $title = $minder.'.'.
 3983: 		&HTML::Entities::encode($sequence->compTitle(),'"\'&');
 3984: 	    push(@titles, $title); # minder in case two titles are identical
 3985: 	    $symbx{$title} = &HTML::Entities::encode($sequence->symb(),'"\'&');
 3986: 	    $minder++;
 3987: 	}
 3988:     }
 3989:     return \@titles,\%symbx;
 3990: }
 3991: 
 3992: #
 3993: #--- Displays a page/sequence w/wo problems, w/wo submissions
 3994: sub displayPage {
 3995:     my ($request) = shift;
 3996: 
 3997:     my ($symb) = &get_symb($request);
 3998:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
 3999:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
 4000:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
 4001:     my $pageTitle = $env{'form.page'};
 4002:     my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
 4003:     my ($uname,$udom) = split(/:/,$env{'form.student'});
 4004:     my $usec=$classlist->{$env{'form.student'}}[5];
 4005: 
 4006:     #need to make sure we have the correct data for later EXT calls, 
 4007:     #thus invalidate the cache
 4008:     &Apache::lonnet::devalidatecourseresdata(
 4009:                  $env{'course.'.$env{'request.course.id'}.'.num'},
 4010:                  $env{'course.'.$env{'request.course.id'}.'.domain'});
 4011:     &Apache::lonnet::clear_EXT_cache_status();
 4012: 
 4013:     if (!&canview($usec)) {
 4014: 	$request->print('<span class="LC_warning">Unable to view requested student.('.$env{'form.student'}.')</span>');
 4015: 	$request->print(&show_grading_menu_form($symb));
 4016: 	return;
 4017:     }
 4018:     my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
 4019:     $result.='<h3>&nbsp;Student: '.&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom).
 4020: 	'</h3>'."\n";
 4021:     if (&Apache::lonnet::validCODE($env{'form.CODE'})) {
 4022: 	$result.='<h3>&nbsp;CODE: '.$env{'form.CODE'}.'</h3>'."\n";
 4023:     } else {
 4024: 	delete($env{'form.CODE'});
 4025:     }
 4026:     &sub_page_js($request);
 4027:     $request->print($result);
 4028: 
 4029:     my $navmap = Apache::lonnavmaps::navmap->new();
 4030:     my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'});
 4031:     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
 4032:     if (!$map) {
 4033: 	$request->print('<span class="LC_warning">Unable to view requested sequence. ('.$resUrl.')</span>');
 4034: 	$request->print(&show_grading_menu_form($symb));
 4035: 	return; 
 4036:     }
 4037:     my $iterator = $navmap->getIterator($map->map_start(),
 4038: 					$map->map_finish());
 4039: 
 4040:     my $studentTable='<form action="/adm/grades" method="post" name="gradePage">'."\n".
 4041: 	'<input type="hidden" name="command" value="gradeByPage" />'."\n".
 4042: 	'<input type="hidden" name="fullname" value="'.$$fullname{$env{'form.student'}}.'" />'."\n".
 4043: 	'<input type="hidden" name="student" value="'.$env{'form.student'}.'" />'."\n".
 4044: 	'<input type="hidden" name="page"    value="'.$pageTitle.'" />'."\n".
 4045: 	'<input type="hidden" name="title"   value="'.$env{'form.title'}.'" />'."\n".
 4046: 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 4047: 	'<input type="hidden" name="overRideScore" value="no" />'."\n".
 4048: 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n";
 4049: 
 4050:     if (defined($env{'form.CODE'})) {
 4051: 	$studentTable.=
 4052: 	    '<input type="hidden" name="CODE" value="'.$env{'form.CODE'}.'" />'."\n";
 4053:     }
 4054:     my $checkIcon = '<img alt="'.&mt('Check Mark').
 4055: 	'" src="'.$request->dir_config('lonIconsURL').
 4056: 	'/check.gif" height="16" border="0" />';
 4057: 
 4058:     $studentTable.='&nbsp;<b>Note:</b> Problems graded correct by the computer are marked with a '.$checkIcon.
 4059: 	' symbol.'."\n".
 4060: 	'<table border="0"><tr><td bgcolor="#777777">'.
 4061: 	'<table border="0"><tr bgcolor="#e6ffff">'.
 4062: 	'<td align="center"><b>&nbsp;Prob.&nbsp;</b></td>'.
 4063: 	'<td><b>&nbsp;'.($env{'form.vProb'} eq 'no' ? 'Title' : 'Problem Text').'/Grade</b></td></tr>';
 4064: 
 4065:     &Apache::lonxml::clear_problem_counter();
 4066:     my ($depth,$question,$prob) = (1,1,1);
 4067:     $iterator->next(); # skip the first BEGIN_MAP
 4068:     my $curRes = $iterator->next(); # for "current resource"
 4069:     while ($depth > 0) {
 4070:         if($curRes == $iterator->BEGIN_MAP) { $depth++; }
 4071:         if($curRes == $iterator->END_MAP) { $depth--; }
 4072: 
 4073:         if (ref($curRes) && $curRes->is_problem()) {
 4074: 	    my $parts = $curRes->parts();
 4075:             my $title = $curRes->compTitle();
 4076: 	    my $symbx = $curRes->symb();
 4077: 	    $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$prob.
 4078: 		(scalar(@{$parts}) == 1 ? '' : '<br />('.scalar(@{$parts}).'&nbsp;parts)').'</td>';
 4079: 	    $studentTable.='<td valign="top">';
 4080: 	    my %form = ('CODE' => $env{'form.CODE'},);
 4081: 	    if ($env{'form.vProb'} eq 'yes' ) {
 4082: 		$studentTable.=&show_problem($request,$symbx,$uname,$udom,1,
 4083: 					     undef,'both',\%form);
 4084: 	    } else {
 4085: 		my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'},%form);
 4086: 		$companswer =~ s|<form(.*?)>||g;
 4087: 		$companswer =~ s|</form>||g;
 4088: #		while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a>
 4089: #		    $companswer =~ s/$1/ /ms;
 4090: #		    $request->print('match='.$1."<br />\n");
 4091: #		}
 4092: #		$companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;
 4093: 		$studentTable.='&nbsp;<b>'.$title.'</b>&nbsp;<br />&nbsp;<b>Correct answer:</b><br />'.$companswer;
 4094: 	    }
 4095: 
 4096: 	    my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
 4097: 
 4098: 	    if ($env{'form.lastSub'} eq 'datesub') {
 4099: 		if ($record{'version'} eq '') {
 4100: 		    $studentTable.='<br />&nbsp;<span class="LC_warning">No recorded submission for this problem</span><br />';
 4101: 		} else {
 4102: 		    my %responseType = ();
 4103: 		    foreach my $partid (@{$parts}) {
 4104: 			my @responseIds =$curRes->responseIds($partid);
 4105: 			my @responseType =$curRes->responseType($partid);
 4106: 			my %responseIds;
 4107: 			for (my $i=0;$i<=$#responseIds;$i++) {
 4108: 			    $responseIds{$responseIds[$i]}=$responseType[$i];
 4109: 			}
 4110: 			$responseType{$partid} = \%responseIds;
 4111: 		    }
 4112: 		    $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom);
 4113: 
 4114: 		}
 4115: 	    } elsif ($env{'form.lastSub'} eq 'all') {
 4116: 		my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
 4117: 		$studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom,
 4118: 									$env{'request.course.id'},
 4119: 									'','.submission');
 4120:  
 4121: 	    }
 4122: 	    if (&canmodify($usec)) {
 4123: 		foreach my $partid (@{$parts}) {
 4124: 		    $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record);
 4125: 		    $studentTable.='<input type="hidden" name="q_'.$question.'" value="'.$partid.'" />'."\n";
 4126: 		    $question++;
 4127: 		}
 4128: 		$prob++;
 4129: 	    }
 4130: 	    $studentTable.='</td></tr>';
 4131: 
 4132: 	}
 4133:         $curRes = $iterator->next();
 4134:     }
 4135: 
 4136:     $studentTable.='</table></td></tr></table>'."\n".
 4137: 	'<input type="button" value="Save" '.
 4138: 	'onClick="javascript:checkSubmitPage(this.form,'.$question.');" />'.
 4139: 	'</form>'."\n";
 4140:     $studentTable.=&show_grading_menu_form($symb);
 4141:     $request->print($studentTable);
 4142: 
 4143:     return '';
 4144: }
 4145: 
 4146: sub displaySubByDates {
 4147:     my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;
 4148:     my $isCODE=0;
 4149:     my $isTask = ($symb =~/\.task$/);
 4150:     if (exists($record->{'resource.CODE'})) { $isCODE=1; }
 4151:     my $studentTable='<table border="0" width="100%"><tr><td bgcolor="#777777">'.
 4152: 	'<table border="0" width="100%"><tr bgcolor="#e6ffff">'.
 4153: 	'<td><b>Date/Time</b></td>'.
 4154: 	($isCODE?'<td><b>CODE</b></td>':'').
 4155: 	'<td><b>Submission</b></td>'.
 4156: 	'<td><b>Status&nbsp;</b></td></tr>';
 4157:     my ($version);
 4158:     my %mark;
 4159:     my %orders;
 4160:     $mark{'correct_by_student'} = $checkIcon;
 4161:     if (!exists($$record{'1:timestamp'})) {
 4162: 	return '<br />&nbsp;<span class="LC_warning">Nothing submitted - no attempts</span><br />';
 4163:     }
 4164: 
 4165:     my $interaction;
 4166:     for ($version=1;$version<=$$record{'version'};$version++) {
 4167: 	my $timestamp = scalar(localtime($$record{$version.':timestamp'}));
 4168: 	if (exists($$record{$version.':resource.0.version'})) {
 4169: 	    $interaction = $$record{$version.':resource.0.version'};
 4170: 	}
 4171: 
 4172: 	my $where = ($isTask ? "$version:resource.$interaction"
 4173: 		             : "$version:resource");
 4174: 	#&Apache::lonnet::logthis(" got $where");
 4175: 	$studentTable.='<tr bgcolor="#ffffff" valign="top"><td>'.$timestamp.'</td>';
 4176: 	if ($isCODE) {
 4177: 	    $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';
 4178: 	}
 4179: 	my @versionKeys = split(/\:/,$$record{$version.':keys'});
 4180: 	my @displaySub = ();
 4181: 	foreach my $partid (@{$parts}) {
 4182: 	    my @matchKey = ($isTask ? sort(grep /^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys)
 4183: 			            : sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys));
 4184: 	    
 4185: 
 4186: #	    next if ($$record{"$version:resource.$partid.solved"} eq '');
 4187: 	    my $display_part=&get_display_part($partid,$symb);
 4188: 	    foreach my $matchKey (@matchKey) {
 4189: 		if (exists($$record{$version.':'.$matchKey}) &&
 4190: 		    $$record{$version.':'.$matchKey} ne '') {
 4191: 
 4192: 		    my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
 4193: 				               : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
 4194: 		    #&Apache::lonnet::logthis("match $matchKey $responseId (".$$record{$version.':'.$matchKey});
 4195: 		    $displaySub[0].='<b>Part:</b>&nbsp;'.$display_part.'&nbsp;';
 4196: 		    $displaySub[0].='<span class="LC_internal_info">(ID&nbsp;'.
 4197: 			$responseId.')</span>&nbsp;<b>';
 4198: 		    if ($$record{"$where.$partid.tries"} eq '') {
 4199: 			$displaySub[0].='Trial&nbsp;not&nbsp;counted';
 4200: 		    } else {
 4201: 			$displaySub[0].='Trial&nbsp;'.
 4202: 			    $$record{"$where.$partid.tries"};
 4203: 		    }
 4204: 		    my $responseType=($isTask ? 'Task'
 4205:                                               : $responseType->{$partid}->{$responseId});
 4206: 		    if (!exists($orders{$partid})) { $orders{$partid}={}; }
 4207: 		    if (!exists($orders{$partid}->{$responseId})) {
 4208: 			$orders{$partid}->{$responseId}=
 4209: 			    &get_order($partid,$responseId,$symb,$uname,$udom);
 4210: 		    }
 4211: 		    $displaySub[0].='</b>&nbsp; '.
 4212: 			&cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom).'<br />';
 4213: 		}
 4214: 	    }
 4215: 	    if (exists($$record{"$where.$partid.checkedin"})) {
 4216: 		$displaySub[1].='Checked in by '.
 4217: 		    $$record{"$where.$partid.checkedin"}.' into slot '.
 4218: 		    $$record{"$where.$partid.checkedin.slot"}.
 4219: 		    '<br />';
 4220: 	    }
 4221: 	    if (exists $$record{"$where.$partid.award"}) {
 4222: 		$displaySub[1].='<b>Part:</b>&nbsp;'.$display_part.' &nbsp;'.
 4223: 		    lc($$record{"$where.$partid.award"}).' '.
 4224: 		    $mark{$$record{"$where.$partid.solved"}}.
 4225: 		    '<br />';
 4226: 	    }
 4227: 	    if (exists $$record{"$where.$partid.regrader"}) {
 4228: 		$displaySub[2].=$$record{"$where.$partid.regrader"}.
 4229: 		    ' (<b>'.&mt('Part').':</b> '.$display_part.')';
 4230: 	    } elsif ($$record{"$version:resource.$partid.regrader"} =~ /\S/) {
 4231: 		$displaySub[2].=
 4232: 		    $$record{"$version:resource.$partid.regrader"}.
 4233: 		    ' (<b>'.&mt('Part').':</b> '.$display_part.')';
 4234: 	    }
 4235: 	}
 4236: 	# needed because old essay regrader has not parts info
 4237: 	if (exists $$record{"$version:resource.regrader"}) {
 4238: 	    $displaySub[2].=$$record{"$version:resource.regrader"};
 4239: 	}
 4240: 	$studentTable.='<td>'.$displaySub[0].'&nbsp;</td><td>'.$displaySub[1];
 4241: 	if ($displaySub[2]) {
 4242: 	    $studentTable.='Manually graded by '.$displaySub[2];
 4243: 	}
 4244: 	$studentTable.='&nbsp;</td></tr>';
 4245:     
 4246:     }
 4247:     $studentTable.='</table></td></tr></table>';
 4248:     return $studentTable;
 4249: }
 4250: 
 4251: sub updateGradeByPage {
 4252:     my ($request) = shift;
 4253: 
 4254:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
 4255:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
 4256:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
 4257:     my $pageTitle = $env{'form.page'};
 4258:     my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
 4259:     my ($uname,$udom) = split(/:/,$env{'form.student'});
 4260:     my $usec=$classlist->{$env{'form.student'}}[5];
 4261:     if (!&canmodify($usec)) {
 4262: 	$request->print('<span class="LC_warning">Unable to modify requested student.('.$env{'form.student'}.'</span>');
 4263: 	$request->print(&show_grading_menu_form($env{'form.symb'}));
 4264: 	return;
 4265:     }
 4266:     my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
 4267:     $result.='<h3>&nbsp;Student: '.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
 4268: 	'</h3>'."\n";
 4269: 
 4270:     $request->print($result);
 4271: 
 4272:     my $navmap = Apache::lonnavmaps::navmap->new();
 4273:     my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'});
 4274:     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
 4275:     if (!$map) {
 4276: 	$request->print('<span class="LC_warning">Unable to grade requested sequence. ('.$resUrl.')</span>');
 4277: 	my ($symb)=&get_symb($request);
 4278: 	$request->print(&show_grading_menu_form($symb));
 4279: 	return; 
 4280:     }
 4281:     my $iterator = $navmap->getIterator($map->map_start(),
 4282: 					$map->map_finish());
 4283: 
 4284:     my $studentTable='<table border="0"><tr><td bgcolor="#777777">'.
 4285: 	'<table border="0"><tr bgcolor="#e6ffff">'.
 4286: 	'<td align="center"><b>&nbsp;Prob.&nbsp;</b></td>'.
 4287: 	'<td><b>&nbsp;Title&nbsp;</b></td>'.
 4288: 	'<td><b>&nbsp;Previous Score&nbsp;</b></td>'.
 4289: 	'<td><b>&nbsp;New Score&nbsp;</b></td></tr>';
 4290: 
 4291:     $iterator->next(); # skip the first BEGIN_MAP
 4292:     my $curRes = $iterator->next(); # for "current resource"
 4293:     my ($depth,$question,$prob,$changeflag)= (1,1,1,0);
 4294:     while ($depth > 0) {
 4295:         if($curRes == $iterator->BEGIN_MAP) { $depth++; }
 4296:         if($curRes == $iterator->END_MAP) { $depth--; }
 4297: 
 4298:         if (ref($curRes) && $curRes->is_problem()) {
 4299: 	    my $parts = $curRes->parts();
 4300:             my $title = $curRes->compTitle();
 4301: 	    my $symbx = $curRes->symb();
 4302: 	    $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$prob.
 4303: 		(scalar(@{$parts}) == 1 ? '' : '<br />('.scalar(@{$parts}).'&nbsp;parts)').'</td>';
 4304: 	    $studentTable.='<td valign="top">&nbsp;<b>'.$title.'</b>&nbsp;</td>';
 4305: 
 4306: 	    my %newrecord=();
 4307: 	    my @displayPts=();
 4308:             my %aggregate = ();
 4309:             my $aggregateflag = 0;
 4310: 	    foreach my $partid (@{$parts}) {
 4311: 		my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};
 4312: 		my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};
 4313: 
 4314: 		my $wgt = $env{'form.WGT'.$question.'_'.$partid} != 0 ? 
 4315: 		    $env{'form.WGT'.$question.'_'.$partid} : 1;
 4316: 		my $partial = $newpts/$wgt;
 4317: 		my $score;
 4318: 		if ($partial > 0) {
 4319: 		    $score = 'correct_by_override';
 4320: 		} elsif ($newpts ne '') { #empty is taken as 0
 4321: 		    $score = 'incorrect_by_override';
 4322: 		}
 4323: 		my $dropMenu = $env{'form.GD_SEL'.$question.'_'.$partid};
 4324: 		if ($dropMenu eq 'excused') {
 4325: 		    $partial = '';
 4326: 		    $score = 'excused';
 4327: 		} elsif ($dropMenu eq 'reset status'
 4328: 			 && $env{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists
 4329: 		    $newrecord{'resource.'.$partid.'.tries'} = 0;
 4330: 		    $newrecord{'resource.'.$partid.'.solved'} = '';
 4331: 		    $newrecord{'resource.'.$partid.'.award'} = '';
 4332: 		    $newrecord{'resource.'.$partid.'.awarded'} = 0;
 4333: 		    $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}";
 4334: 		    $changeflag++;
 4335: 		    $newpts = '';
 4336:                     
 4337:                     my $aggtries =  $env{'form.aggtries'.$question.'_'.$partid};
 4338:                     my $totaltries = $env{'form.totaltries'.$question.'_'.$partid};
 4339:                     my $solvedstatus = $env{'form.solved'.$question.'_'.$partid};
 4340:                     if ($aggtries > 0) {
 4341:                         &decrement_aggs($symbx,$partid,\%aggregate,$aggtries,$totaltries,$solvedstatus);
 4342:                         $aggregateflag = 1;
 4343:                     }
 4344: 		}
 4345: 		my $display_part=&get_display_part($partid,$curRes->symb());
 4346: 		my $oldstatus = $env{'form.solved'.$question.'_'.$partid};
 4347: 		$displayPts[0].='&nbsp;<b>Part:</b> '.$display_part.' = '.
 4348: 		    (($oldstatus eq 'excused') ? 'excused' : $oldpts).
 4349: 		    '&nbsp;<br />';
 4350: 		$displayPts[1].='&nbsp;<b>Part:</b> '.$display_part.' = '.
 4351: 		     (($score eq 'excused') ? 'excused' : $newpts).
 4352: 		    '&nbsp;<br />';
 4353: 		$question++;
 4354: 		next if ($dropMenu eq 'reset status' || ($newpts eq $oldpts && $score ne 'excused'));
 4355: 
 4356: 		$newrecord{'resource.'.$partid.'.awarded'}  = $partial if $partial ne '';
 4357: 		$newrecord{'resource.'.$partid.'.solved'}   = $score if $score ne '';
 4358: 		$newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}"
 4359: 		    if (scalar(keys(%newrecord)) > 0);
 4360: 
 4361: 		$changeflag++;
 4362: 	    }
 4363: 	    if (scalar(keys(%newrecord)) > 0) {
 4364: 		my %record = 
 4365: 		    &Apache::lonnet::restore($symbx,$env{'request.course.id'},
 4366: 					     $udom,$uname);
 4367: 
 4368: 		if (&Apache::lonnet::validCODE($env{'form.CODE'})) {
 4369: 		    $newrecord{'resource.CODE'} = $env{'form.CODE'};
 4370: 		} elsif (&Apache::lonnet::validCODE($record{'resource.CODE'})) {
 4371: 		    $newrecord{'resource.CODE'} = '';
 4372: 		}
 4373: 		&Apache::lonnet::cstore(\%newrecord,$symbx,$env{'request.course.id'},
 4374: 					$udom,$uname);
 4375: 		%record = &Apache::lonnet::restore($symbx,
 4376: 						   $env{'request.course.id'},
 4377: 						   $udom,$uname);
 4378: 		&check_and_remove_from_queue($parts,\%record,undef,$symbx,
 4379: 					     $cdom,$cnum,$udom,$uname);
 4380: 	    }
 4381: 	    
 4382:             if ($aggregateflag) {
 4383:                 &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
 4384:                       $env{'course.'.$env{'request.course.id'}.'.domain'},
 4385:                       $env{'course.'.$env{'request.course.id'}.'.num'});
 4386:             }
 4387: 
 4388: 	    $studentTable.='<td valign="top">'.$displayPts[0].'</td>'.
 4389: 		'<td valign="top">'.$displayPts[1].'</td>'.
 4390: 		'</tr>';
 4391: 
 4392: 	    $prob++;
 4393: 	}
 4394:         $curRes = $iterator->next();
 4395:     }
 4396: 
 4397:     $studentTable.='</td></tr></table></td></tr></table>';
 4398:     $studentTable.=&show_grading_menu_form($env{'form.symb'});
 4399:     my $grademsg=($changeflag == 0 ? 'No score was changed or updated.' :
 4400: 		  'The scores were changed for '.
 4401: 		  $changeflag.' problem'.($changeflag == 1 ? '.' : 's.'));
 4402:     $request->print($grademsg.$studentTable);
 4403: 
 4404:     return '';
 4405: }
 4406: 
 4407: #-------- end of section for handling grading by page/sequence ---------
 4408: #
 4409: #-------------------------------------------------------------------
 4410: 
 4411: #--------------------Scantron Grading-----------------------------------
 4412: #
 4413: #------ start of section for handling grading by page/sequence ---------
 4414: 
 4415: =pod
 4416: 
 4417: =head1 Bubble sheet grading routines
 4418: 
 4419:   For this documentation:
 4420: 
 4421:    'scanline' refers to the full line of characters
 4422:    from the file that we are parsing that represents one entire sheet
 4423: 
 4424:    'bubble line' refers to the data
 4425:    representing the line of bubbles that are on the physical bubble sheet
 4426: 
 4427: 
 4428: The overall process is that a scanned in bubble sheet data is uploaded
 4429: into a course. When a user wants to grade, they select a
 4430: sequence/folder of resources, a file of bubble sheet info, and pick
 4431: one of the predefined configurations for what each scanline looks
 4432: like.
 4433: 
 4434: Next each scanline is checked for any errors of either 'missing
 4435: bubbles' (it's an error because it may have been mis-scanned
 4436: because too light bubbling), 'double bubble' (each bubble line should
 4437: have no more that one letter picked), invalid or duplicated CODE,
 4438: invalid student ID
 4439: 
 4440: If the CODE option is used that determines the randomization of the
 4441: homework problems, either way the student ID is looked up into a
 4442: username:domain.
 4443: 
 4444: During the validation phase the instructor can choose to skip scanlines. 
 4445: 
 4446: After the validation phase, there are now 3 bubble sheet files
 4447: 
 4448:   scantron_original_filename (unmodified original file)
 4449:   scantron_corrected_filename (file where the corrected information has replaced the original information)
 4450:   scantron_skipped_filename (contains the exact text of scanlines that where skipped)
 4451: 
 4452: Also there is a separate hash nohist_scantrondata that contains extra
 4453: correction information that isn't representable in the bubble sheet
 4454: file (see &scantron_getfile() for more information)
 4455: 
 4456: After all scanlines are either valid, marked as valid or skipped, then
 4457: foreach line foreach problem in the picked sequence, an ssi request is
 4458: made that simulates a user submitting their selected letter(s) against
 4459: the homework problem.
 4460: 
 4461: =over 4
 4462: 
 4463: =cut
 4464: 
 4465: 
 4466: =pod 
 4467: 
 4468: =item defaultFormData
 4469: 
 4470:   Returns html hidden inputs used to hold context/default values.
 4471: 
 4472:  Arguments:
 4473:   $symb - $symb of the current resource 
 4474: 
 4475: =cut
 4476: 
 4477: sub defaultFormData {
 4478:     my ($symb)=@_;
 4479:     return '
 4480:       <input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 4481:      '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
 4482:      '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
 4483: }
 4484: 
 4485: =pod 
 4486: 
 4487: =item getSequenceDropDown
 4488: 
 4489:    Return html dropdown of possible sequences to grade
 4490:  
 4491:  Arguments:
 4492:    $symb - $symb of the current resource 
 4493: 
 4494: =cut
 4495: 
 4496: sub getSequenceDropDown {
 4497:     my ($symb)=@_;
 4498:     my $result='<select name="selectpage">'."\n";
 4499:     my ($titles,$symbx) = &getSymbMap();
 4500:     my ($curpage)=&Apache::lonnet::decode_symb($symb); 
 4501:     my $ctr=0;
 4502:     foreach (@$titles) {
 4503: 	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
 4504: 	$result.='<option value="'.$$symbx{$_}.'" '.
 4505: 	    ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
 4506: 	    '>'.$showtitle.'</option>'."\n";
 4507: 	$ctr++;
 4508:     }
 4509:     $result.= '</select>';
 4510:     return $result;
 4511: }
 4512: 
 4513: 
 4514: =pod 
 4515: 
 4516: =item scantron_filenames
 4517: 
 4518:    Returns a list of the scantron files in the current course 
 4519: 
 4520: =cut
 4521: 
 4522: sub scantron_filenames {
 4523:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 4524:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 4525:     my @files=&Apache::lonnet::dirlist('userfiles',$cdom,$cname,
 4526: 				    &propath($cdom,$cname));
 4527:     my @possiblenames;
 4528:     foreach my $filename (sort(@files)) {
 4529: 	($filename)=split(/&/,$filename);
 4530: 	if ($filename!~/^scantron_orig_/) { next ; }
 4531: 	$filename=~s/^scantron_orig_//;
 4532: 	push(@possiblenames,$filename);
 4533:     }
 4534:     return @possiblenames;
 4535: }
 4536: 
 4537: =pod 
 4538: 
 4539: =item scantron_uploads
 4540: 
 4541:    Returns  html drop-down list of scantron files in current course.
 4542: 
 4543:  Arguments:
 4544:    $file2grade - filename to set as selected in the dropdown
 4545: 
 4546: =cut
 4547: 
 4548: sub scantron_uploads {
 4549:     my ($file2grade) = @_;
 4550:     my $result=	'<select name="scantron_selectfile">';
 4551:     $result.="<option></option>";
 4552:     foreach my $filename (sort(&scantron_filenames())) {
 4553: 	$result.="<option".($filename eq $file2grade ? ' selected="selected"':'').">$filename</option>\n";
 4554:     }
 4555:     $result.="</select>";
 4556:     return $result;
 4557: }
 4558: 
 4559: =pod 
 4560: 
 4561: =item scantron_scantab
 4562: 
 4563:   Returns html drop down of the scantron formats in the scantronformat.tab
 4564:   file.
 4565: 
 4566: =cut
 4567: 
 4568: sub scantron_scantab {
 4569:     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
 4570:     my $result='<select name="scantron_format">'."\n";
 4571:     $result.='<option></option>'."\n";
 4572:     foreach my $line (<$fh>) {
 4573: 	my ($name,$descrip)=split(/:/,$line);
 4574: 	if ($name =~ /^\#/) { next; }
 4575: 	$result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";
 4576:     }
 4577:     $result.='</select>'."\n";
 4578: 
 4579:     return $result;
 4580: }
 4581: 
 4582: =pod 
 4583: 
 4584: =item scantron_CODElist
 4585: 
 4586:   Returns html drop down of the saved CODE lists from current course,
 4587:   generated from earlier printings.
 4588: 
 4589: =cut
 4590: 
 4591: sub scantron_CODElist {
 4592:     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
 4593:     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
 4594:     my @names=&Apache::lonnet::getkeys('CODEs',$cdom,$cnum);
 4595:     my $namechoice='<option></option>';
 4596:     foreach my $name (sort {uc($a) cmp uc($b)} @names) {
 4597: 	if ($name =~ /^error: 2 /) { next; }
 4598: 	if ($name =~ /^type\0/) { next; }
 4599: 	$namechoice.='<option value="'.$name.'">'.$name.'</option>';
 4600:     }
 4601:     $namechoice='<select name="scantron_CODElist">'.$namechoice.'</select>';
 4602:     return $namechoice;
 4603: }
 4604: 
 4605: =pod 
 4606: 
 4607: =item scantron_CODEunique
 4608: 
 4609:   Returns the html for "Each CODE to be used once" radio.
 4610: 
 4611: =cut
 4612: 
 4613: sub scantron_CODEunique {
 4614:     my $result='<span style="white-space: nowrap;">
 4615:                  <label><input type="radio" name="scantron_CODEunique"
 4616:                         value="yes" checked="checked" />'.&mt('Yes').' </label>
 4617:                 </span>
 4618:                 <span style="white-space: nowrap;">
 4619:                  <label><input type="radio" name="scantron_CODEunique"
 4620:                         value="no" />'.&mt('No').' </label>
 4621:                 </span>';
 4622:     return $result;
 4623: }
 4624: 
 4625: =pod 
 4626: 
 4627: =item scantron_selectphase
 4628: 
 4629:   Generates the initial screen to start the bubble sheet process.
 4630:   Allows for - starting a grading run.
 4631:              - downloading existing scan data (original, corrected
 4632:                                                 or skipped info)
 4633: 
 4634:              - uploading new scan data
 4635: 
 4636:  Arguments:
 4637:   $r          - The Apache request object
 4638:   $file2grade - name of the file that contain the scanned data to score
 4639: 
 4640: =cut
 4641: 
 4642: sub scantron_selectphase {
 4643:     my ($r,$file2grade) = @_;
 4644:     my ($symb)=&get_symb($r);
 4645:     if (!$symb) {return '';}
 4646:     my $sequence_selector=&getSequenceDropDown($symb);
 4647:     my $default_form_data=&defaultFormData($symb);
 4648:     my $grading_menu_button=&show_grading_menu_form($symb);
 4649:     my $file_selector=&scantron_uploads($file2grade);
 4650:     my $format_selector=&scantron_scantab();
 4651:     my $CODE_selector=&scantron_CODElist();
 4652:     my $CODE_unique=&scantron_CODEunique();
 4653:     my $result;
 4654: 
 4655:     # Chunk of form to prompt for a file to grade and how:
 4656: 
 4657:     $result.= <<SCANTRONFORM;
 4658:     <table width="100%" border="0">
 4659:     <tr>
 4660:      <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">
 4661:       <td bgcolor="#777777">
 4662:        <input type="hidden" name="command" value="scantron_warning" />
 4663:         $default_form_data
 4664:         <table width="100%" border="0">
 4665:           <tr bgcolor="#e6ffff">
 4666:             <td colspan="2">
 4667:               &nbsp;<b>Specify file and which Folder/Sequence to grade</b>
 4668:             </td>
 4669:           </tr>
 4670:           <tr bgcolor="#ffffe6">
 4671:             <td> Sequence to grade: </td><td> $sequence_selector </td>
 4672:           </tr>
 4673:           <tr bgcolor="#ffffe6">
 4674:             <td> Filename of scoring office file: </td><td> $file_selector </td>
 4675:           </tr>
 4676:           <tr bgcolor="#ffffe6">
 4677:             <td> Format of data file: </td><td> $format_selector </td>
 4678:           </tr>
 4679:           <tr bgcolor="#ffffe6">
 4680:             <td> Saved CODEs to validate against: </td><td> $CODE_selector</td>
 4681:           </tr>
 4682:           <tr bgcolor="#ffffe6">
 4683:             <td> Each CODE is only to be used once:</td><td> $CODE_unique </td>
 4684:           </tr>
 4685:           <tr bgcolor="#ffffe6">
 4686: 	    <td> Options: </td>
 4687:             <td>
 4688: 	       <label><input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> Do only previously skipped records</label> <br />
 4689:                <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> Remove all existing corrections</label> <br />
 4690:                <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> Skip hidden resources when grading</label>
 4691: 	    </td>
 4692:           </tr>
 4693:           <tr bgcolor="#ffffe6">
 4694:             <td colspan="2">
 4695:               <input type="submit" value="Grading: Validate Scantron Records" />
 4696:             </td>
 4697:           </tr>
 4698:         </table>
 4699:        </td>
 4700:      </form>
 4701:     </tr>
 4702: SCANTRONFORM
 4703:    
 4704:     $r->print($result);
 4705: 
 4706:     if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
 4707:         &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
 4708: 
 4709: 	# Chunk of form to prompt for a scantron file upload.
 4710: 
 4711:         $r->print(<<SCANTRONFORM);
 4712:     <tr>
 4713:       <td bgcolor="#777777">
 4714:         <table width="100%" border="0">
 4715:           <tr bgcolor="#e6ffff">
 4716:             <td>
 4717:               &nbsp;<b>Specify a Scantron data file to upload.</b>
 4718:             </td>
 4719:           </tr>
 4720:           <tr bgcolor="#ffffe6">
 4721:             <td>
 4722: SCANTRONFORM
 4723:     my $default_form_data=&defaultFormData(&get_symb($r,1));
 4724:     my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
 4725:     my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};
 4726:     $r->print(<<UPLOAD);
 4727:               <script type="text/javascript" language="javascript">
 4728:     function checkUpload(formname) {
 4729: 	if (formname.upfile.value == "") {
 4730: 	    alert("Please use the browse button to select a file from your local directory.");
 4731: 	    return false;
 4732: 	}
 4733: 	formname.submit();
 4734:     }
 4735:               </script>
 4736: 
 4737:               <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'>
 4738:                 $default_form_data
 4739:                 <input name='courseid' type='hidden' value='$cnum' />
 4740:                 <input name='domainid' type='hidden' value='$cdom' />
 4741:                 <input name='command' value='scantronupload_save' type='hidden' />
 4742:                 File to upload:<input type="file" name="upfile" size="50" />
 4743:                 <br />
 4744:                 <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" />
 4745:               </form>
 4746: UPLOAD
 4747: 
 4748:         $r->print(<<SCANTRONFORM);
 4749:             </td>
 4750:           </tr>
 4751:         </table>
 4752:       </td>
 4753:     </tr>
 4754: SCANTRONFORM
 4755:     }
 4756: 
 4757:     # Chunk of the form that prompts to view a scoring office file,
 4758:     # corrected file, skipped records in a file.
 4759: 
 4760:     $r->print(<<SCANTRONFORM);
 4761:     <tr>
 4762:       <form action='/adm/grades' name='scantron_download'>
 4763:         <td bgcolor="#777777">
 4764: 	  $default_form_data
 4765:           <input type="hidden" name="command" value="scantron_download" />
 4766:           <table width="100%" border="0">
 4767:             <tr bgcolor="#e6ffff">
 4768:               <td colspan="2">
 4769:                 &nbsp;<b>Download a scoring office file</b>
 4770:               </td>
 4771:             </tr>
 4772:             <tr bgcolor="#ffffe6">
 4773:               <td> Filename of scoring office file: </td><td> $file_selector </td>
 4774:             </tr>
 4775:             <tr bgcolor="#ffffe6">
 4776:               <td colspan="2">
 4777:                 <input type="submit" value="Download: Show List of Associated Files" />
 4778:               </td>
 4779:             </tr>
 4780:           </table>
 4781:         </td>
 4782:       </form>
 4783:     </tr>
 4784: SCANTRONFORM
 4785: 
 4786:     $r->print(<<SCANTRONFORM);
 4787:   </table>
 4788: $grading_menu_button
 4789: SCANTRONFORM
 4790: 
 4791:     return
 4792: }
 4793: 
 4794: =pod
 4795: 
 4796: =item get_scantron_config
 4797: 
 4798:    Parse and return the scantron configuration line selected as a
 4799:    hash of configuration file fields.
 4800: 
 4801:  Arguments:
 4802:     which - the name of the configuration to parse from the file.
 4803: 
 4804: 
 4805:  Returns:
 4806:             If the named configuration is not in the file, an empty
 4807:             hash is returned.
 4808:     a hash with the fields
 4809:       name         - internal name for the this configuration setup
 4810:       description  - text to display to operator that describes this config
 4811:       CODElocation - if 0 or the string 'none'
 4812:                           - no CODE exists for this config
 4813:                      if -1 || the string 'letter'
 4814:                           - a CODE exists for this config and is
 4815:                             a string of letters
 4816:                      Unsupported value (but planned for future support)
 4817:                           if a positive integer
 4818:                                - The CODE exists as the first n items from
 4819:                                  the question section of the form
 4820:                           if the string 'number'
 4821:                                - The CODE exists for this config and is
 4822:                                  a string of numbers
 4823:       CODEstart   - (only matter if a CODE exists) column in the line where
 4824:                      the CODE starts
 4825:       CODElength  - length of the CODE
 4826:       IDstart     - column where the student ID number starts
 4827:       IDlength    - length of the student ID info
 4828:       Qstart      - column where the information from the bubbled
 4829:                     'questions' start
 4830:       Qlength     - number of columns comprising a single bubble line from
 4831:                     the sheet. (usually either 1 or 10)
 4832:       Qon         - either a single character representing the character used
 4833:                     to signal a bubble was chosen in the positional setup, or
 4834:                     the string 'letter' if the letter of the chosen bubble is
 4835:                     in the final, or 'number' if a number representing the
 4836:                     chosen bubble is in the file (1->A 0->J)
 4837:       Qoff        - the character used to represent that a bubble was
 4838:                     left blank
 4839:       PaperID     - if the scanning process generates a unique number for each
 4840:                     sheet scanned the column that this ID number starts in
 4841:       PaperIDlength - number of columns that comprise the unique ID number
 4842:                       for the sheet of paper
 4843:       FirstName   - column that the first name starts in
 4844:       FirstNameLength - number of columns that the first name spans
 4845:  
 4846:       LastName    - column that the last name starts in
 4847:       LastNameLength - number of columns that the last name spans
 4848: 
 4849: =cut
 4850: 
 4851: sub get_scantron_config {
 4852:     my ($which) = @_;
 4853:     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
 4854:     my %config;
 4855:     #FIXME probably should move to XML it has already gotten a bit much now
 4856:     foreach my $line (<$fh>) {
 4857: 	my ($name,$descrip)=split(/:/,$line);
 4858: 	if ($name ne $which ) { next; }
 4859: 	chomp($line);
 4860: 	my @config=split(/:/,$line);
 4861: 	$config{'name'}=$config[0];
 4862: 	$config{'description'}=$config[1];
 4863: 	$config{'CODElocation'}=$config[2];
 4864: 	$config{'CODEstart'}=$config[3];
 4865: 	$config{'CODElength'}=$config[4];
 4866: 	$config{'IDstart'}=$config[5];
 4867: 	$config{'IDlength'}=$config[6];
 4868: 	$config{'Qstart'}=$config[7];
 4869: 	$config{'Qlength'}=$config[8];
 4870: 	$config{'Qoff'}=$config[9];
 4871: 	$config{'Qon'}=$config[10];
 4872: 	$config{'PaperID'}=$config[11];
 4873: 	$config{'PaperIDlength'}=$config[12];
 4874: 	$config{'FirstName'}=$config[13];
 4875: 	$config{'FirstNamelength'}=$config[14];
 4876: 	$config{'LastName'}=$config[15];
 4877: 	$config{'LastNamelength'}=$config[16];
 4878: 	last;
 4879:     }
 4880:     return %config;
 4881: }
 4882: 
 4883: =pod 
 4884: 
 4885: =item username_to_idmap
 4886: 
 4887:     creates a hash keyed by student id with values of the corresponding
 4888:     student username:domain.
 4889: 
 4890:   Arguments:
 4891: 
 4892:     $classlist - reference to the class list hash. This is a hash
 4893:                  keyed by student name:domain  whose elements are references
 4894:                  to arrays containing various chunks of information
 4895:                  about the student. (See loncoursedata for more info).
 4896: 
 4897:   Returns
 4898:     %idmap - the constructed hash
 4899: 
 4900: =cut
 4901: 
 4902: sub username_to_idmap {
 4903:     my ($classlist)= @_;
 4904:     my %idmap;
 4905:     foreach my $student (keys(%$classlist)) {
 4906: 	$idmap{$classlist->{$student}->[&Apache::loncoursedata::CL_ID]}=
 4907: 	    $student;
 4908:     }
 4909:     return %idmap;
 4910: }
 4911: 
 4912: =pod
 4913: 
 4914: =item scantron_fixup_scanline
 4915: 
 4916:    Process a requested correction to a scanline.
 4917: 
 4918:   Arguments:
 4919:     $scantron_config   - hash from &get_scantron_config()
 4920:     $scan_data         - hash of correction information 
 4921:                           (see &scantron_getfile())
 4922:     $line              - existing scanline
 4923:     $whichline         - line number of the passed in scanline
 4924:     $field             - type of change to process 
 4925:                          (either 
 4926:                           'ID'     -> correct the student ID number
 4927:                           'CODE'   -> correct the CODE
 4928:                           'answer' -> fixup the submitted answers)
 4929:     
 4930:    $args               - hash of additional info,
 4931:                           - 'ID' 
 4932:                                'newid' -> studentID to use in replacement
 4933:                                           of existing one
 4934:                           - 'CODE' 
 4935:                                'CODE_ignore_dup' - set to true if duplicates
 4936:                                                    should be ignored.
 4937: 	                       'CODE' - is new code or 'use_unfound'
 4938:                                         if the existing unfound code should
 4939:                                         be used as is
 4940:                           - 'answer'
 4941:                                'response' - new answer or 'none' if blank
 4942:                                'question' - the bubble line to change
 4943: 
 4944:   Returns:
 4945:     $line - the modified scanline
 4946: 
 4947:   Side effects: 
 4948:     $scan_data - may be updated
 4949: 
 4950: =cut
 4951: 
 4952: 
 4953: sub scantron_fixup_scanline {
 4954:     my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
 4955: 
 4956:     if ($field eq 'ID') {
 4957: 	if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {
 4958: 	    return ($line,1,'New value too large');
 4959: 	}
 4960: 	if (length($args->{'newid'}) < $$scantron_config{'IDlength'}) {
 4961: 	    $args->{'newid'}=sprintf('%-'.$$scantron_config{'IDlength'}.'s',
 4962: 				     $args->{'newid'});
 4963: 	}
 4964: 	substr($line,$$scantron_config{'IDstart'}-1,
 4965: 	       $$scantron_config{'IDlength'})=$args->{'newid'};
 4966: 	if ($args->{'newid'}=~/^\s*$/) {
 4967: 	    &scan_data($scan_data,"$whichline.user",
 4968: 		       $args->{'username'}.':'.$args->{'domain'});
 4969: 	}
 4970:     } elsif ($field eq 'CODE') {
 4971: 	if ($args->{'CODE_ignore_dup'}) {
 4972: 	    &scan_data($scan_data,"$whichline.CODE_ignore_dup",'1');
 4973: 	}
 4974: 	&scan_data($scan_data,"$whichline.useCODE",'1');
 4975: 	if ($args->{'CODE'} ne 'use_unfound') {
 4976: 	    if (length($args->{'CODE'}) > $$scantron_config{'CODElength'}) {
 4977: 		return ($line,1,'New CODE value too large');
 4978: 	    }
 4979: 	    if (length($args->{'CODE'}) < $$scantron_config{'CODElength'}) {
 4980: 		$args->{'CODE'}=sprintf('%-'.$$scantron_config{'CODElength'}.'s',$args->{'CODE'});
 4981: 	    }
 4982: 	    substr($line,$$scantron_config{'CODEstart'}-1,
 4983: 		   $$scantron_config{'CODElength'})=$args->{'CODE'};
 4984: 	}
 4985:     } elsif ($field eq 'answer') {
 4986: 	my $length=$scantron_config->{'Qlength'};
 4987: 	my $off=$scantron_config->{'Qoff'};
 4988: 	my $on=$scantron_config->{'Qon'};
 4989: 	my $answer=${off}x$length;
 4990: 	if ($args->{'response'} eq 'none') {
 4991: 	    &scan_data($scan_data,
 4992: 		       "$whichline.no_bubble.".$args->{'question'},'1');
 4993: 	} else {
 4994: 	    if ($on eq 'letter') {
 4995: 		my @alphabet=('A'..'Z');
 4996: 		$answer=$alphabet[$args->{'response'}];
 4997: 	    } elsif ($on eq 'number') {
 4998: 		$answer=$args->{'response'}+1;
 4999: 		if ($answer == 10) { $answer = '0'; }
 5000: 	    } else {
 5001: 		substr($answer,$args->{'response'},1)=$on;
 5002: 	    }
 5003: 	    &scan_data($scan_data,
 5004: 		       "$whichline.no_bubble.".$args->{'question'},undef,'1');
 5005: 	}
 5006: 	my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'};
 5007: 	substr($line,$where-1,$length)=$answer;
 5008:     }
 5009:     return $line;
 5010: }
 5011: 
 5012: =pod
 5013: 
 5014: =item scan_data
 5015: 
 5016:     Edit or look up  an item in the scan_data hash.
 5017: 
 5018:   Arguments:
 5019:     $scan_data  - The hash (see scantron_getfile)
 5020:     $key        - shorthand of the key to edit (actual key is
 5021:                   scantronfilename_key).
 5022:     $data        - New value of the hash entry.
 5023:     $delete      - If true, the entry is removed from the hash.
 5024: 
 5025:   Returns:
 5026:     The new value of the hash table field (undefined if deleted).
 5027: 
 5028: =cut
 5029: 
 5030: 
 5031: sub scan_data {
 5032:     my ($scan_data,$key,$value,$delete)=@_;
 5033:     my $filename=$env{'form.scantron_selectfile'};
 5034:     if (defined($value)) {
 5035: 	$scan_data->{$filename.'_'.$key} = $value;
 5036:     }
 5037:     if ($delete) { delete($scan_data->{$filename.'_'.$key}); }
 5038:     return $scan_data->{$filename.'_'.$key};
 5039: }
 5040: 
 5041: =pod 
 5042: 
 5043: =item scantron_parse_scanline
 5044: 
 5045:   Decodes a scanline from the selected scantron file
 5046: 
 5047:  Arguments:
 5048:     line             - The text of the scantron file line to process
 5049:     whichline        - Line number
 5050:     scantron_config  - Hash describing the format of the scantron lines.
 5051:     scan_data        - Hash of extra information about the scanline
 5052:                        (see scantron_getfile for more information)
 5053:     just_header      - True if should not process question answers but only
 5054:                        the stuff to the left of the answers.
 5055:  Returns:
 5056:    Hash containing the result of parsing the scanline
 5057: 
 5058:    Keys are all proceeded by the string 'scantron.'
 5059: 
 5060:        CODE    - the CODE in use for this scanline
 5061:        useCODE - 1 if the CODE is invalid but it usage has been forced
 5062:                  by the operator
 5063:        CODE_ignore_dup - 1 if the CODE is a duplicated use when unique
 5064:                             CODEs were selected, but the usage has been
 5065:                             forced by the operator
 5066:        ID  - student ID
 5067:        PaperID - if used, the ID number printed on the sheet when the 
 5068:                  paper was scanned
 5069:        FirstName - first name from the sheet
 5070:        LastName  - last name from the sheet
 5071: 
 5072:      if just_header was not true these key may also exist
 5073: 
 5074:        missingerror - a list of bubbled line numbers that had a blank bubble
 5075:                       that is considered an error (if the operator had already
 5076:                       okayed a blank bubble line as really being blank then
 5077:                       that bubble line number won't appear here.
 5078:        doubleerror  - a list of bubbled line numbers that had more than one
 5079:                       bubble filled in and has not been corrected by the
 5080:                       operator
 5081:        maxquest     - the number of the last bubble line that was parsed
 5082: 
 5083:        (<number> starts at 1)
 5084:        <number>.answer - zero or more letters representing the selected
 5085:                          letters from the scanline for the bubble line 
 5086:                          <number>.
 5087:                          if blank there was either no bubble or there where
 5088:                          multiple bubbles, (consult the keys missingerror and
 5089:                          doubleerror if this is an error condition)
 5090: 
 5091: =cut
 5092: 
 5093: sub scantron_parse_scanline {
 5094:     my ($line,$whichline,$scantron_config,$scan_data,$just_header)=@_;
 5095:     my %record;
 5096:     my $questions=substr($line,$$scantron_config{'Qstart'}-1);  # Answers
 5097:     my $data=substr($line,0,$$scantron_config{'Qstart'}-1);     # earlier stuff
 5098:     if (!($$scantron_config{'CODElocation'} eq 0 ||
 5099: 	  $$scantron_config{'CODElocation'} eq 'none')) {
 5100: 	if ($$scantron_config{'CODElocation'} < 0 ||
 5101: 	    $$scantron_config{'CODElocation'} eq 'letter' ||
 5102: 	    $$scantron_config{'CODElocation'} eq 'number') {
 5103: 	    $record{'scantron.CODE'}=substr($data,
 5104: 					    $$scantron_config{'CODEstart'}-1,
 5105: 					    $$scantron_config{'CODElength'});
 5106: 	    if (&scan_data($scan_data,"$whichline.useCODE")) {
 5107: 		$record{'scantron.useCODE'}=1;
 5108: 	    }
 5109: 	    if (&scan_data($scan_data,"$whichline.CODE_ignore_dup")) {
 5110: 		$record{'scantron.CODE_ignore_dup'}=1;
 5111: 	    }
 5112: 	} else {
 5113: 	    #FIXME interpret first N questions
 5114: 	}
 5115:     }
 5116:     $record{'scantron.ID'}=substr($data,$$scantron_config{'IDstart'}-1,
 5117: 				  $$scantron_config{'IDlength'});
 5118:     $record{'scantron.PaperID'}=
 5119: 	substr($data,$$scantron_config{'PaperID'}-1,
 5120: 	       $$scantron_config{'PaperIDlength'});
 5121:     $record{'scantron.FirstName'}=
 5122: 	substr($data,$$scantron_config{'FirstName'}-1,
 5123: 	       $$scantron_config{'FirstNamelength'});
 5124:     $record{'scantron.LastName'}=
 5125: 	substr($data,$$scantron_config{'LastName'}-1,
 5126: 	       $$scantron_config{'LastNamelength'});
 5127:     if ($just_header) { return \%record; }
 5128: 
 5129:     my @alphabet=('A'..'Z');
 5130:     my $questnum=0;
 5131:     while ($questions) {
 5132: 	$questnum++;
 5133: 	my $currentquest=substr($questions,0,$$scantron_config{'Qlength'});
 5134: 	substr($questions,0,$$scantron_config{'Qlength'})='';
 5135: 	if (length($currentquest) < $$scantron_config{'Qlength'}) { next; }
 5136: 	if ($$scantron_config{'Qon'} eq 'letter') {
 5137: 	    if ($currentquest eq '?'
 5138: 		|| $currentquest eq '*') {
 5139: 		push(@{$record{'scantron.doubleerror'}},$questnum);
 5140: 		$record{"scantron.$questnum.answer"}='';
 5141: 	    } elsif (!defined($currentquest)
 5142: 		     || $currentquest eq $$scantron_config{'Qoff'}
 5143: 		     || $currentquest !~ /^[A-Z]$/) {
 5144: 		$record{"scantron.$questnum.answer"}='';
 5145: 		if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
 5146: 		    push(@{$record{"scantron.missingerror"}},$questnum);
 5147: 		}
 5148: 	    } else {
 5149: 		$record{"scantron.$questnum.answer"}=$currentquest;
 5150: 	    }
 5151: 	} elsif ($$scantron_config{'Qon'} eq 'number') {
 5152: 	    if ($currentquest eq '?'
 5153: 		|| $currentquest eq '*') {
 5154: 		push(@{$record{'scantron.doubleerror'}},$questnum);
 5155: 		$record{"scantron.$questnum.answer"}='';
 5156: 	    } elsif (!defined($currentquest)
 5157: 		     || $currentquest eq $$scantron_config{'Qoff'} 
 5158: 		     || $currentquest !~ /^\d$/) {
 5159: 		$record{"scantron.$questnum.answer"}='';
 5160: 		if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
 5161: 		    push(@{$record{"scantron.missingerror"}},$questnum);
 5162: 		}
 5163: 	    } else {
 5164: 		# wrap zero back to J
 5165: 		if ($currentquest eq '0') {
 5166: 		    $record{"scantron.$questnum.answer"}=
 5167: 			$alphabet[9];
 5168: 		} else {
 5169: 		    $record{"scantron.$questnum.answer"}=
 5170: 			$alphabet[$currentquest-1];
 5171: 		}
 5172: 	    }
 5173: 	} else {
 5174: 	    my @array=split($$scantron_config{'Qon'},$currentquest,-1);
 5175: 	    if (length($array[0]) eq $$scantron_config{'Qlength'}) {
 5176: 		$record{"scantron.$questnum.answer"}='';
 5177: 		if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
 5178: 		    push(@{$record{"scantron.missingerror"}},$questnum);
 5179: 		}
 5180: 	    } else {
 5181: 		$record{"scantron.$questnum.answer"}=
 5182: 		    $alphabet[length($array[0])];
 5183: 	    }
 5184: 	    if (scalar(@array) gt 2) {
 5185: 		push(@{$record{'scantron.doubleerror'}},$questnum);
 5186: 		my @ans=@array;
 5187: 		my $i=length($ans[0]);shift(@ans);
 5188: 		while ($#ans) {
 5189: 		    $i+=length($ans[0])+1;
 5190: 		    $record{"scantron.$questnum.answer"}.=$alphabet[$i];
 5191: 		    shift(@ans);
 5192: 		}
 5193: 	    }
 5194: 	}
 5195:     }
 5196:     $record{'scantron.maxquest'}=$questnum;
 5197:     return \%record;
 5198: }
 5199: 
 5200: =pod
 5201: 
 5202: =item scantron_add_delay
 5203: 
 5204:    Adds an error message that occurred during the grading phase to a
 5205:    queue of messages to be shown after grading pass is complete
 5206: 
 5207:  Arguments:
 5208:    $delayqueue  - arrary ref of hash ref of error messages
 5209:    $scanline    - the scanline that caused the error
 5210:    $errormesage - the error message
 5211:    $errorcode   - a numeric code for the error
 5212: 
 5213:  Side Effects:
 5214:    updates the $delayqueue to have a new hash ref of the error
 5215: 
 5216: =cut
 5217: 
 5218: sub scantron_add_delay {
 5219:     my ($delayqueue,$scanline,$errormessage,$errorcode)=@_;
 5220:     push(@$delayqueue,
 5221: 	 {'line' => $scanline, 'emsg' => $errormessage,
 5222: 	  'ecode' => $errorcode }
 5223: 	 );
 5224: }
 5225: 
 5226: =pod
 5227: 
 5228: =item scantron_find_student
 5229: 
 5230:    Finds the username for the current scanline
 5231: 
 5232:   Arguments:
 5233:    $scantron_record - hash result from scantron_parse_scanline
 5234:    $scan_data       - hash of correction information 
 5235:                       (see &scantron_getfile() form more information)
 5236:    $idmap           - hash from &username_to_idmap()
 5237:    $line            - number of current scanline
 5238:  
 5239:   Returns:
 5240:    Either 'username:domain' or undef if unknown
 5241: 
 5242: =cut
 5243: 
 5244: sub scantron_find_student {
 5245:     my ($scantron_record,$scan_data,$idmap,$line)=@_;
 5246:     my $scanID=$$scantron_record{'scantron.ID'};
 5247:     if ($scanID =~ /^\s*$/) {
 5248:  	return &scan_data($scan_data,"$line.user");
 5249:     }
 5250:     foreach my $id (keys(%$idmap)) {
 5251:  	if (lc($id) eq lc($scanID)) {
 5252:  	    return $$idmap{$id};
 5253:  	}
 5254:     }
 5255:     return undef;
 5256: }
 5257: 
 5258: =pod
 5259: 
 5260: =item scantron_filter
 5261: 
 5262:    Filter sub for lonnavmaps, filters out hidden resources if ignore
 5263:    hidden resources was selected
 5264: 
 5265: =cut
 5266: 
 5267: sub scantron_filter {
 5268:     my ($curres)=@_;
 5269: 
 5270:     if (ref($curres) && $curres->is_problem()) {
 5271: 	# if the user has asked to not have either hidden
 5272: 	# or 'randomout' controlled resources to be graded
 5273: 	# don't include them
 5274: 	if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
 5275: 	    && $curres->randomout) {
 5276: 	    return 0;
 5277: 	}
 5278: 	return 1;
 5279:     }
 5280:     return 0;
 5281: }
 5282: 
 5283: =pod
 5284: 
 5285: =item scantron_process_corrections
 5286: 
 5287:    Gets correction information out of submitted form data and corrects
 5288:    the scanline
 5289: 
 5290: =cut
 5291: 
 5292: sub scantron_process_corrections {
 5293:     my ($r) = @_;
 5294:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
 5295:     my ($scanlines,$scan_data)=&scantron_getfile();
 5296:     my $classlist=&Apache::loncoursedata::get_classlist();
 5297:     my $which=$env{'form.scantron_line'};
 5298:     my $line=&scantron_get_line($scanlines,$scan_data,$which);
 5299:     my ($skip,$err,$errmsg);
 5300:     if ($env{'form.scantron_skip_record'}) {
 5301: 	$skip=1;
 5302:     } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) {
 5303: 	my $newstudent=$env{'form.scantron_username'}.':'.
 5304: 	    $env{'form.scantron_domain'};
 5305: 	my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID];
 5306: 	($line,$err,$errmsg)=
 5307: 	    &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
 5308: 				     'ID',{'newid'=>$newid,
 5309: 				    'username'=>$env{'form.scantron_username'},
 5310: 				    'domain'=>$env{'form.scantron_domain'}});
 5311:     } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)CODE$/) {
 5312: 	my $resolution=$env{'form.scantron_CODE_resolution'};
 5313: 	my $newCODE;
 5314: 	my %args;
 5315: 	if      ($resolution eq 'use_unfound') {
 5316: 	    $newCODE='use_unfound';
 5317: 	} elsif ($resolution eq 'use_found') {
 5318: 	    $newCODE=$env{'form.scantron_CODE_selectedvalue'};
 5319: 	} elsif ($resolution eq 'use_typed') {
 5320: 	    $newCODE=$env{'form.scantron_CODE_newvalue'};
 5321: 	} elsif ($resolution =~ /^use_closest_(\d+)/) {
 5322: 	    $newCODE=$env{"form.scantron_CODE_closest_$1"};
 5323: 	}
 5324: 	if ($env{'form.scantron_corrections'} eq 'duplicateCODE') {
 5325: 	    $args{'CODE_ignore_dup'}=1;
 5326: 	}
 5327: 	$args{'CODE'}=$newCODE;
 5328: 	($line,$err,$errmsg)=
 5329: 	    &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
 5330: 				     'CODE',\%args);
 5331:     } elsif ($env{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) {
 5332: 	foreach my $question (split(',',$env{'form.scantron_questions'})) {
 5333: 	    ($line,$err,$errmsg)=
 5334: 		&scantron_fixup_scanline(\%scantron_config,$scan_data,$line,
 5335: 					 $which,'answer',
 5336: 					 { 'question'=>$question,
 5337: 		       'response'=>$env{"form.scantron_correct_Q_$question"}});
 5338: 	    if ($err) { last; }
 5339: 	}
 5340:     }
 5341:     if ($err) {
 5342: 	$r->print("<span class=\"LC_warning\">Unable to accept last correction, an error occurred :$errmsg:</span>");
 5343:     } else {
 5344: 	&scantron_put_line($scanlines,$scan_data,$which,$line,$skip);
 5345: 	&scantron_putfile($scanlines,$scan_data);
 5346:     }
 5347: }
 5348: 
 5349: =pod
 5350: 
 5351: =item reset_skipping_status
 5352: 
 5353:    Forgets the current set of remember skipped scanlines (and thus
 5354:    reverts back to considering all lines in the
 5355:    scantron_skipped_<filename> file)
 5356: 
 5357: =cut
 5358: 
 5359: sub reset_skipping_status {
 5360:     my ($scanlines,$scan_data)=&scantron_getfile();
 5361:     &scan_data($scan_data,'remember_skipping',undef,1);
 5362:     &scantron_putfile(undef,$scan_data);
 5363: }
 5364: 
 5365: =pod
 5366: 
 5367: =item start_skipping
 5368: 
 5369:    Marks a scanline to be skipped. 
 5370: 
 5371: =cut
 5372: 
 5373: sub start_skipping {
 5374:     my ($scan_data,$i)=@_;
 5375:     my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
 5376:     if ($env{'form.scantron_options_redo'} =~ /^redo_/) {
 5377: 	$remembered{$i}=2;
 5378:     } else {
 5379: 	$remembered{$i}=1;
 5380:     }
 5381:     &scan_data($scan_data,'remember_skipping',join(':',%remembered));
 5382: }
 5383: 
 5384: =pod
 5385: 
 5386: =item should_be_skipped
 5387: 
 5388:    Checks whether a scanline should be skipped.
 5389: 
 5390: =cut
 5391: 
 5392: sub should_be_skipped {
 5393:     my ($scanlines,$scan_data,$i)=@_;
 5394:     if ($env{'form.scantron_options_redo'} !~ /^redo_/) {
 5395: 	# not redoing old skips
 5396: 	if ($scanlines->{'skipped'}[$i]) { return 1; }
 5397: 	return 0;
 5398:     }
 5399:     my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
 5400: 
 5401:     if (exists($remembered{$i}) && $remembered{$i} != 2 ) {
 5402: 	return 0;
 5403:     }
 5404:     return 1;
 5405: }
 5406: 
 5407: =pod
 5408: 
 5409: =item remember_current_skipped
 5410: 
 5411:    Discovers what scanlines are in the scantron_skipped_<filename>
 5412:    file and remembers them into scan_data for later use.
 5413: 
 5414: =cut
 5415: 
 5416: sub remember_current_skipped {
 5417:     my ($scanlines,$scan_data)=&scantron_getfile();
 5418:     my %to_remember;
 5419:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 5420: 	if ($scanlines->{'skipped'}[$i]) {
 5421: 	    $to_remember{$i}=1;
 5422: 	}
 5423:     }
 5424: 
 5425:     &scan_data($scan_data,'remember_skipping',join(':',%to_remember));
 5426:     &scantron_putfile(undef,$scan_data);
 5427: }
 5428: 
 5429: =pod
 5430: 
 5431: =item check_for_error
 5432: 
 5433:     Checks if there was an error when attempting to remove a specific
 5434:     scantron_.. bubble sheet data file. Prints out an error if
 5435:     something went wrong.
 5436: 
 5437: =cut
 5438: 
 5439: sub check_for_error {
 5440:     my ($r,$result)=@_;
 5441:     if ($result ne 'ok' && $result ne 'not_found' ) {
 5442: 	$r->print("An error occurred ($result) when trying to Remove the existing corrections.");
 5443:     }
 5444: }
 5445: 
 5446: =pod
 5447: 
 5448: =item scantron_warning_screen
 5449: 
 5450:    Interstitial screen to make sure the operator has selected the
 5451:    correct options before we start the validation phase.
 5452: 
 5453: =cut
 5454: 
 5455: sub scantron_warning_screen {
 5456:     my ($button_text)=@_;
 5457:     my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
 5458:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
 5459:     my $CODElist;
 5460:     if ($scantron_config{'CODElocation'} &&
 5461: 	$scantron_config{'CODEstart'} &&
 5462: 	$scantron_config{'CODElength'}) {
 5463: 	$CODElist=$env{'form.scantron_CODElist'};
 5464: 	if ($env{'form.scantron_CODElist'} eq '') { $CODElist='<span class="LC_warning">None</span>'; }
 5465: 	$CODElist=
 5466: 	    '<tr><td><b>List of CODES to validate against:</b></td><td><tt>'.
 5467: 	    $env{'form.scantron_CODElist'}.'</tt></td></tr>';
 5468:     }
 5469:     return (<<STUFF);
 5470: <p>
 5471: <span class="LC_warning">Please double check the information
 5472:                  below before clicking on '$button_text'</span>
 5473: </p>
 5474: <table>
 5475: <tr><td><b>Sequence to be Graded:</b></td><td>$title</td></tr>
 5476: <tr><td><b>Data File that will be used:</b></td><td><tt>$env{'form.scantron_selectfile'}</tt></td></tr>
 5477: $CODElist
 5478: </table>
 5479: <br />
 5480: <p> If this information is correct, please click on '$button_text'.</p>
 5481: <p> If something is incorrect, please click the 'Grading Menu' button to start over.</p>
 5482: 
 5483: <br />
 5484: STUFF
 5485: }
 5486: 
 5487: =pod
 5488: 
 5489: =item scantron_do_warning
 5490: 
 5491:    Check if the operator has picked something for all required
 5492:    fields. Error out if something is missing.
 5493: 
 5494: =cut
 5495: 
 5496: sub scantron_do_warning {
 5497:     my ($r)=@_;
 5498:     my ($symb)=&get_symb($r);
 5499:     if (!$symb) {return '';}
 5500:     my $default_form_data=&defaultFormData($symb);
 5501:     $r->print(&scantron_form_start().$default_form_data);
 5502:     if ( $env{'form.selectpage'} eq '' ||
 5503: 	 $env{'form.scantron_selectfile'} eq '' ||
 5504: 	 $env{'form.scantron_format'} eq '' ) {
 5505: 	$r->print("<p>You have forgetten to specify some information. Please go Back and try again.</p>");
 5506: 	if ( $env{'form.selectpage'} eq '') {
 5507: 	    $r->print('<p><span class="LC_error">You have not selected a Sequence to grade</span></p>');
 5508: 	} 
 5509: 	if ( $env{'form.scantron_selectfile'} eq '') {
 5510: 	    $r->print('<p><span class="LC_error">You have not selected a file that contains the student\'s response data.</span></p>');
 5511: 	} 
 5512: 	if ( $env{'form.scantron_format'} eq '') {
 5513: 	    $r->print('<p><span class="LC_error">You have not selected a the format of the student\'s response data.</span></p>');
 5514: 	} 
 5515:     } else {
 5516: 	my $warning=&scantron_warning_screen('Grading: Validate Records');
 5517: 	$r->print(<<STUFF);
 5518: $warning
 5519: <input type="submit" name="submit" value="Grading: Validate Records" />
 5520: <input type="hidden" name="command" value="scantron_validate" />
 5521: STUFF
 5522:     }
 5523:     $r->print("</form><br />".&show_grading_menu_form($symb));
 5524:     return '';
 5525: }
 5526: 
 5527: =pod
 5528: 
 5529: =item scantron_form_start
 5530: 
 5531:     html hidden input for remembering all selected grading options
 5532: 
 5533: =cut
 5534: 
 5535: sub scantron_form_start {
 5536:     my ($max_bubble)=@_;
 5537:     my $result= <<SCANTRONFORM;
 5538: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
 5539:   <input type="hidden" name="selectpage" value="$env{'form.selectpage'}" />
 5540:   <input type="hidden" name="scantron_format" value="$env{'form.scantron_format'}" />
 5541:   <input type="hidden" name="scantron_selectfile" value="$env{'form.scantron_selectfile'}" />
 5542:   <input type="hidden" name="scantron_maxbubble" value="$max_bubble" />
 5543:   <input type="hidden" name="scantron_CODElist" value="$env{'form.scantron_CODElist'}" />
 5544:   <input type="hidden" name="scantron_CODEunique" value="$env{'form.scantron_CODEunique'}" />
 5545:   <input type="hidden" name="scantron_options_redo" value="$env{'form.scantron_options_redo'}" />
 5546:   <input type="hidden" name="scantron_options_ignore" value="$env{'form.scantron_options_ignore'}" />
 5547:   <input type="hidden" name="scantron_options_hidden" value="$env{'form.scantron_options_hidden'}" />
 5548: SCANTRONFORM
 5549:     return $result;
 5550: }
 5551: 
 5552: =pod
 5553: 
 5554: =item scantron_validate_file
 5555: 
 5556:     Dispatch routine for doing validation of a bubble sheet data file.
 5557: 
 5558:     Also processes any necessary information resets that need to
 5559:     occur before validation begins (ignore previous corrections,
 5560:     restarting the skipped records processing)
 5561: 
 5562: =cut
 5563: 
 5564: sub scantron_validate_file {
 5565:     my ($r) = @_;
 5566:     my ($symb)=&get_symb($r);
 5567:     if (!$symb) {return '';}
 5568:     my $default_form_data=&defaultFormData($symb);
 5569:     
 5570:     # do the detection of only doing skipped records first befroe we delete
 5571:     # them when doing the corrections reset
 5572:     if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') {
 5573: 	&reset_skipping_status();
 5574:     }
 5575:     if ($env{'form.scantron_options_redo'} eq 'redo_skipped') {
 5576: 	&remember_current_skipped();
 5577: 	$env{'form.scantron_options_redo'}='redo_skipped_ready';
 5578:     }
 5579: 
 5580:     if ($env{'form.scantron_options_ignore'} eq 'ignore_corrections') {
 5581: 	&check_for_error($r,&scantron_remove_file('corrected'));
 5582: 	&check_for_error($r,&scantron_remove_file('skipped'));
 5583: 	&check_for_error($r,&scantron_remove_scan_data());
 5584: 	$env{'form.scantron_options_ignore'}='done';
 5585:     }
 5586: 
 5587:     if ($env{'form.scantron_corrections'}) {
 5588: 	&scantron_process_corrections($r);
 5589:     }
 5590:     $r->print("<p>Gathering necessary info.</p>");$r->rflush();
 5591:     #get the student pick code ready
 5592:     $r->print(&Apache::loncommon::studentbrowser_javascript());
 5593:     my $max_bubble=&scantron_get_maxbubble();
 5594:     my $result=&scantron_form_start($max_bubble).$default_form_data;
 5595:     $r->print($result);
 5596:     
 5597:     my @validate_phases=( 'sequence',
 5598: 			  'ID',
 5599: 			  'CODE',
 5600: 			  'doublebubble',
 5601: 			  'missingbubbles');
 5602:     if (!$env{'form.validatepass'}) {
 5603: 	$env{'form.validatepass'} = 0;
 5604:     }
 5605:     my $currentphase=$env{'form.validatepass'};
 5606: 
 5607:     my $stop=0;
 5608:     while (!$stop && $currentphase < scalar(@validate_phases)) {
 5609: 	$r->print("<p> Validating ".$validate_phases[$currentphase]."</p>");
 5610: 	$r->rflush();
 5611: 	my $which="scantron_validate_".$validate_phases[$currentphase];
 5612: 	{
 5613: 	    no strict 'refs';
 5614: 	    ($stop,$currentphase)=&$which($r,$currentphase);
 5615: 	}
 5616:     }
 5617:     if (!$stop) {
 5618: 	my $warning=&scantron_warning_screen('Start Grading');
 5619: 	$r->print(<<STUFF);
 5620: Validation process complete.<br />
 5621: $warning
 5622: <input type="submit" name="submit" value="Start Grading" />
 5623: <input type="hidden" name="command" value="scantron_process" />
 5624: STUFF
 5625: 
 5626:     } else {
 5627: 	$r->print('<input type="hidden" name="command" value="scantron_validate" />');
 5628: 	$r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />");
 5629:     }
 5630:     if ($stop) {
 5631: 	if ($validate_phases[$currentphase] eq 'sequence') {
 5632: 	    $r->print('<input type="submit" name="submit" value="Ignore -> " />');
 5633: 	    $r->print(' this error <br />');
 5634: 
 5635: 	    $r->print(" <p>Or click the 'Grading Menu' button to start over.</p>");
 5636: 	} else {
 5637: 	    $r->print('<input type="submit" name="submit" value="Continue ->" />');
 5638: 	    $r->print(' using corrected info <br />');
 5639: 	    $r->print("<input type='submit' value='Skip' name='scantron_skip_record' />");
 5640: 	    $r->print(" this scanline saving it for later.");
 5641: 	}
 5642:     }
 5643:     $r->print(" </form><br />".&show_grading_menu_form($symb));
 5644:     return '';
 5645: }
 5646: 
 5647: 
 5648: =pod
 5649: 
 5650: =item scantron_remove_file
 5651: 
 5652:    Removes the requested bubble sheet data file, makes sure that
 5653:    scantron_original_<filename> is never removed
 5654: 
 5655: 
 5656: =cut
 5657: 
 5658: sub scantron_remove_file {
 5659:     my ($which)=@_;
 5660:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 5661:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 5662:     my $file='scantron_';
 5663:     if ($which eq 'corrected' || $which eq 'skipped') {
 5664: 	$file.=$which.'_';
 5665:     } else {
 5666: 	return 'refused';
 5667:     }
 5668:     $file.=$env{'form.scantron_selectfile'};
 5669:     return &Apache::lonnet::removeuserfile($cname,$cdom,$file);
 5670: }
 5671: 
 5672: 
 5673: =pod
 5674: 
 5675: =item scantron_remove_scan_data
 5676: 
 5677:    Removes all scan_data correction for the requested bubble sheet
 5678:    data file.  (In the case that both the are doing skipped records we need
 5679:    to remember the old skipped lines for the time being so that element
 5680:    persists for a while.)
 5681: 
 5682: =cut
 5683: 
 5684: sub scantron_remove_scan_data {
 5685:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 5686:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 5687:     my @keys=&Apache::lonnet::getkeys('nohist_scantrondata',$cdom,$cname);
 5688:     my @todelete;
 5689:     my $filename=$env{'form.scantron_selectfile'};
 5690:     foreach my $key (@keys) {
 5691: 	if ($key=~/^\Q$filename\E_/) {
 5692: 	    if ($env{'form.scantron_options_redo'} eq 'redo_skipped_ready' &&
 5693: 		$key=~/remember_skipping/) {
 5694: 		next;
 5695: 	    }
 5696: 	    push(@todelete,$key);
 5697: 	}
 5698:     }
 5699:     my $result;
 5700:     if (@todelete) {
 5701: 	$result=&Apache::lonnet::del('nohist_scantrondata',\@todelete,$cdom,$cname);
 5702:     }
 5703:     return $result;
 5704: }
 5705: 
 5706: 
 5707: =pod
 5708: 
 5709: =item scantron_getfile
 5710: 
 5711:     Fetches the requested bubble sheet data file (all 3 versions), and
 5712:     the scan_data hash
 5713:   
 5714:   Arguments:
 5715:     None
 5716: 
 5717:   Returns:
 5718:     2 hash references
 5719: 
 5720:      - first one has 
 5721:          orig      -
 5722:          corrected -
 5723:          skipped   -  each of which points to an array ref of the specified
 5724:                       file broken up into individual lines
 5725:          count     - number of scanlines
 5726:  
 5727:      - second is the scan_data hash possible keys are
 5728:        ($number refers to scanline numbered $number and thus the key affects
 5729:         only that scanline
 5730:         $bubline refers to the specific bubble line element and the aspects
 5731:         refers to that specific bubble line element)
 5732: 
 5733:        $number.user - username:domain to use
 5734:        $number.CODE_ignore_dup 
 5735:                     - ignore the duplicate CODE error 
 5736:        $number.useCODE
 5737:                     - use the CODE in the scanline as is
 5738:        $number.no_bubble.$bubline
 5739:                     - it is valid that there is no bubbled in bubble
 5740:                       at $number $bubline
 5741:        remember_skipping
 5742:                     - a frozen hash containing keys of $number and values
 5743:                       of either 
 5744:                         1 - we are on a 'do skipped records pass' and plan
 5745:                             on processing this line
 5746:                         2 - we are on a 'do skipped records pass' and this
 5747:                             scanline has been marked to skip yet again
 5748: 
 5749: =cut
 5750: 
 5751: sub scantron_getfile {
 5752:     #FIXME really would prefer a scantron directory
 5753:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 5754:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 5755:     my $lines;
 5756:     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
 5757: 		       'scantron_orig_'.$env{'form.scantron_selectfile'});
 5758:     my %scanlines;
 5759:     $scanlines{'orig'}=[(split("\n",$lines,-1))];
 5760:     my $temp=$scanlines{'orig'};
 5761:     $scanlines{'count'}=$#$temp;
 5762: 
 5763:     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
 5764: 		       'scantron_corrected_'.$env{'form.scantron_selectfile'});
 5765:     if ($lines eq '-1') {
 5766: 	$scanlines{'corrected'}=[];
 5767:     } else {
 5768: 	$scanlines{'corrected'}=[(split("\n",$lines,-1))];
 5769:     }
 5770:     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
 5771: 		       'scantron_skipped_'.$env{'form.scantron_selectfile'});
 5772:     if ($lines eq '-1') {
 5773: 	$scanlines{'skipped'}=[];
 5774:     } else {
 5775: 	$scanlines{'skipped'}=[(split("\n",$lines,-1))];
 5776:     }
 5777:     my @tmp=&Apache::lonnet::dump('nohist_scantrondata',$cdom,$cname);
 5778:     if ($tmp[0] =~ /^(error:|no_such_host)/) { @tmp=(); }
 5779:     my %scan_data = @tmp;
 5780:     return (\%scanlines,\%scan_data);
 5781: }
 5782: 
 5783: =pod
 5784: 
 5785: =item lonnet_putfile
 5786: 
 5787:    Wrapper routine to call &Apache::lonnet::finishuserfileupload
 5788: 
 5789:  Arguments:
 5790:    $contents - data to store
 5791:    $filename - filename to store $contents into
 5792: 
 5793:  Returns:
 5794:    result value from &Apache::lonnet::finishuserfileupload
 5795: 
 5796: =cut
 5797: 
 5798: sub lonnet_putfile {
 5799:     my ($contents,$filename)=@_;
 5800:     my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};
 5801:     my $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 5802:     $env{'form.sillywaytopassafilearound'}=$contents;
 5803:     &Apache::lonnet::finishuserfileupload($docuname,$docudom,'sillywaytopassafilearound',$filename);
 5804: 
 5805: }
 5806: 
 5807: =pod
 5808: 
 5809: =item scantron_putfile
 5810: 
 5811:     Stores the current version of the bubble sheet data files, and the
 5812:     scan_data hash. (Does not modify the original version only the
 5813:     corrected and skipped versions.
 5814: 
 5815:  Arguments:
 5816:     $scanlines - hash ref that looks like the first return value from
 5817:                  &scantron_getfile()
 5818:     $scan_data - hash ref that looks like the second return value from
 5819:                  &scantron_getfile()
 5820: 
 5821: =cut
 5822: 
 5823: sub scantron_putfile {
 5824:     my ($scanlines,$scan_data) = @_;
 5825:     #FIXME really would prefer a scantron directory
 5826:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 5827:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 5828:     if ($scanlines) {
 5829: 	my $prefix='scantron_';
 5830: # no need to update orig, shouldn't change
 5831: #   &lonnet_putfile(join("\n",@{$scanlines->{'orig'}}),$prefix.'orig_'.
 5832: #		    $env{'form.scantron_selectfile'});
 5833: 	&lonnet_putfile(join("\n",@{$scanlines->{'corrected'}}),
 5834: 			$prefix.'corrected_'.
 5835: 			$env{'form.scantron_selectfile'});
 5836: 	&lonnet_putfile(join("\n",@{$scanlines->{'skipped'}}),
 5837: 			$prefix.'skipped_'.
 5838: 			$env{'form.scantron_selectfile'});
 5839:     }
 5840:     &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname);
 5841: }
 5842: 
 5843: =pod
 5844: 
 5845: =item scantron_get_line
 5846: 
 5847:    Returns the correct version of the scanline
 5848: 
 5849:  Arguments:
 5850:     $scanlines - hash ref that looks like the first return value from
 5851:                  &scantron_getfile()
 5852:     $scan_data - hash ref that looks like the second return value from
 5853:                  &scantron_getfile()
 5854:     $i         - number of the requested line (starts at 0)
 5855: 
 5856:  Returns:
 5857:    A scanline, (either the original or the corrected one if it
 5858:    exists), or undef if the requested scanline should be
 5859:    skipped. (Either because it's an skipped scanline, or it's an
 5860:    unskipped scanline and we are not doing a 'do skipped scanlines'
 5861:    pass.
 5862: 
 5863: =cut
 5864: 
 5865: sub scantron_get_line {
 5866:     my ($scanlines,$scan_data,$i)=@_;
 5867:     if (&should_be_skipped($scanlines,$scan_data,$i)) { return undef; }
 5868:     #if ($scanlines->{'skipped'}[$i]) { return undef; }
 5869:     if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$i];}
 5870:     return $scanlines->{'orig'}[$i]; 
 5871: }
 5872: 
 5873: =pod
 5874: 
 5875: =item scantron_todo_count
 5876: 
 5877:     Counts the number of scanlines that need processing.
 5878: 
 5879:  Arguments:
 5880:     $scanlines - hash ref that looks like the first return value from
 5881:                  &scantron_getfile()
 5882:     $scan_data - hash ref that looks like the second return value from
 5883:                  &scantron_getfile()
 5884: 
 5885:  Returns:
 5886:     $count - number of scanlines to process
 5887: 
 5888: =cut
 5889: 
 5890: sub get_todo_count {
 5891:     my ($scanlines,$scan_data)=@_;
 5892:     my $count=0;
 5893:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 5894: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 5895: 	if ($line=~/^[\s\cz]*$/) { next; }
 5896: 	$count++;
 5897:     }
 5898:     return $count;
 5899: }
 5900: 
 5901: =pod
 5902: 
 5903: =item scantron_put_line
 5904: 
 5905:     Updates the 'corrected' or 'skipped' versions of the bubble sheet
 5906:     data file.
 5907: 
 5908:  Arguments:
 5909:     $scanlines - hash ref that looks like the first return value from
 5910:                  &scantron_getfile()
 5911:     $scan_data - hash ref that looks like the second return value from
 5912:                  &scantron_getfile()
 5913:     $i         - line number to update
 5914:     $newline   - contents of the updated scanline
 5915:     $skip      - if true make the line for skipping and update the
 5916:                  'skipped' file
 5917: 
 5918: =cut
 5919: 
 5920: sub scantron_put_line {
 5921:     my ($scanlines,$scan_data,$i,$newline,$skip)=@_;
 5922:     if ($skip) {
 5923: 	$scanlines->{'skipped'}[$i]=$newline;
 5924: 	&start_skipping($scan_data,$i);
 5925: 	return;
 5926:     }
 5927:     $scanlines->{'corrected'}[$i]=$newline;
 5928: }
 5929: 
 5930: =pod
 5931: 
 5932: =item scantron_clear_skip
 5933: 
 5934:    Remove a line from the 'skipped' file
 5935: 
 5936:  Arguments:
 5937:     $scanlines - hash ref that looks like the first return value from
 5938:                  &scantron_getfile()
 5939:     $scan_data - hash ref that looks like the second return value from
 5940:                  &scantron_getfile()
 5941:     $i         - line number to update
 5942: 
 5943: =cut
 5944: 
 5945: sub scantron_clear_skip {
 5946:     my ($scanlines,$scan_data,$i)=@_;
 5947:     if (exists($scanlines->{'skipped'}[$i])) {
 5948: 	undef($scanlines->{'skipped'}[$i]);
 5949: 	return 1;
 5950:     }
 5951:     return 0;
 5952: }
 5953: 
 5954: =pod
 5955: 
 5956: =item scantron_filter_not_exam
 5957: 
 5958:    Filter routine used by &Apache::lonnavmaps::retrieveResources(), to
 5959:    filter out resources that are not marked as 'exam' mode
 5960: 
 5961: =cut
 5962: 
 5963: sub scantron_filter_not_exam {
 5964:     my ($curres)=@_;
 5965:     
 5966:     if (ref($curres) && $curres->is_problem() && !$curres->is_exam()) {
 5967: 	# if the user has asked to not have either hidden
 5968: 	# or 'randomout' controlled resources to be graded
 5969: 	# don't include them
 5970: 	if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
 5971: 	    && $curres->randomout) {
 5972: 	    return 0;
 5973: 	}
 5974: 	return 1;
 5975:     }
 5976:     return 0;
 5977: }
 5978: 
 5979: =pod
 5980: 
 5981: =item scantron_validate_sequence
 5982: 
 5983:     Validates the selected sequence, checking for resource that are
 5984:     not set to exam mode.
 5985: 
 5986: =cut
 5987: 
 5988: sub scantron_validate_sequence {
 5989:     my ($r,$currentphase) = @_;
 5990: 
 5991:     my $navmap=Apache::lonnavmaps::navmap->new();
 5992:     my (undef,undef,$sequence)=
 5993: 	&Apache::lonnet::decode_symb($env{'form.selectpage'});
 5994: 
 5995:     my $map=$navmap->getResourceByUrl($sequence);
 5996: 
 5997:     $r->print('<input type="hidden" name="validate_sequence_exam"
 5998:                                     value="ignore" />');
 5999:     if ($env{'form.validate_sequence_exam'} ne 'ignore') {
 6000: 	my @resources=
 6001: 	    $navmap->retrieveResources($map,\&scantron_filter_not_exam,1,0);
 6002: 	if (@resources) {
 6003: 	    $r->print("<p>".&mt('Some resources in the sequence currently are not set to exam mode. Grading these resources currently may not work correctly.')."</p>");
 6004: 	    return (1,$currentphase);
 6005: 	}
 6006:     }
 6007: 
 6008:     return (0,$currentphase+1);
 6009: }
 6010: 
 6011: =pod
 6012: 
 6013: =item scantron_validate_ID
 6014: 
 6015:    Validates all scanlines in the selected file to not have any
 6016:    invalid or underspecified student IDs
 6017: 
 6018: =cut
 6019: 
 6020: sub scantron_validate_ID {
 6021:     my ($r,$currentphase) = @_;
 6022:     
 6023:     #get student info
 6024:     my $classlist=&Apache::loncoursedata::get_classlist();
 6025:     my %idmap=&username_to_idmap($classlist);
 6026: 
 6027:     #get scantron line setup
 6028:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
 6029:     my ($scanlines,$scan_data)=&scantron_getfile();
 6030: 
 6031:     my %found=('ids'=>{},'usernames'=>{});
 6032:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 6033: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 6034: 	if ($line=~/^[\s\cz]*$/) { next; }
 6035: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
 6036: 						 $scan_data);
 6037: 	my $id=$$scan_record{'scantron.ID'};
 6038: 	my $found;
 6039: 	foreach my $checkid (keys(%idmap)) {
 6040: 	    if (lc($checkid) eq lc($id)) { $found=$checkid;last; }
 6041: 	}
 6042: 	if ($found) {
 6043: 	    my $username=$idmap{$found};
 6044: 	    if ($found{'ids'}{$found}) {
 6045: 		&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
 6046: 					 $line,'duplicateID',$found);
 6047: 		return(1,$currentphase);
 6048: 	    } elsif ($found{'usernames'}{$username}) {
 6049: 		&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
 6050: 					 $line,'duplicateID',$username);
 6051: 		return(1,$currentphase);
 6052: 	    }
 6053: 	    #FIXME store away line we previously saw the ID on to use above
 6054: 	    $found{'ids'}{$found}++;
 6055: 	    $found{'usernames'}{$username}++;
 6056: 	} else {
 6057: 	    if ($id =~ /^\s*$/) {
 6058: 		my $username=&scan_data($scan_data,"$i.user");
 6059: 		if (defined($username) && $found{'usernames'}{$username}) {
 6060: 		    &scantron_get_correction($r,$i,$scan_record,
 6061: 					     \%scantron_config,
 6062: 					     $line,'duplicateID',$username);
 6063: 		    return(1,$currentphase);
 6064: 		} elsif (!defined($username)) {
 6065: 		    &scantron_get_correction($r,$i,$scan_record,
 6066: 					     \%scantron_config,
 6067: 					     $line,'incorrectID');
 6068: 		    return(1,$currentphase);
 6069: 		}
 6070: 		$found{'usernames'}{$username}++;
 6071: 	    } else {
 6072: 		&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
 6073: 					 $line,'incorrectID');
 6074: 		return(1,$currentphase);
 6075: 	    }
 6076: 	}
 6077:     }
 6078: 
 6079:     return (0,$currentphase+1);
 6080: }
 6081: 
 6082: =pod
 6083: 
 6084: =item scantron_get_correction
 6085: 
 6086:    Builds the interface screen to interact with the operator to fix a
 6087:    specific error condition in a specific scanline
 6088: 
 6089:  Arguments:
 6090:     $r           - Apache request object
 6091:     $i           - number of the current scanline
 6092:     $scan_record - hash ref as returned from &scantron_parse_scanline()
 6093:     $scan_config - hash ref as returned from &get_scantron_config()
 6094:     $line        - full contents of the current scanline
 6095:     $error       - error condition, valid values are
 6096:                    'incorrectCODE', 'duplicateCODE',
 6097:                    'doublebubble', 'missingbubble',
 6098:                    'duplicateID', 'incorrectID'
 6099:     $arg         - extra information needed
 6100:        For errors:
 6101:          - duplicateID   - paper number that this studentID was seen before on
 6102:          - duplicateCODE - array ref of the paper numbers this CODE was
 6103:                            seen on before
 6104:          - incorrectCODE - current incorrect CODE 
 6105:          - doublebubble  - array ref of the bubble lines that have double
 6106:                            bubble errors
 6107:          - missingbubble - array ref of the bubble lines that have missing
 6108:                            bubble errors
 6109: 
 6110: =cut
 6111: 
 6112: sub scantron_get_correction {
 6113:     my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;
 6114: 
 6115: #FIXME in the case of a duplicated ID the previous line, probaly need
 6116: #to show both the current line and the previous one and allow skipping
 6117: #the previous one or the current one
 6118: 
 6119:     $r->print("<p><b>An error was detected ($error)</b>");
 6120:     if ( $$scan_record{'scantron.PaperID'} =~ /\S/) {
 6121: 	$r->print(" for PaperID <tt>".
 6122: 		  $$scan_record{'scantron.PaperID'}."</tt> \n");
 6123:     } else {
 6124: 	$r->print(" in scanline $i <pre>".
 6125: 		  $line."</pre> \n");
 6126:     }
 6127:     my $message="<p>The ID on the form is  <tt>".
 6128: 	$$scan_record{'scantron.ID'}."</tt><br />\n".
 6129: 	"The name on the paper is ".
 6130: 	$$scan_record{'scantron.LastName'}.",".
 6131: 	$$scan_record{'scantron.FirstName'}."</p>";
 6132: 
 6133:     $r->print('<input type="hidden" name="scantron_corrections" value="'.$error.'" />'."\n");
 6134:     $r->print('<input type="hidden" name="scantron_line" value="'.$i.'" />'."\n");
 6135:     if ($error =~ /ID$/) {
 6136: 	if ($error eq 'incorrectID') {
 6137: 	    $r->print("The encoded ID is not in the classlist</p>\n");
 6138: 	} elsif ($error eq 'duplicateID') {
 6139: 	    $r->print("The encoded ID has also been used by a previous paper $arg</p>\n");
 6140: 	}
 6141: 	$r->print($message);
 6142: 	$r->print("<p>How should I handle this? <br /> \n");
 6143: 	$r->print("\n<ul><li> ");
 6144: 	#FIXME it would be nice if this sent back the user ID and
 6145: 	#could do partial userID matches
 6146: 	$r->print(&Apache::loncommon::selectstudent_link('scantronupload',
 6147: 				       'scantron_username','scantron_domain'));
 6148: 	$r->print(": <input type='text' name='scantron_username' value='' />");
 6149: 	$r->print("\n@".
 6150: 		 &Apache::loncommon::select_dom_form($env{'request.role.domain'},'scantron_domain'));
 6151: 
 6152: 	$r->print('</li>');
 6153:     } elsif ($error =~ /CODE$/) {
 6154: 	if ($error eq 'incorrectCODE') {
 6155: 	    $r->print("</p><p>The encoded CODE is not in the list of possible CODEs</p>\n");
 6156: 	} elsif ($error eq 'duplicateCODE') {
 6157: 	    $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");
 6158: 	}
 6159: 	$r->print("<p>The CODE on the form is  <tt>'".
 6160: 		  $$scan_record{'scantron.CODE'}."'</tt><br />\n");
 6161: 	$r->print($message);
 6162: 	$r->print("<p>How should I handle this? <br /> \n");
 6163: 	$r->print("\n<br /> ");
 6164: 	my $i=0;
 6165: 	if ($error eq 'incorrectCODE' 
 6166: 	    && $$scan_record{'scantron.CODE'}=~/\S/ ) {
 6167: 	    my ($max,$closest)=&scantron_get_closely_matching_CODEs($arg,$$scan_record{'scantron.CODE'});
 6168: 	    if ($closest > 0) {
 6169: 		foreach my $testcode (@{$closest}) {
 6170: 		    my $checked='';
 6171: 		    if (!$i) { $checked=' checked="checked" '; }
 6172: 		    $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_closest_$i' $checked /> Use the similar CODE <b><tt>".$testcode."</tt></b> instead.</label><input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />");
 6173: 		    $r->print("\n<br />");
 6174: 		    $i++;
 6175: 		}
 6176: 	    }
 6177: 	}
 6178: 	if ($$scan_record{'scantron.CODE'}=~/\S/ ) {
 6179: 	    my $checked; if (!$i) { $checked=' checked="checked" '; }
 6180: 	    $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_unfound' $checked /> Use the CODE <b><tt>".$$scan_record{'scantron.CODE'}."</tt></b> that is was on the paper, ignoring the error.</label>");
 6181: 	    $r->print("\n<br />");
 6182: 	}
 6183: 
 6184: 	$r->print(<<ENDSCRIPT);
 6185: <script type="text/javascript">
 6186: function change_radio(field) {
 6187:     var slct=document.scantronupload.scantron_CODE_resolution;
 6188:     var i;
 6189:     for (i=0;i<slct.length;i++) {
 6190:         if (slct[i].value==field) { slct[i].checked=true; }
 6191:     }
 6192: }
 6193: </script>
 6194: ENDSCRIPT
 6195: 	my $href="/adm/pickcode?".
 6196: 	   "form=".&escape("scantronupload").
 6197: 	   "&scantron_format=".&escape($env{'form.scantron_format'}).
 6198: 	   "&scantron_CODElist=".&escape($env{'form.scantron_CODElist'}).
 6199: 	   "&curCODE=".&escape($$scan_record{'scantron.CODE'}).
 6200: 	   "&scantron_selectfile=".&escape($env{'form.scantron_selectfile'});
 6201: 	if ($env{'form.scantron_CODElist'} =~ /\S/) { 
 6202: 	    $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_found' /> <a target='_blank' href='$href'>Select</a> a CODE from the list of all CODEs and use it.</label> Selected CODE is <input readonly='true' type='text' size='8' name='scantron_CODE_selectedvalue' onfocus=\"javascript:change_radio('use_found')\" onchange=\"javascript:change_radio('use_found')\" />");
 6203: 	    $r->print("\n<br />");
 6204: 	}
 6205: 	$r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_typed' /> Use </label><input type='text' size='8' name='scantron_CODE_newvalue' onfocus=\"javascript:change_radio('use_typed')\" onkeypress=\"javascript:change_radio('use_typed')\" /> as the CODE.");
 6206: 	$r->print("\n<br /><br />");
 6207:     } elsif ($error eq 'doublebubble') {
 6208: 	$r->print("<p>There have been multiple bubbles scanned for a some question(s)</p>\n");
 6209: 	$r->print('<input type="hidden" name="scantron_questions" value="'.
 6210: 		  join(',',@{$arg}).'" />');
 6211: 	$r->print($message);
 6212: 	$r->print("<p>Please indicate which bubble should be used for grading</p>");
 6213: 	foreach my $question (@{$arg}) {
 6214: 	    my $selected=$$scan_record{"scantron.$question.answer"};
 6215: 	    &scantron_bubble_selector($r,$scan_config,$question,
 6216: 				      split('',$selected));
 6217: 	}
 6218:     } elsif ($error eq 'missingbubble') {
 6219: 	$r->print("<p>There have been <b>no</b> bubbles scanned for some question(s)</p>\n");
 6220: 	$r->print($message);
 6221: 	$r->print("<p>Please indicate which bubble should be used for grading</p>");
 6222: 	$r->print("Some questions have no scanned bubbles\n");
 6223: 	$r->print('<input type="hidden" name="scantron_questions" value="'.
 6224: 		  join(',',@{$arg}).'" />');
 6225: 	foreach my $question (@{$arg}) {
 6226: 	    my $selected=$$scan_record{"scantron.$question.answer"};
 6227: 	    &scantron_bubble_selector($r,$scan_config,$question);
 6228: 	}
 6229:     } else {
 6230: 	$r->print("\n<ul>");
 6231:     }
 6232:     $r->print("\n</li></ul>");
 6233: 
 6234: }
 6235: 
 6236: =pod
 6237: 
 6238: =item scantron_bubble_selector
 6239:   
 6240:    Generates the html radiobuttons to correct a single bubble line
 6241:    possibly showing the existing the selected bubbles if known
 6242: 
 6243:  Arguments:
 6244:     $r           - Apache request object
 6245:     $scan_config - hash from &get_scantron_config()
 6246:     $quest       - number of the bubble line to make a corrector for
 6247:     $selected    - array of letters of previously selected bubbles
 6248:     $lines       - if present, number of bubble lines to show
 6249: 
 6250: =cut
 6251: 
 6252: sub scantron_bubble_selector {
 6253:     my ($r,$scan_config,$quest,@selected, $lines)=@_;
 6254:     my $max=$$scan_config{'Qlength'};
 6255: 
 6256:     my $scmode=$$scan_config{'Qon'};
 6257:     if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }	     
 6258: 
 6259: 
 6260:     if (!defined($lines)) {
 6261: 	$lines = 1;
 6262:     }
 6263:     my $total_lines = $lines*2;
 6264:     my @alphabet=('A'..'Z');
 6265:     $r->print("<table border='1'><tr><td rowspan='".$total_lines."'>$quest</td>");
 6266: 
 6267:     for (my $l = 0; $l < $lines; $l++) {
 6268: 	if ($l != 0) {
 6269: 	    $r->print('<tr>');
 6270: 	}
 6271: 
 6272: 	# FIXME:  This loop probably has to be considerably more clever for
 6273: 	#  multiline bubbles: User can multibubble by having bubbles in
 6274: 	#  several lines.  User can skip lines legitimately etc. etc.
 6275: 
 6276: 	for (my $i=0;$i<$max;$i++) {
 6277: 	    $r->print("\n".'<td align="center">');
 6278: 	    if ($selected[0] eq $alphabet[$i]) { 
 6279: 		$r->print('X'); 
 6280: 		shift(@selected) ;
 6281: 	    } else { 
 6282: 		$r->print('&nbsp;'); 
 6283: 	    }
 6284: 	    $r->print('</td>');
 6285: 	    
 6286: 	}
 6287: 
 6288: 	if ($l == 0) {
 6289: 	    my $lspan = $total_lines * 2;   #  2 table rows per bubble line.
 6290: 
 6291: 	    $r->print('<td rowspan='.$lspan.'><label><input type="radio" name="scantron_correct_Q_'.
 6292: 	      $quest.'" value="none" /> No bubble </label></td>');
 6293: 	
 6294: 	}
 6295: 
 6296: 	$r->print('</tr><tr>');
 6297: 
 6298: 	# FIXME: This may have to be a bit more clever for
 6299: 	#        multiline questions (different values e.g..).
 6300: 
 6301: 	for (my $i=0;$i<$max;$i++) {
 6302: 	    $r->print("\n".
 6303: 		      '<td><label><input type="radio" name="scantron_correct_Q_'.
 6304: 		      $quest.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");
 6305: 	}
 6306: 	$r->print('</tr>');
 6307: 
 6308: 	    
 6309:     }
 6310:     $r->print('</table>');
 6311: }
 6312: 
 6313: =pod
 6314: 
 6315: =item num_matches
 6316: 
 6317:    Counts the number of characters that are the same between the two arguments.
 6318: 
 6319:  Arguments:
 6320:    $orig - CODE from the scanline
 6321:    $code - CODE to match against
 6322: 
 6323:  Returns:
 6324:    $count - integer count of the number of same characters between the
 6325:             two arguments
 6326: 
 6327: =cut
 6328: 
 6329: sub num_matches {
 6330:     my ($orig,$code) = @_;
 6331:     my @code=split(//,$code);
 6332:     my @orig=split(//,$orig);
 6333:     my $same=0;
 6334:     for (my $i=0;$i<scalar(@code);$i++) {
 6335: 	if ($code[$i] eq $orig[$i]) { $same++; }
 6336:     }
 6337:     return $same;
 6338: }
 6339: 
 6340: =pod
 6341: 
 6342: =item scantron_get_closely_matching_CODEs
 6343: 
 6344:    Cycles through all CODEs and finds the set that has the greatest
 6345:    number of same characters as the provided CODE
 6346: 
 6347:  Arguments:
 6348:    $allcodes - hash ref returned by &get_codes()
 6349:    $CODE     - CODE from the current scanline
 6350: 
 6351:  Returns:
 6352:    2 element list
 6353:     - first elements is number of how closely matching the best fit is 
 6354:       (5 means best set has 5 matching characters)
 6355:     - second element is an arrary ref containing the set of valid CODEs
 6356:       that best fit the passed in CODE
 6357: 
 6358: =cut
 6359: 
 6360: sub scantron_get_closely_matching_CODEs {
 6361:     my ($allcodes,$CODE)=@_;
 6362:     my @CODEs;
 6363:     foreach my $testcode (sort(keys(%{$allcodes}))) {
 6364: 	push(@{$CODEs[&num_matches($CODE,$testcode)]},$testcode);
 6365:     }
 6366: 
 6367:     return ($#CODEs,$CODEs[-1]);
 6368: }
 6369: 
 6370: =pod
 6371: 
 6372: =item get_codes
 6373: 
 6374:    Builds a hash which has keys of all of the valid CODEs from the selected
 6375:    set of remembered CODEs.
 6376: 
 6377:  Arguments:
 6378:   $old_name - name of the set of remembered CODEs
 6379:   $cdom     - domain of the course
 6380:   $cnum     - internal course name
 6381: 
 6382:  Returns:
 6383:   %allcodes - keys are the valid CODEs, values are all 1
 6384: 
 6385: =cut
 6386: 
 6387: sub get_codes {
 6388:     my ($old_name, $cdom, $cnum) = @_;
 6389:     if (!$old_name) {
 6390: 	$old_name=$env{'form.scantron_CODElist'};
 6391:     }
 6392:     if (!$cdom) {
 6393: 	$cdom =$env{'course.'.$env{'request.course.id'}.'.domain'};
 6394:     }
 6395:     if (!$cnum) {
 6396: 	$cnum =$env{'course.'.$env{'request.course.id'}.'.num'};
 6397:     }
 6398:     my %result=&Apache::lonnet::get('CODEs',[$old_name,"type\0$old_name"],
 6399: 				    $cdom,$cnum);
 6400:     my %allcodes;
 6401:     if ($result{"type\0$old_name"} eq 'number') {
 6402: 	%allcodes=map {($_,1)} split(',',$result{$old_name});
 6403:     } else {
 6404: 	%allcodes=map {(&Apache::lonprintout::num_to_letters($_),1)} split(',',$result{$old_name});
 6405:     }
 6406:     return %allcodes;
 6407: }
 6408: 
 6409: =pod
 6410: 
 6411: =item scantron_validate_CODE
 6412: 
 6413:    Validates all scanlines in the selected file to not have any
 6414:    invalid or underspecified CODEs and that none of the codes are
 6415:    duplicated if this was requested.
 6416: 
 6417: =cut
 6418: 
 6419: sub scantron_validate_CODE {
 6420:     my ($r,$currentphase) = @_;
 6421:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
 6422:     if ($scantron_config{'CODElocation'} &&
 6423: 	$scantron_config{'CODEstart'} &&
 6424: 	$scantron_config{'CODElength'}) {
 6425: 	if (!defined($env{'form.scantron_CODElist'})) {
 6426: 	    &FIXME_blow_up()
 6427: 	}
 6428:     } else {
 6429: 	return (0,$currentphase+1);
 6430:     }
 6431:     
 6432:     my %usedCODEs;
 6433: 
 6434:     my %allcodes=&get_codes();
 6435: 
 6436:     my ($scanlines,$scan_data)=&scantron_getfile();
 6437:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 6438: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 6439: 	if ($line=~/^[\s\cz]*$/) { next; }
 6440: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
 6441: 						 $scan_data);
 6442: 	my $CODE=$$scan_record{'scantron.CODE'};
 6443: 	my $error=0;
 6444: 	if (!&Apache::lonnet::validCODE($CODE)) {
 6445: 	    &scantron_get_correction($r,$i,$scan_record,
 6446: 				     \%scantron_config,
 6447: 				     $line,'incorrectCODE',\%allcodes);
 6448: 	    return(1,$currentphase);
 6449: 	}
 6450: 	if (%allcodes && !exists($allcodes{$CODE}) 
 6451: 	    && !$$scan_record{'scantron.useCODE'}) {
 6452: 	    &scantron_get_correction($r,$i,$scan_record,
 6453: 				     \%scantron_config,
 6454: 				     $line,'incorrectCODE',\%allcodes);
 6455: 	    return(1,$currentphase);
 6456: 	}
 6457: 	if (exists($usedCODEs{$CODE}) 
 6458: 	    && $env{'form.scantron_CODEunique'} eq 'yes'
 6459: 	    && !$$scan_record{'scantron.CODE_ignore_dup'}) {
 6460: 	    &scantron_get_correction($r,$i,$scan_record,
 6461: 				     \%scantron_config,
 6462: 				     $line,'duplicateCODE',$usedCODEs{$CODE});
 6463: 	    return(1,$currentphase);
 6464: 	}
 6465: 	push (@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});
 6466:     }
 6467:     return (0,$currentphase+1);
 6468: }
 6469: 
 6470: =pod
 6471: 
 6472: =item scantron_validate_doublebubble
 6473: 
 6474:    Validates all scanlines in the selected file to not have any
 6475:    bubble lines with multiple bubbles marked.
 6476: 
 6477: =cut
 6478: 
 6479: sub scantron_validate_doublebubble {
 6480:     my ($r,$currentphase) = @_;
 6481:     #get student info
 6482:     my $classlist=&Apache::loncoursedata::get_classlist();
 6483:     my %idmap=&username_to_idmap($classlist);
 6484: 
 6485:     #get scantron line setup
 6486:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
 6487:     my ($scanlines,$scan_data)=&scantron_getfile();
 6488:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 6489: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 6490: 	if ($line=~/^[\s\cz]*$/) { next; }
 6491: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
 6492: 						 $scan_data);
 6493: 	if (!defined($$scan_record{'scantron.doubleerror'})) { next; }
 6494: 	&scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line,
 6495: 				 'doublebubble',
 6496: 				 $$scan_record{'scantron.doubleerror'});
 6497:     	return (1,$currentphase);
 6498:     }
 6499:     return (0,$currentphase+1);
 6500: }
 6501: 
 6502: =pod
 6503: 
 6504: =item scantron_get_maxbubble
 6505: 
 6506:    Returns the maximum number of bubble lines that are expected to
 6507:    occur. Does this by walking the selected sequence rendering the
 6508:    resource and then checking &Apache::lonxml::get_problem_counter()
 6509:    for what the current value of the problem counter is.
 6510: 
 6511:    Caches the result to $env{'form.scantron_maxbubble'}
 6512: 
 6513: =cut
 6514: 
 6515: sub scantron_get_maxbubble {    
 6516: 
 6517:     if (defined($env{'form.scantron_maxbubble'}) &&
 6518: 	$env{'form.scantron_maxbubble'}) {
 6519: 	return $env{'form.scantron_maxbubble'};
 6520:     }
 6521: 
 6522:     my $navmap=Apache::lonnavmaps::navmap->new();
 6523:     my (undef,undef,$sequence)=
 6524: 	&Apache::lonnet::decode_symb($env{'form.selectpage'});
 6525: 
 6526:     my $map=$navmap->getResourceByUrl($sequence);
 6527:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
 6528: 
 6529:     &Apache::lonxml::clear_problem_counter();
 6530: 
 6531:     my $uname       = $env{'form.student'};
 6532:     my $udom        = $env{'form.userdom'};
 6533:     my $cid         = $env{'request.course.id'};
 6534:     my $total_lines = 0;
 6535:     %bubble_lines_per_response = ();
 6536: 
 6537:     foreach my $resource (@resources) {
 6538: 	my $symb = $resource->symb();
 6539: 	my $result=&Apache::lonnet::ssi($resource->src(),
 6540: 					('symb' => $resource->symb()),
 6541: 					('grade_target' => 'analyze'),
 6542: 					('grade_courseid' => $cid),
 6543: 					('grade_domain' => $udom),
 6544: 					('grade_username' => $uname));
 6545: 	my (undef, $an) =
 6546: 	    split(/_HASH_REF__/,$result, 2);
 6547: 
 6548: 	my %analysis = &Apache::lonnet::str2hash($an);
 6549: 
 6550: 
 6551: 
 6552: 	foreach my $part_id (@{$analysis{'parts'}}) {
 6553: 	    my $bubble_lines = $analysis{"$part_id.bubble_lines"}[0];
 6554: 	    if (!$bubble_lines) {
 6555: 		$bubble_lines = 1;
 6556: 	    }
 6557: 	    $bubble_lines_per_response{"$symb.$part_id"} = $bubble_lines;
 6558: 	    $total_lines = $total_lines + $bubble_lines;
 6559: 	}
 6560: 
 6561:     }
 6562:     &Apache::lonnet::delenv('scantron\.');
 6563:     $env{'form.scantron_maxbubble'} =
 6564: 	$total_lines;
 6565:     return $env{'form.scantron_maxbubble'};
 6566: }
 6567: 
 6568: =pod
 6569: 
 6570: =item scantron_validate_missingbubbles
 6571: 
 6572:    Validates all scanlines in the selected file to not have any
 6573:    bubble lines with missing bubbles that haven't been verified as missing.
 6574: 
 6575: =cut
 6576: 
 6577: sub scantron_validate_missingbubbles {
 6578:     my ($r,$currentphase) = @_;
 6579:     #get student info
 6580:     my $classlist=&Apache::loncoursedata::get_classlist();
 6581:     my %idmap=&username_to_idmap($classlist);
 6582: 
 6583:     #get scantron line setup
 6584:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
 6585:     my ($scanlines,$scan_data)=&scantron_getfile();
 6586:     my $max_bubble=&scantron_get_maxbubble();
 6587:     if (!$max_bubble) { $max_bubble=2**31; }
 6588:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 6589: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 6590: 	if ($line=~/^[\s\cz]*$/) { next; }
 6591: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
 6592: 						 $scan_data);
 6593: 	if (!defined($$scan_record{'scantron.missingerror'})) { next; }
 6594: 	my @to_correct;
 6595: 	foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {
 6596: 	    if ($missing > $max_bubble) { next; }
 6597: 	    push(@to_correct,$missing);
 6598: 	}
 6599: 	if (@to_correct) {
 6600: 	    &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
 6601: 				     $line,'missingbubble',\@to_correct);
 6602: 	    return (1,$currentphase);
 6603: 	}
 6604: 
 6605:     }
 6606:     return (0,$currentphase+1);
 6607: }
 6608: 
 6609: =pod
 6610: 
 6611: =item scantron_process_students
 6612: 
 6613:    Routine that does the actual grading of the bubble sheet information.
 6614: 
 6615:    The parsed scanline hash is added to %env 
 6616: 
 6617:    Then foreach unskipped scanline it does an &Apache::lonnet::ssi()
 6618:    foreach resource , with the form data of
 6619: 
 6620: 	'submitted'     =>'scantron' 
 6621: 	'grade_target'  =>'grade',
 6622: 	'grade_username'=> username of student
 6623: 	'grade_domain'  => domain of student
 6624: 	'grade_courseid'=> of course
 6625: 	'grade_symb'    => symb of resource to grade
 6626: 
 6627:     This triggers a grading pass. The problem grading code takes care
 6628:     of converting the bubbled letter information (now in %env) into a
 6629:     valid submission.
 6630: 
 6631: =cut
 6632: 
 6633: sub scantron_process_students {
 6634:     my ($r) = @_;
 6635:     my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
 6636:     my ($symb)=&get_symb($r);
 6637:     if (!$symb) {return '';}
 6638:     my $default_form_data=&defaultFormData($symb);
 6639: 
 6640:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
 6641:     my ($scanlines,$scan_data)=&scantron_getfile();
 6642:     my $classlist=&Apache::loncoursedata::get_classlist();
 6643:     my %idmap=&username_to_idmap($classlist);
 6644:     my $navmap=Apache::lonnavmaps::navmap->new();
 6645:     my $map=$navmap->getResourceByUrl($sequence);
 6646:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
 6647: #    $r->print("geto ".scalar(@resources)."<br />");
 6648:     my $result= <<SCANTRONFORM;
 6649: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
 6650:   <input type="hidden" name="command" value="scantron_configphase" />
 6651:   $default_form_data
 6652: SCANTRONFORM
 6653:     $r->print($result);
 6654: 
 6655:     my @delayqueue;
 6656:     my %completedstudents;
 6657:     
 6658:     my $count=&get_todo_count($scanlines,$scan_data);
 6659:     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron Status',
 6660:  				    'Scantron Progress',$count,
 6661: 				    'inline',undef,'scantronupload');
 6662:     &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
 6663: 					  'Processing first student');
 6664:     my $start=&Time::HiRes::time();
 6665:     my $i=-1;
 6666:     my ($uname,$udom,$started);
 6667:     while ($i<$scanlines->{'count'}) {
 6668:  	($uname,$udom)=('','');
 6669:  	$i++;
 6670:  	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 6671:  	if ($line=~/^[\s\cz]*$/) { next; }
 6672: 	if ($started) {
 6673: 	    &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
 6674: 						     'last student');
 6675: 	}
 6676: 	$started=1;
 6677:  	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
 6678:  						 $scan_data);
 6679:  	unless ($uname=&scantron_find_student($scan_record,$scan_data,
 6680:  					      \%idmap,$i)) {
 6681:   	    &scantron_add_delay(\@delayqueue,$line,
 6682:  				'Unable to find a student that matches',1);
 6683:  	    next;
 6684:   	}
 6685:  	if (exists $completedstudents{$uname}) {
 6686:  	    &scantron_add_delay(\@delayqueue,$line,
 6687:  				'Student '.$uname.' has multiple sheets',2);
 6688:  	    next;
 6689:  	}
 6690:   	($uname,$udom)=split(/:/,$uname);
 6691: 
 6692: 	&Apache::lonxml::clear_problem_counter();
 6693:   	&Apache::lonnet::appenv(%$scan_record);
 6694: 
 6695: 	if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
 6696: 	    &scantron_putfile($scanlines,$scan_data);
 6697: 	}
 6698: 	
 6699: 	my $i=0;
 6700: 	foreach my $resource (@resources) {
 6701: 	    $i++;
 6702: 	    my %form=('submitted'     =>'scantron',
 6703: 		      'grade_target'  =>'grade',
 6704: 		      'grade_username'=>$uname,
 6705: 		      'grade_domain'  =>$udom,
 6706: 		      'grade_courseid'=>$env{'request.course.id'},
 6707: 		      'grade_symb'    =>$resource->symb());
 6708: 	    if (exists($scan_record->{'scantron.CODE'})
 6709: 		&& 
 6710: 		&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'})) {
 6711: 		$form{'CODE'}=$scan_record->{'scantron.CODE'};
 6712: 	    } else {
 6713: 		$form{'CODE'}='';
 6714: 	    }
 6715: 	    my $result=&Apache::lonnet::ssi($resource->src(),%form);
 6716: 	    if ($result ne '') {
 6717: 		&Apache::lonnet::logthis("scantron grading error -> $result");
 6718: 		&Apache::lonnet::logthis("scantron grading error info name $uname domain $udom course $env{'request.course.id'} url ".$resource->src());
 6719: 	    }
 6720: 	    if (&Apache::loncommon::connection_aborted($r)) { last; }
 6721: 	}
 6722: 	$completedstudents{$uname}={'line'=>$line};
 6723: 	if (&Apache::loncommon::connection_aborted($r)) { last; }
 6724:     } continue {
 6725: 	&Apache::lonxml::clear_problem_counter();
 6726: 	&Apache::lonnet::delenv('scantron\.');
 6727:     }
 6728:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
 6729: #    my $lasttime = &Time::HiRes::time()-$start;
 6730: #    $r->print("<p>took $lasttime</p>");
 6731: 
 6732:     $r->print("</form>");
 6733:     $r->print(&show_grading_menu_form($symb));
 6734:     return '';
 6735: }
 6736: 
 6737: =pod
 6738: 
 6739: =item scantron_upload_scantron_data
 6740: 
 6741:     Creates the screen for adding a new bubble sheet data file to a course.
 6742: 
 6743: =cut
 6744: 
 6745: sub scantron_upload_scantron_data {
 6746:     my ($r)=@_;
 6747:     $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}));
 6748:     my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
 6749: 							  'domainid',
 6750: 							  'coursename');
 6751:     my $domsel=&Apache::loncommon::select_dom_form($env{'request.role.domain'},
 6752: 						   'domainid');
 6753:     my $default_form_data=&defaultFormData(&get_symb($r,1));
 6754:     $r->print(<<UPLOAD);
 6755: <script type="text/javascript" language="javascript">
 6756:     function checkUpload(formname) {
 6757: 	if (formname.upfile.value == "") {
 6758: 	    alert("Please use the browse button to select a file from your local directory.");
 6759: 	    return false;
 6760: 	}
 6761: 	formname.submit();
 6762:     }
 6763: </script>
 6764: 
 6765: <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'>
 6766: $default_form_data
 6767: <table>
 6768: <tr><td>$select_link </td></tr>
 6769: <tr><td>Course ID:   </td><td><input name='courseid' type='text' />  </td></tr>
 6770: <tr><td>Course Name: </td><td><input name='coursename' type='text' /></td></tr>
 6771: <tr><td>Domain:      </td><td>$domsel                                </td></tr>
 6772: <tr><td>File to upload:</td><td><input type="file" name="upfile" size="50" /></td></tr>
 6773: </table>
 6774: <input name='command' value='scantronupload_save' type='hidden' />
 6775: <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" />
 6776: </form>
 6777: UPLOAD
 6778:     return '';
 6779: }
 6780: 
 6781: =pod
 6782: 
 6783: =item scantron_upload_scantron_data_save
 6784: 
 6785:    Adds a provided bubble information data file to the course if user
 6786:    has the correct privileges to do so.  
 6787: 
 6788: =cut
 6789: 
 6790: sub scantron_upload_scantron_data_save {
 6791:     my($r)=@_;
 6792:     my ($symb)=&get_symb($r,1);
 6793:     my $doanotherupload=
 6794: 	'<br /><form action="/adm/grades" method="post">'."\n".
 6795: 	'<input type="hidden" name="command" value="scantronupload" />'."\n".
 6796: 	'<input type="submit" name="submit" value="Do Another Upload" />'."\n".
 6797: 	'</form>'."\n";
 6798:     if (!&Apache::lonnet::allowed('usc',$env{'form.domainid'}) &&
 6799: 	!&Apache::lonnet::allowed('usc',
 6800: 			    $env{'form.domainid'}.'_'.$env{'form.courseid'})) {
 6801: 	$r->print("You are not allowed to upload Scantron data to the requested course.<br />");
 6802: 	if ($symb) {
 6803: 	    $r->print(&show_grading_menu_form($symb));
 6804: 	} else {
 6805: 	    $r->print($doanotherupload);
 6806: 	}
 6807: 	return '';
 6808:     }
 6809:     my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
 6810:     $r->print("Doing upload to ".$coursedata{'description'}." <br />");
 6811:     my $fname=$env{'form.upfile.filename'};
 6812:     #FIXME
 6813:     #copied from lonnet::userfileupload()
 6814:     #make that function able to target a specified course
 6815:     # Replace Windows backslashes by forward slashes
 6816:     $fname=~s/\\/\//g;
 6817:     # Get rid of everything but the actual filename
 6818:     $fname=~s/^.*\/([^\/]+)$/$1/;
 6819:     # Replace spaces by underscores
 6820:     $fname=~s/\s+/\_/g;
 6821:     # Replace all other weird characters by nothing
 6822:     $fname=~s/[^\w\.\-]//g;
 6823:     # See if there is anything left
 6824:     unless ($fname) { return 'error: no uploaded file'; }
 6825:     my $uploadedfile=$fname;
 6826:     $fname='scantron_orig_'.$fname;
 6827:     if (length($env{'form.upfile'}) < 2) {
 6828: 	$r->print("<span class=\"LC_error\">Error:</span> The file you attempted to upload, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>, contained no information. Please check that you entered the correct filename.");
 6829:     } else {
 6830: 	my $result=&Apache::lonnet::finishuserfileupload($env{'form.courseid'},$env{'form.domainid'},'upfile',$fname);
 6831: 	if ($result =~ m|^/uploaded/|) {
 6832: 	    $r->print("<span class=\"LC_success\">Success:</span> Successfully uploaded ".(length($env{'form.upfile'})-1)." bytes of data into location <tt>".$result."</tt>");
 6833: 	} else {
 6834: 	    $r->print("<span class=\"LC_error\">Error:</span> An error (".$result.") occurred when attempting to upload the file, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>");
 6835: 	}
 6836:     }
 6837:     if ($symb) {
 6838: 	$r->print(&scantron_selectphase($r,$uploadedfile));
 6839:     } else {
 6840: 	$r->print($doanotherupload);
 6841:     }
 6842:     return '';
 6843: }
 6844: 
 6845: =pod
 6846: 
 6847: =item valid_file
 6848: 
 6849:    Validates that the requested bubble data file exists in the course.
 6850: 
 6851: =cut
 6852: 
 6853: sub valid_file {
 6854:     my ($requested_file)=@_;
 6855:     foreach my $filename (sort(&scantron_filenames())) {
 6856: 	if ($requested_file eq $filename) { return 1; }
 6857:     }
 6858:     return 0;
 6859: }
 6860: 
 6861: =pod
 6862: 
 6863: =item scantron_download_scantron_data
 6864: 
 6865:    Shows a list of the three internal files (original, corrected,
 6866:    skipped) for a specific bubble sheet data file that exists in the
 6867:    course.
 6868: 
 6869: =cut
 6870: 
 6871: sub scantron_download_scantron_data {
 6872:     my ($r)=@_;
 6873:     my $default_form_data=&defaultFormData(&get_symb($r,1));
 6874:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 6875:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 6876:     my $file=$env{'form.scantron_selectfile'};
 6877:     if (! &valid_file($file)) {
 6878: 	$r->print(<<ERROR);
 6879: 	<p>
 6880: 	    The requested file name was invalid.
 6881:         </p>
 6882: ERROR
 6883: 	$r->print(&show_grading_menu_form(&get_symb($r,1)));
 6884: 	return;
 6885:     }
 6886:     my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file;
 6887:     my $corrected='/uploaded/'.$cdom.'/'.$cname.'/scantron_corrected_'.$file;
 6888:     my $skipped='/uploaded/'.$cdom.'/'.$cname.'/scantron_skipped_'.$file;
 6889:     &Apache::lonnet::allowuploaded('/adm/grades',$orig);
 6890:     &Apache::lonnet::allowuploaded('/adm/grades',$corrected);
 6891:     &Apache::lonnet::allowuploaded('/adm/grades',$skipped);
 6892:     $r->print(<<DOWNLOAD);
 6893:     <p>
 6894: 	<a href="$orig">Original</a> file as uploaded by the scantron office.
 6895:     </p>
 6896:     <p>
 6897: 	<a href="$corrected">Corrections</a>, a file of corrected records that were used in grading.
 6898:     </p>
 6899:     <p>
 6900: 	<a href="$skipped">Skipped</a>, a file of records that were skipped.
 6901:     </p>
 6902: DOWNLOAD
 6903:     $r->print(&show_grading_menu_form(&get_symb($r,1)));
 6904:     return '';
 6905: }
 6906: 
 6907: =pod
 6908: 
 6909: =back
 6910: 
 6911: =cut
 6912: 
 6913: #-------- end of section for handling grading scantron forms -------
 6914: #
 6915: #-------------------------------------------------------------------
 6916: 
 6917: #-------------------------- Menu interface -------------------------
 6918: #
 6919: #--- Show a Grading Menu button - Calls the next routine ---
 6920: sub show_grading_menu_form {
 6921:     my ($symb)=@_;
 6922:     my $result.='<br /><form action="/adm/grades" method="post">'."\n".
 6923: 	'<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 6924: 	'<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
 6925: 	'<input type="hidden" name="command" value="gradingmenu" />'."\n".
 6926: 	'<input type="submit" name="submit" value="Grading Menu" />'."\n".
 6927: 	'</form>'."\n";
 6928:     return $result;
 6929: }
 6930: 
 6931: # -- Retrieve choices for grading form
 6932: sub savedState {
 6933:     my %savedState = ();
 6934:     if ($env{'form.saveState'}) {
 6935: 	foreach (split(/:/,$env{'form.saveState'})) {
 6936: 	    my ($key,$value) = split(/=/,$_,2);
 6937: 	    $savedState{$key} = $value;
 6938: 	}
 6939:     }
 6940:     return \%savedState;
 6941: }
 6942: 
 6943: #--- Displays the main menu page -------
 6944: sub gradingmenu {
 6945:     my ($request) = @_;
 6946:     my ($symb)=&get_symb($request);
 6947:     if (!$symb) {return '';}
 6948:     my $probTitle = &Apache::lonnet::gettitle($symb);
 6949: 
 6950:     $request->print(<<GRADINGMENUJS);
 6951: <script type="text/javascript" language="javascript">
 6952:     function checkChoice(formname,val,cmdx) {
 6953: 	if (val <= 2) {
 6954: 	    var cmd = radioSelection(formname.radioChoice);
 6955: 	    var cmdsave = cmd;
 6956: 	} else {
 6957: 	    cmd = cmdx;
 6958: 	    cmdsave = 'submission';
 6959: 	}
 6960: 	formname.command.value = cmd;
 6961: 	formname.saveState.value = "saveCmd="+cmdsave+":saveSec="+pullDownSelection(formname.section)+
 6962: 	    ":saveSub="+pullDownSelection(formname.submitonly)+":saveStatus="+pullDownSelection(formname.Status);
 6963: 	if (val < 5) formname.submit();
 6964: 	if (val == 5) {
 6965: 	    if (!checkReceiptNo(formname,'notOK')) { return false;}
 6966: 	    formname.submit();
 6967: 	}
 6968: 	if (val < 7) formname.submit();
 6969:     }
 6970: 
 6971:     function checkReceiptNo(formname,nospace) {
 6972: 	var receiptNo = formname.receipt.value;
 6973: 	var checkOpt = false;
 6974: 	if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
 6975: 	if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
 6976: 	if (checkOpt) {
 6977: 	    alert("Please enter a receipt number given by a student in the receipt box.");
 6978: 	    formname.receipt.value = "";
 6979: 	    formname.receipt.focus();
 6980: 	    return false;
 6981: 	}
 6982: 	return true;
 6983:     }
 6984: </script>
 6985: GRADINGMENUJS
 6986:     &commonJSfunctions($request);
 6987:     my $result='<h3>&nbsp;<span class="LC_info">Manual Grading/View Submission</span></h3>';
 6988:     my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
 6989:     $result.=$table;
 6990:     my (undef,$sections) = &getclasslist('all','0');
 6991:     my $savedState = &savedState();
 6992:     my $saveCmd = ($$savedState{'saveCmd'} eq '' ? 'submission' : $$savedState{'saveCmd'});
 6993:     my $saveSec = ($$savedState{'saveSec'} eq '' ? 'all' : $$savedState{'saveSec'});
 6994:     my $saveSub = ($$savedState{'saveSub'} eq '' ? 'all' : $$savedState{'saveSub'});
 6995:     my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'});
 6996: 
 6997:     $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
 6998: 	'<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 6999: 	'<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
 7000: 	'<input type="hidden" name="probTitle"   value="'.$probTitle.'" />'."\n".
 7001: 	'<input type="hidden" name="command"     value="" />'."\n".
 7002: 	'<input type="hidden" name="saveState"   value="" />'."\n".
 7003: 	'<input type="hidden" name="gradingMenu" value="1" />'."\n".
 7004: 	'<input type="hidden" name="showgrading" value="yes" />'."\n";
 7005: 
 7006:     $result.='<table width="100%" border="0"><tr><td bgcolor=#777777>'."\n".
 7007: 	'<table width="100%" border="0"><tr bgcolor="#e6ffff"><td colspan="2">'."\n".
 7008: 	'&nbsp;<b>Select a Grading/Viewing Option</b></td></tr>'."\n".
 7009: 	'<tr bgcolor="#ffffe6" valign="top"><td>'."\n";
 7010: 
 7011:     $result.='<table width="100%" border="0">';
 7012:     $result.='<tr bgcolor="#ffffe6" valign="top"><td>'."\n".
 7013: 	'&nbsp;'.&mt('Select Section').': <select name="section" multiple="multiple" size="3">'."\n";
 7014:     if (ref($sections)) {
 7015: 	foreach (sort (@$sections)) {
 7016: 	    $result.='<option value="'.$_.'" '.
 7017: 		($saveSec eq $_ ? 'selected="selected"':'').'>'.$_.'</option>'."\n";
 7018: 	}
 7019:     }
 7020:     $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> &nbsp; ';
 7021: 
 7022:     $result.=&mt('Student Status').':'.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,undef);
 7023: 
 7024:     $result.='</td></tr>';
 7025: 
 7026:     $result.='<tr bgcolor="#ffffe6"valign="top"><td><label>'.
 7027: 	'<input type="radio" name="radioChoice" value="submission" '.
 7028: 	($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.'<b>'.&mt('Current Resource').':</b> '.&mt('For one or more students').
 7029: 	'</label> <select name="submitonly">'.
 7030: 	'<option value="yes" '.
 7031: 	($saveSub eq 'yes' ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>'.
 7032: 	'<option value="queued" '.
 7033: 	($saveSub eq 'queued' ? 'selected="selected"' : '').'>'.&mt('in grading queue').'</option>'.
 7034: 	'<option value="graded" '.
 7035: 	($saveSub eq 'graded' ? 'selected="selected"' : '').'>'.&mt('with ungraded submissions').'</option>'.
 7036: 	'<option value="incorrect" '.
 7037: 	($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>'.
 7038: 	'<option value="all" '.
 7039: 	($saveSub eq 'all' ? 'selected="selected"' : '').'>'.&mt('with any status').'</option></select></td></tr>'."\n";
 7040: 
 7041:     $result.='<tr bgcolor="#ffffe6"valign="top"><td>'.
 7042: 	'<label><input type="radio" name="radioChoice" value="viewgrades" '.
 7043: 	($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '.
 7044: 	'<b>Current Resource:</b> For all students in selected section or course</label></td></tr>'."\n";
 7045: 
 7046:     $result.='<tr bgcolor="#ffffe6" valign="top"><td>'.
 7047: 	'<label><input type="radio" name="radioChoice" value="pickStudentPage" '.
 7048: 	($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '.
 7049: 	'The <b>complete</b> set/page/sequence: For one student</label></td></tr>'."\n";
 7050: 
 7051:     $result.='<tr bgcolor="#ffffe6"><td><br />'.
 7052: 	'<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'.
 7053: 	'</td></tr></table>'."\n";
 7054: 
 7055:     $result.='</td><td valign="top">';
 7056: 
 7057:     $result.='<table width="100%" border="0">';
 7058:     $result.='<tr bgcolor="#ffffe6"><td>'.
 7059: 	'<input type="button" onClick="javascript:checkChoice(this.form,\'3\',\'csvform\');" value="'.&mt('Upload').'" />'.
 7060: 	' '.&mt('scores from file').' </td></tr>'."\n";
 7061: 
 7062:     $result.='<tr bgcolor="#ffffe6"><td>'.
 7063:         '<input type="button" onClick="javascript:checkChoice(this.form,\'6\',\'processclicker\');" value="'.&mt('Process').'" />'.
 7064:         ' '.&mt('clicker file').' </td></tr>'."\n";
 7065: 
 7066:     $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
 7067: 	'<input type="button" onClick="javascript:checkChoice(this.form,\'4\',\'scantron_selectphase\');'.
 7068: 	'" value="'.&mt('Grade').'" /> scantron forms</td></tr>'."\n";
 7069: 
 7070:     if ((&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) && ($symb)) {
 7071: 	$result.='<tr bgcolor="#ffffe6"valign="top"><td>'.
 7072: 	    '<input type="button" onClick="javascript:checkChoice(this.form,\'5\',\'verify\');" value="'.&mt('Verify').'" />'.
 7073: 	    ' '.&mt('receipt').': '.
 7074: 	    &Apache::lonnet::recprefix($env{'request.course.id'}).
 7075: 	    '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />'.
 7076: 	    '</td></tr>'."\n";
 7077:     } 
 7078:     $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
 7079: 	'<input type="button" onClick="javascript:this.form.action=\'/adm/helper/resettimes.helper\';this.form.submit();'.
 7080: 	'" value="'.&mt('Manage').'" /> access times.</td></tr>'."\n";
 7081:     $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.
 7082: 	'<input type="button" onClick="javascript:this.form.command.value=\'codelist\';this.form.action=\'/adm/pickcode\';this.form.submit();'.
 7083: 	'" value="'.&mt('View').'" /> saved CODEs.</td></tr>'."\n";
 7084: 
 7085:     $result.='</table>'."\n".
 7086: 	'</td></tr></table>'."\n".
 7087: 	'</td></tr></table></form>'."\n";
 7088:     return $result;
 7089: }
 7090: 
 7091: sub reset_perm {
 7092:     undef(%perm);
 7093: }
 7094: 
 7095: sub init_perm {
 7096:     &reset_perm();
 7097:     foreach my $test_perm ('vgr','mgr','opa') {
 7098: 
 7099: 	my $scope = $env{'request.course.id'};
 7100: 	if (!($perm{$test_perm}=&Apache::lonnet::allowed($test_perm,$scope))) {
 7101: 
 7102: 	    $scope .= '/'.$env{'request.course.sec'};
 7103: 	    if ( $perm{$test_perm}=
 7104: 		 &Apache::lonnet::allowed($test_perm,$scope)) {
 7105: 		$perm{$test_perm.'_section'}=$env{'request.course.sec'};
 7106: 	    } else {
 7107: 		delete($perm{$test_perm});
 7108: 	    }
 7109: 	}
 7110:     }
 7111: }
 7112: 
 7113: sub gather_clicker_ids {
 7114:     my %clicker_ids;
 7115: 
 7116:     my $classlist = &Apache::loncoursedata::get_classlist();
 7117: 
 7118:     # Set up a couple variables.
 7119:     my $username_idx = &Apache::loncoursedata::CL_SNAME();
 7120:     my $domain_idx   = &Apache::loncoursedata::CL_SDOM();
 7121:     my $status_idx   = &Apache::loncoursedata::CL_STATUS();
 7122: 
 7123:     foreach my $student (keys(%$classlist)) {
 7124:         if ($classlist->{$student}->[$status_idx] ne 'Active') { next; }
 7125:         my $username = $classlist->{$student}->[$username_idx];
 7126:         my $domain   = $classlist->{$student}->[$domain_idx];
 7127:         my $clickers =
 7128: 	    (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1];
 7129:         foreach my $id (split(/\,/,$clickers)) {
 7130:             $id=~s/^[\#0]+//;
 7131:             $id=~s/[\-\:]//g;
 7132:             if (exists($clicker_ids{$id})) {
 7133: 		$clicker_ids{$id}.=','.$username.':'.$domain;
 7134:             } else {
 7135: 		$clicker_ids{$id}=$username.':'.$domain;
 7136:             }
 7137:         }
 7138:     }
 7139:     return %clicker_ids;
 7140: }
 7141: 
 7142: sub gather_adv_clicker_ids {
 7143:     my %clicker_ids;
 7144:     my $cnum=$env{'course.'.$env{'request.course.id'}.'.num'};
 7145:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 7146:     my %coursepersonnel=&Apache::lonnet::get_course_adv_roles($cdom.'/'.$cnum);
 7147:     foreach my $element (sort(keys(%coursepersonnel))) {
 7148:         foreach my $person (split(/\,/,$coursepersonnel{$element})) {
 7149:             my ($puname,$pudom)=split(/\:/,$person);
 7150:             my $clickers =
 7151: 		(&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1];
 7152:             foreach my $id (split(/\,/,$clickers)) {
 7153: 		$id=~s/^[\#0]+//;
 7154:                 $id=~s/[\-\:]//g;
 7155: 		if (exists($clicker_ids{$id})) {
 7156: 		    $clicker_ids{$id}.=','.$puname.':'.$pudom;
 7157: 		} else {
 7158: 		    $clicker_ids{$id}=$puname.':'.$pudom;
 7159: 		}
 7160:             }
 7161:         }
 7162:     }
 7163:     return %clicker_ids;
 7164: }
 7165: 
 7166: sub clicker_grading_parameters {
 7167:     return ('gradingmechanism' => 'scalar',
 7168:             'upfiletype' => 'scalar',
 7169:             'specificid' => 'scalar',
 7170:             'pcorrect' => 'scalar',
 7171:             'pincorrect' => 'scalar');
 7172: }
 7173: 
 7174: sub process_clicker {
 7175:     my ($r)=@_;
 7176:     my ($symb)=&get_symb($r);
 7177:     if (!$symb) {return '';}
 7178:     my $result=&checkforfile_js();
 7179:     $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
 7180:     my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});
 7181:     $result.=$table;
 7182:     $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";
 7183:     $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";
 7184:     $result.='&nbsp;<b>'.&mt('Specify a file containing the clicker information for this resource').
 7185:         '.</b></td></tr>'."\n";
 7186:     $result.='<tr bgcolor=#ffffe6><td>'."\n";
 7187: # Attempt to restore parameters from last session, set defaults if not present
 7188:     my %Saveable_Parameters=&clicker_grading_parameters();
 7189:     &Apache::loncommon::restore_course_settings('grades_clicker',
 7190:                                                  \%Saveable_Parameters);
 7191:     if (!$env{'form.pcorrect'}) { $env{'form.pcorrect'}=100; }
 7192:     if (!$env{'form.pincorrect'}) { $env{'form.pincorrect'}=100; }
 7193:     if (!$env{'form.gradingmechanism'}) { $env{'form.gradingmechanism'}='attendance'; }
 7194:     if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; }
 7195: 
 7196:     my %checked;
 7197:     foreach my $gradingmechanism ('attendance','personnel','specific') {
 7198:        if ($env{'form.gradingmechanism'} eq $gradingmechanism) {
 7199:           $checked{$gradingmechanism}="checked='checked'";
 7200:        }
 7201:     }
 7202: 
 7203:     my $upload=&mt("Upload File");
 7204:     my $type=&mt("Type");
 7205:     my $attendance=&mt("Award points just for participation");
 7206:     my $personnel=&mt("Correctness determined from response by course personnel");
 7207:     my $specific=&mt("Correctness determined from response with clicker ID(s)"); 
 7208:     my $pcorrect=&mt("Percentage points for correct solution");
 7209:     my $pincorrect=&mt("Percentage points for incorrect solution");
 7210:     my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',
 7211: 						   ('iclicker' => 'i>clicker',
 7212:                                                     'interwrite' => 'interwrite PRS'));
 7213:     $symb = &Apache::lonenc::check_encrypt($symb);
 7214:     $result.=<<ENDUPFORM;
 7215: <script type="text/javascript">
 7216: function sanitycheck() {
 7217: // Accept only integer percentages
 7218:    document.forms.gradesupload.pcorrect.value=Math.round(document.forms.gradesupload.pcorrect.value);
 7219:    document.forms.gradesupload.pincorrect.value=Math.round(document.forms.gradesupload.pincorrect.value);
 7220: // Find out grading choice
 7221:    for (i=0; i<document.forms.gradesupload.gradingmechanism.length; i++) {
 7222:       if (document.forms.gradesupload.gradingmechanism[i].checked) {
 7223:          gradingchoice=document.forms.gradesupload.gradingmechanism[i].value;
 7224:       }
 7225:    }
 7226: // By default, new choice equals user selection
 7227:    newgradingchoice=gradingchoice;
 7228: // Not good to give more points for false answers than correct ones
 7229:    if (Math.round(document.forms.gradesupload.pcorrect.value)<Math.round(document.forms.gradesupload.pincorrect.value)) {
 7230:       document.forms.gradesupload.pcorrect.value=document.forms.gradesupload.pincorrect.value;
 7231:    }
 7232: // If new choice is attendance only, and old choice was correctness-based, restore defaults
 7233:    if ((gradingchoice=='attendance') && (document.forms.gradesupload.waschecked.value!='attendance')) {
 7234:       document.forms.gradesupload.pcorrect.value=100;
 7235:       document.forms.gradesupload.pincorrect.value=100;
 7236:    }
 7237: // If the values are different, cannot be attendance only
 7238:    if ((Math.round(document.forms.gradesupload.pcorrect.value)!=Math.round(document.forms.gradesupload.pincorrect.value)) &&
 7239:        (gradingchoice=='attendance')) {
 7240:        newgradingchoice='personnel';
 7241:    }
 7242: // Change grading choice to new one
 7243:    for (i=0; i<document.forms.gradesupload.gradingmechanism.length; i++) {
 7244:       if (document.forms.gradesupload.gradingmechanism[i].value==newgradingchoice) {
 7245:          document.forms.gradesupload.gradingmechanism[i].checked=true;
 7246:       } else {
 7247:          document.forms.gradesupload.gradingmechanism[i].checked=false;
 7248:       }
 7249:    }
 7250: // Remember the old state
 7251:    document.forms.gradesupload.waschecked.value=newgradingchoice;
 7252: }
 7253: </script>
 7254: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 7255: <input type="hidden" name="symb" value="$symb" />
 7256: <input type="hidden" name="command" value="processclickerfile" />
 7257: <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
 7258: <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
 7259: <input type="file" name="upfile" size="50" />
 7260: <br /><label>$type: $selectform</label>
 7261: <br /><label>$attendance: <input type="radio" name="gradingmechanism" value="attendance" $checked{'attendance'} onClick="sanitycheck()" /></label>
 7262: <br /><label>$personnel: <input type="radio" name="gradingmechanism" value="personnel" $checked{'personnel'} onClick="sanitycheck()" /></label>
 7263: <br /><label>$specific: <input type="radio" name="gradingmechanism" value="specific" $checked{'specific'} onClick="sanitycheck()" /></label>
 7264: <input type="text" name="specificid" value="$env{'form.specificid'}" size="20" />
 7265: <input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" />
 7266: <br /><label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onChange="sanitycheck()" /></label>
 7267: <br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onChange="sanitycheck()" /></label>
 7268: <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="$upload" />
 7269: </form>
 7270: ENDUPFORM
 7271:     $result.='</td></tr></table>'."\n".
 7272:              '</td></tr></table><br /><br />'."\n";
 7273:     $result.=&show_grading_menu_form($symb);
 7274:     return $result;
 7275: }
 7276: 
 7277: sub process_clicker_file {
 7278:     my ($r)=@_;
 7279:     my ($symb)=&get_symb($r);
 7280:     if (!$symb) {return '';}
 7281: 
 7282:     my %Saveable_Parameters=&clicker_grading_parameters();
 7283:     &Apache::loncommon::store_course_settings('grades_clicker',
 7284:                                               \%Saveable_Parameters);
 7285: 
 7286:     my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
 7287:     if (($env{'form.gradingmechanism'} eq 'specific') && ($env{'form.specificid'}!~/\w/)) {
 7288: 	$result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>';
 7289: 	return $result.&show_grading_menu_form($symb);
 7290:     }
 7291:     my %clicker_ids=&gather_clicker_ids();
 7292:     my %correct_ids;
 7293:     if ($env{'form.gradingmechanism'} eq 'personnel') {
 7294: 	%correct_ids=&gather_adv_clicker_ids();
 7295:     }
 7296:     if ($env{'form.gradingmechanism'} eq 'specific') {
 7297: 	foreach my $correct_id (split(/[\s\,]/,$env{'form.specificid'})) {;
 7298: 	   $correct_id=~tr/a-z/A-Z/;
 7299: 	   $correct_id=~s/\s//gs;
 7300: 	   $correct_id=~s/^[\#0]+//;
 7301:            $correct_id=~s/[\-\:]//g;
 7302:            if ($correct_id) {
 7303: 	      $correct_ids{$correct_id}='specified';
 7304:            }
 7305:         }
 7306:     }
 7307:     if ($env{'form.gradingmechanism'} eq 'attendance') {
 7308: 	$result.=&mt('Score based on attendance only');
 7309:     } else {
 7310: 	my $number=0;
 7311: 	$result.='<p><b>'.&mt('Correctness determined by the following IDs').'</b>';
 7312: 	foreach my $id (sort(keys(%correct_ids))) {
 7313: 	    $result.='<br /><tt>'.$id.'</tt> - ';
 7314: 	    if ($correct_ids{$id} eq 'specified') {
 7315: 		$result.=&mt('specified');
 7316: 	    } else {
 7317: 		my ($uname,$udom)=split(/\:/,$correct_ids{$id});
 7318: 		$result.=&Apache::loncommon::plainname($uname,$udom);
 7319: 	    }
 7320: 	    $number++;
 7321: 	}
 7322:         $result.="</p>\n";
 7323: 	if ($number==0) {
 7324: 	    $result.='<span class="LC_error">'.&mt('No IDs found to determine correct answer').'</span>';
 7325: 	    return $result.&show_grading_menu_form($symb);
 7326: 	}
 7327:     }
 7328:     if (length($env{'form.upfile'}) < 2) {
 7329:         $result.=&mt('[_1] Error: [_2] The file you attempted to upload, [_3] contained no information. Please check that you entered the correct filename.',
 7330: 		     '<span class="LC_error">',
 7331: 		     '</span>',
 7332: 		     '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>');
 7333:         return $result.&show_grading_menu_form($symb);
 7334:     }
 7335: 
 7336: # Were able to get all the info needed, now analyze the file
 7337: 
 7338:     $result.=&Apache::loncommon::studentbrowser_javascript();
 7339:     $symb = &Apache::lonenc::check_encrypt($symb);
 7340:     my $heading=&mt('Scanning clicker file');
 7341:     $result.=(<<ENDHEADER);
 7342: <br /><table width="100%" border="0"><tr><td bgcolor="#777777">
 7343: <table width="100%" border="0"><tr bgcolor="#e6ffff"><td>
 7344: <b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>
 7345: <form method="post" action="/adm/grades" name="clickeranalysis">
 7346: <input type="hidden" name="symb" value="$symb" />
 7347: <input type="hidden" name="command" value="assignclickergrades" />
 7348: <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
 7349: <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
 7350: <input type="hidden" name="gradingmechanism" value="$env{'form.gradingmechanism'}" />
 7351: <input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" />
 7352: <input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" />
 7353: ENDHEADER
 7354:     my %responses;
 7355:     my @questiontitles;
 7356:     my $errormsg='';
 7357:     my $number=0;
 7358:     if ($env{'form.upfiletype'} eq 'iclicker') {
 7359: 	($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses);
 7360:     }
 7361:     if ($env{'form.upfiletype'} eq 'interwrite') {
 7362:         ($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses);
 7363:     }
 7364:     $result.='<br />'.&mt('Found [_1] question(s)',$number).'<br />'.
 7365:              '<input type="hidden" name="number" value="'.$number.'" />'.
 7366:              &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',
 7367:                  $env{'form.pcorrect'},$env{'form.pincorrect'}).
 7368:              '<br />';
 7369: # Remember Question Titles
 7370: # FIXME: Possibly need delimiter other than ":"
 7371:     for (my $i=0;$i<$number;$i++) {
 7372:         $result.='<input type="hidden" name="question:'.$i.'" value="'.
 7373:                  &HTML::Entities::encode($questiontitles[$i],'"&<>').'" />';
 7374:     }
 7375:     my $correct_count=0;
 7376:     my $student_count=0;
 7377:     my $unknown_count=0;
 7378: # Match answers with usernames
 7379: # FIXME: Possibly need delimiter other than ":"
 7380:     foreach my $id (keys(%responses)) {
 7381:        if ($correct_ids{$id}) {
 7382:           $result.="\n".'<input type="hidden" name="correct:'.$correct_count.':'.$correct_ids{$id}.'" value="'.$responses{$id}.'" />';
 7383:           $correct_count++;
 7384:        } elsif ($clicker_ids{$id}) {
 7385:           if ($clicker_ids{$id}=~/\,/) {
 7386: # More than one user with the same clicker!
 7387:              $result.="\n<hr />".&mt('Clicker registered more than once').": <tt>".$id."</tt><br />";
 7388:              $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
 7389:                            "<select name='multi".$id."'>";
 7390:              foreach my $reguser (sort(split(/\,/,$clicker_ids{$id}))) {
 7391:                  $result.="<option value='".$reguser."'>".&Apache::loncommon::plainname(split(/\:/,$reguser)).' ('.$reguser.')</option>';
 7392:              }
 7393:              $result.='</select>';
 7394:              $unknown_count++;
 7395:           } else {
 7396: # Good: found one and only one user with the right clicker
 7397:              $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />';
 7398:              $student_count++;
 7399:           }
 7400:        } else {
 7401:           $result.="\n<hr />".&mt('Unregistered Clicker')." <tt>".$id."</tt><br />";
 7402:           $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
 7403:                    "\n".&mt("Username").": <input type='text' name='uname".$id."' />&nbsp;".
 7404:                    "\n".&mt("Domain").": ".
 7405:                    &Apache::loncommon::select_dom_form($env{'course.'.$env{'request.course.id'}.'.domain'},'udom'.$id).'&nbsp;'.
 7406:                    &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id);
 7407:           $unknown_count++;
 7408:        }
 7409:     }
 7410:     $result.='<hr />'.
 7411:              &mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);
 7412:     if ($env{'form.gradingmechanism'} ne 'attendance') {
 7413:        if ($correct_count==0) {
 7414:           $errormsg.="Found no correct answers answers for grading!";
 7415:        } elsif ($correct_count>1) {
 7416:           $result.='<br /><span class="LC_warning">'.&mt("Found [_1] entries for grading!",$correct_count).'</span>';
 7417:        }
 7418:     }
 7419:     if ($number<1) {
 7420:        $errormsg.="Found no questions.";
 7421:     }
 7422:     if ($errormsg) {
 7423:        $result.='<br /><span class="LC_error">'.&mt($errormsg).'</span>';
 7424:     } else {
 7425:        $result.='<br /><input type="submit" name="finalize" value="'.&mt('Finalize Grading').'" />';
 7426:     }
 7427:     $result.='</form></td></tr></table>'."\n".
 7428:              '</td></tr></table><br /><br />'."\n";
 7429:     return $result.&show_grading_menu_form($symb);
 7430: }
 7431: 
 7432: sub iclicker_eval {
 7433:     my ($questiontitles,$responses)=@_;
 7434:     my $number=0;
 7435:     my $errormsg='';
 7436:     foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
 7437:         my %components=&Apache::loncommon::record_sep($line);
 7438:         my @entries=map {$components{$_}} (sort(keys(%components)));
 7439: 	if ($entries[0] eq 'Question') {
 7440: 	    for (my $i=3;$i<$#entries;$i+=6) {
 7441: 		$$questiontitles[$number]=$entries[$i];
 7442: 		$number++;
 7443: 	    }
 7444: 	}
 7445: 	if ($entries[0]=~/^\#/) {
 7446: 	    my $id=$entries[0];
 7447: 	    my @idresponses;
 7448: 	    $id=~s/^[\#0]+//;
 7449: 	    for (my $i=0;$i<$number;$i++) {
 7450: 		my $idx=3+$i*6;
 7451: 		push(@idresponses,$entries[$idx]);
 7452: 	    }
 7453: 	    $$responses{$id}=join(',',@idresponses);
 7454: 	}
 7455:     }
 7456:     return ($errormsg,$number);
 7457: }
 7458: 
 7459: sub interwrite_eval {
 7460:     my ($questiontitles,$responses)=@_;
 7461:     my $number=0;
 7462:     my $errormsg='';
 7463:     my $skipline=1;
 7464:     my $questionnumber=0;
 7465:     my %idresponses=();
 7466:     foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
 7467:         my %components=&Apache::loncommon::record_sep($line);
 7468:         my @entries=map {$components{$_}} (sort(keys(%components)));
 7469:         if ($entries[1] eq 'Time') { $skipline=0; next; }
 7470:         if ($entries[1] eq 'Response') { $skipline=1; }
 7471:         next if $skipline;
 7472:         if ($entries[0]!=$questionnumber) {
 7473:            $questionnumber=$entries[0];
 7474:            $$questiontitles[$number]=&mt('Question [_1]',$questionnumber);
 7475:            $number++;
 7476:         }
 7477:         my $id=$entries[4];
 7478:         $id=~s/^[\#0]+//;
 7479:         $id=~s/^v\d*\://i;
 7480:         $id=~s/[\-\:]//g;
 7481:         $idresponses{$id}[$number]=$entries[6];
 7482:     }
 7483:     foreach my $id (keys %idresponses) {
 7484:        $$responses{$id}=join(',',@{$idresponses{$id}});
 7485:        $$responses{$id}=~s/^\s*\,//;
 7486:     }
 7487:     return ($errormsg,$number);
 7488: }
 7489: 
 7490: sub assign_clicker_grades {
 7491:     my ($r)=@_;
 7492:     my ($symb)=&get_symb($r);
 7493:     if (!$symb) {return '';}
 7494: # See which part we are saving to
 7495:     my ($partlist,$handgrade,$responseType) = &response_type($symb);
 7496: # FIXME: This should probably look for the first handgradeable part
 7497:     my $part=$$partlist[0];
 7498: # Start screen output
 7499:     my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
 7500: 
 7501:     my $heading=&mt('Assigning grades based on clicker file');
 7502:     $result.=(<<ENDHEADER);
 7503: <br /><table width="100%" border="0"><tr><td bgcolor="#777777">
 7504: <table width="100%" border="0"><tr bgcolor="#e6ffff"><td>
 7505: <b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>
 7506: ENDHEADER
 7507: # Get correct result
 7508: # FIXME: Possibly need delimiter other than ":"
 7509:     my @correct=();
 7510:     my $gradingmechanism=$env{'form.gradingmechanism'};
 7511:     my $number=$env{'form.number'};
 7512:     if ($gradingmechanism ne 'attendance') {
 7513:        foreach my $key (keys(%env)) {
 7514:           if ($key=~/^form\.correct\:/) {
 7515:              my @input=split(/\,/,$env{$key});
 7516:              for (my $i=0;$i<=$#input;$i++) {
 7517:                  if (($correct[$i]) && ($input[$i]) &&
 7518:                      ($correct[$i] ne $input[$i])) {
 7519:                     $result.='<br /><span class="LC_warning">'.
 7520:                              &mt('More than one correct result given for question "[_1]": [_2] versus [_3].',
 7521:                                  $env{'form.question:'.$i},$correct[$i],$input[$i]).'</span>';
 7522:                  } elsif ($input[$i]) {
 7523:                     $correct[$i]=$input[$i];
 7524:                  }
 7525:              }
 7526:           }
 7527:        }
 7528:        for (my $i=0;$i<$number;$i++) {
 7529:           if (!$correct[$i]) {
 7530:              $result.='<br /><span class="LC_error">'.
 7531:                       &mt('No correct result given for question "[_1]"!',
 7532:                           $env{'form.question:'.$i}).'</span>';
 7533:           }
 7534:        }
 7535:        $result.='<br />'.&mt("Correct answer: [_1]",join(', ',map { ($_?$_:'-') } @correct));
 7536:     }
 7537: # Start grading
 7538:     my $pcorrect=$env{'form.pcorrect'};
 7539:     my $pincorrect=$env{'form.pincorrect'};
 7540:     my $storecount=0;
 7541:     foreach my $key (keys(%env)) {
 7542:        my $user='';
 7543:        if ($key=~/^form\.student\:(.*)$/) {
 7544:           $user=$1;
 7545:        }
 7546:        if ($key=~/^form\.unknown\:(.*)$/) {
 7547:           my $id=$1;
 7548:           if (($env{'form.uname'.$id}) && ($env{'form.udom'.$id})) {
 7549:              $user=$env{'form.uname'.$id}.':'.$env{'form.udom'.$id};
 7550:           } elsif ($env{'form.multi'.$id}) {
 7551:              $user=$env{'form.multi'.$id};
 7552:           }
 7553:        }
 7554:        if ($user) { 
 7555:           my @answer=split(/\,/,$env{$key});
 7556:           my $sum=0;
 7557:           for (my $i=0;$i<$number;$i++) {
 7558:              if ($answer[$i]) {
 7559:                 if ($gradingmechanism eq 'attendance') {
 7560:                    $sum+=$pcorrect;
 7561:                 } else {
 7562:                    if ($answer[$i] eq $correct[$i]) {
 7563:                       $sum+=$pcorrect;
 7564:                    } else {
 7565:                       $sum+=$pincorrect;
 7566:                    }
 7567:                 }
 7568:              }
 7569:           }
 7570:           my $ave=$sum/(100*$number);
 7571: # Store
 7572:           my ($username,$domain)=split(/\:/,$user);
 7573:           my %grades=();
 7574:           $grades{"resource.$part.solved"}='correct_by_override';
 7575:           $grades{"resource.$part.awarded"}=$ave;
 7576:           $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
 7577:           my $returncode=&Apache::lonnet::cstore(\%grades,$symb,
 7578:                                                  $env{'request.course.id'},
 7579:                                                  $domain,$username);
 7580:           if ($returncode ne 'ok') {
 7581:              $result.="<br /><span class=\"LC_error\">Failed to save student $username:$domain. Message when trying to save was ($returncode)</span>";
 7582:           } else {
 7583:              $storecount++;
 7584:           }
 7585:        }
 7586:     }
 7587: # We are done
 7588:     $result.='<br />'.&mt('Successfully stored grades for [_1] student(s).',$storecount).
 7589:              '</td></tr></table>'."\n".
 7590:              '</td></tr></table><br /><br />'."\n";
 7591:     return $result.&show_grading_menu_form($symb);
 7592: }
 7593: 
 7594: sub handler {
 7595:     my $request=$_[0];
 7596: 
 7597:     &reset_caches();
 7598:     if ($env{'browser.mathml'}) {
 7599: 	&Apache::loncommon::content_type($request,'text/xml');
 7600:     } else {
 7601: 	&Apache::loncommon::content_type($request,'text/html');
 7602:     }
 7603:     $request->send_http_header;
 7604:     return '' if $request->header_only;
 7605:     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});
 7606:     my $symb=&get_symb($request,1);
 7607:     my @commands=&Apache::loncommon::get_env_multiple('form.command');
 7608:     my $command=$commands[0];
 7609:     if ($#commands > 0) {
 7610: 	&Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
 7611:     }
 7612:     $request->print(&Apache::loncommon::start_page('Grading'));
 7613:     if ($symb eq '' && $command eq '') {
 7614: 	if ($env{'user.adv'}) {
 7615: 	    if (($env{'form.codeone'}) && ($env{'form.codetwo'}) &&
 7616: 		($env{'form.codethree'})) {
 7617: 		my $token=$env{'form.codeone'}.'*'.$env{'form.codetwo'}.'*'.
 7618: 		    $env{'form.codethree'};
 7619: 		my ($tsymb,$tuname,$tudom,$tcrsid)=
 7620: 		    &Apache::lonnet::checkin($token);
 7621: 		if ($tsymb) {
 7622: 		    my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb);
 7623: 		    if (&Apache::lonnet::allowed('mgr',$tcrsid)) {
 7624: 			$request->print(&Apache::lonnet::ssi_body('/res/'.$url,
 7625: 					  ('grade_username' => $tuname,
 7626: 					   'grade_domain' => $tudom,
 7627: 					   'grade_courseid' => $tcrsid,
 7628: 					   'grade_symb' => $tsymb)));
 7629: 		    } else {
 7630: 			$request->print('<h3>Not authorized: '.$token.'</h3>');
 7631: 		    }
 7632: 		} else {
 7633: 		    $request->print('<h3>Not a valid DocID: '.$token.'</h3>');
 7634: 		}
 7635: 	    } else {
 7636: 		$request->print(&Apache::lonxml::tokeninputfield());
 7637: 	    }
 7638: 	}
 7639:     } else {
 7640: 	&init_perm();
 7641: 	if ($command eq 'submission' && $perm{'vgr'}) {
 7642: 	    ($env{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0));
 7643: 	} elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) {
 7644: 	    &pickStudentPage($request);
 7645: 	} elsif ($command eq 'displayPage' && $perm{'vgr'}) {
 7646: 	    &displayPage($request);
 7647: 	} elsif ($command eq 'gradeByPage' && $perm{'mgr'}) {
 7648: 	    &updateGradeByPage($request);
 7649: 	} elsif ($command eq 'processGroup' && $perm{'vgr'}) {
 7650: 	    &processGroup($request);
 7651: 	} elsif ($command eq 'gradingmenu' && $perm{'vgr'}) {
 7652: 	    $request->print(&gradingmenu($request));
 7653: 	} elsif ($command eq 'viewgrades' && $perm{'vgr'}) {
 7654: 	    $request->print(&viewgrades($request));
 7655: 	} elsif ($command eq 'handgrade' && $perm{'mgr'}) {
 7656: 	    $request->print(&processHandGrade($request));
 7657: 	} elsif ($command eq 'editgrades' && $perm{'mgr'}) {
 7658: 	    $request->print(&editgrades($request));
 7659: 	} elsif ($command eq 'verify' && $perm{'vgr'}) {
 7660: 	    $request->print(&verifyreceipt($request));
 7661:         } elsif ($command eq 'processclicker' && $perm{'mgr'}) {
 7662:             $request->print(&process_clicker($request));
 7663:         } elsif ($command eq 'processclickerfile' && $perm{'mgr'}) {
 7664:             $request->print(&process_clicker_file($request));
 7665:         } elsif ($command eq 'assignclickergrades' && $perm{'mgr'}) {
 7666:             $request->print(&assign_clicker_grades($request));
 7667: 	} elsif ($command eq 'csvform' && $perm{'mgr'}) {
 7668: 	    $request->print(&upcsvScores_form($request));
 7669: 	} elsif ($command eq 'csvupload' && $perm{'mgr'}) {
 7670: 	    $request->print(&csvupload($request));
 7671: 	} elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) {
 7672: 	    $request->print(&csvuploadmap($request));
 7673: 	} elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) {
 7674: 	    if ($env{'form.associate'} ne 'Reverse Association') {
 7675: 		$request->print(&csvuploadoptions($request));
 7676: 	    } else {
 7677: 		if ( $env{'form.upfile_associate'} ne 'reverse' ) {
 7678: 		    $env{'form.upfile_associate'} = 'reverse';
 7679: 		} else {
 7680: 		    $env{'form.upfile_associate'} = 'forward';
 7681: 		}
 7682: 		$request->print(&csvuploadmap($request));
 7683: 	    }
 7684: 	} elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {
 7685: 	    $request->print(&csvuploadassign($request));
 7686: 	} elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
 7687: 	    $request->print(&scantron_selectphase($request));
 7688:  	} elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {
 7689:  	    $request->print(&scantron_do_warning($request));
 7690: 	} elsif ($command eq 'scantron_validate' && $perm{'mgr'}) {
 7691: 	    $request->print(&scantron_validate_file($request));
 7692: 	} elsif ($command eq 'scantron_process' && $perm{'mgr'}) {
 7693: 	    $request->print(&scantron_process_students($request));
 7694:  	} elsif ($command eq 'scantronupload' && 
 7695:  		 (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
 7696: 		  &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
 7697:  	    $request->print(&scantron_upload_scantron_data($request)); 
 7698:  	} elsif ($command eq 'scantronupload_save' &&
 7699:  		 (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
 7700: 		  &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
 7701:  	    $request->print(&scantron_upload_scantron_data_save($request));
 7702:  	} elsif ($command eq 'scantron_download' &&
 7703: 		 &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
 7704:  	    $request->print(&scantron_download_scantron_data($request));
 7705: 	} elsif ($command) {
 7706: 	    $request->print("Access Denied ($command)");
 7707: 	}
 7708:     }
 7709:     $request->print(&Apache::loncommon::end_page());
 7710:     &reset_caches();
 7711:     return '';
 7712: }
 7713: 
 7714: 1;
 7715: 
 7716: __END__;

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