File:  [LON-CAPA] / loncom / homework / grades.pm
Revision 1.41: download - view: text, annotated - select for diffs
Thu Jul 25 21:25:38 2002 UTC (21 years, 8 months ago) by ng
Branches: MAIN
CVS tags: HEAD
Start work on grading for a whole class or section. Javascript part works.
Needs to update to db properly.
Also, give option to display just the submission w/o any option to grade.
Handles collaborators more elegantly now. Instructor is informed if a
student exceeds the max number of collaborators.

# The LearningOnline Network with CAPA
# The LON-CAPA Grading handler
#
# $Id: grades.pm,v 1.41 2002/07/25 21:25:38 ng Exp $
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
#
# LON-CAPA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LON-CAPA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# /home/httpd/html/adm/gpl.txt
#
# http://www.lon-capa.org/
#
# 2/9,2/13 Guy Albertelli
# 6/8 Gerd Kortemeyer
# 7/26 H.K. Ng
# 8/20 Gerd Kortemeyer
# Year 2002
# June, July 2002 H.K. Ng
#

package Apache::grades;
use strict;
use Apache::style;
use Apache::lonxml;
use Apache::lonnet;
use Apache::loncommon;
use Apache::lonhomework;
use Apache::lonmsg qw(:user_normal_msg);
use Apache::Constants qw(:common);
#use Time::HiRes qw( gettimeofday tv_interval );

sub moreinfo {
    my ($request,$reason) = @_;
    $request->print("Unable to process request: $reason");
    if ( $Apache::grades::viewgrades eq 'F' ) {
	$request->print('<form action="/adm/grades" method="post">'."\n");
	if ($ENV{'form.url'}) {
	    $request->print('<input type="hidden" name="url" value="'.$ENV{'form.url'}.'" />'."\n");
	}
	if ($ENV{'form.symb'}) {
	    $request->print('<input type="hidden" name="symb" value="'.$ENV{'form.symb'}.'" />'."\n");
	}
#    $request->print('<input type="hidden" name="command" value="submission" />'."\n");
	$request->print('<input type="hidden" name="command" value="'.$ENV{'form.command'}.'" />'."\n");
	$request->print("Student:".'<input type="text" name="student" value="'.$ENV{'form.student'}.'" />'."<br />\n");
	$request->print("Domain:".'<input type="text" name="domain" value="'.$ENV{'user.domain'}.'" />'."<br />\n");
	$request->print('<input type="submit" name="submit" value="ReSubmit" />'."<br />\n");
	$request->print('</form>');
    }
    return '';
}

sub verifyreceipt {
    my $request=shift;
    my $courseid=$ENV{'request.course.id'};
#    my $cdom=$ENV{"course.$courseid.domain"};
#    my $cnum=$ENV{"course.$courseid.num"};
    my $receipt=unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'}).'-'.
	$ENV{'form.receipt'};
    $receipt=~s/[^\-\d]//g;
    my $symb=$ENV{'form.symb'};
    unless ($symb) {
	$symb=&Apache::lonnet::symbread($ENV{'form.url'});
    }
    if ((&Apache::lonnet::allowed('mgr',$courseid)) && ($symb)) {
        $request->print('<h1>Verifying Submission Receipt '.$receipt.'</h1>');
        my $matches=0;
        my ($classlist) = &getclasslist('all','0');
        foreach my $student ( sort(@{ $$classlist{'all'} }) ) {
            my ($uname,$udom)=split(/\:/,$student);
            if ($receipt eq 
		&Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb)) {
		$request->print('Matching '.$student.'<br>');
		$matches++;
	   }
        }
        $request->printf('<p>'.$matches." match%s</p>",$matches <= 1 ? '' : 'es');
# needs to print who is matched
    }
    return '';
}

sub student_gradeStatus {
    my ($url,$udom,$uname,$partlist) = @_;
    my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));
    my %record= &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname);
    my %partstatus = ();
    foreach (@$partlist) {
	my ($status,$foo)=split(/_/,$record{"resource.$_.solved"},2);
	$status = 'nothing' if ($status eq '');
	$partstatus{$_} = $status;
	$partstatus{"resource.$_.submitted_by"} = $record{"resource.$_.submitted_by"} 
	if ($record{"resource.$_.submitted_by"} ne '');
    }
    return %partstatus;
}

sub get_fullname {
    my ($uname,$udom) = @_;
    my %name=&Apache::lonnet::get('environment', ['lastname','generation',
						  'firstname','middlename'],$udom,$uname);
    my $fullname;
    my ($tmp) = keys(%name);
    if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
	$fullname=$name{'lastname'}.$name{'generation'};
	if ($fullname =~ /[^\s]+/) { $fullname.=', '; }
	$fullname.=$name{'firstname'}.' '.$name{'middlename'};
    }
    return $fullname;
}

sub response_type {
    my ($url) = shift;
    my $allkeys = &Apache::lonnet::metadata($url,'keys');
#    print "allkeys=>$allkeys<br>";
    my %seen = ();
    my (@partlist,%handgrade);
    foreach (split(/,/,&Apache::lonnet::metadata($url,'packages'))) {
	if (/^\w+response_\d{1,2}.*/) {
	    my ($responsetype,$part) = split(/_/,$_,2);
	    my ($partid,$respid) = split(/_/,$part);
	    $handgrade{$part} = $responsetype.':'.($allkeys =~ /parameter_$part\_handgrade/ ? 'yes' : 'no');
	    next if ($seen{$partid} > 0);
	    $seen{$partid}++;
	    push @partlist,$partid;
	}
    }
    return \@partlist,\%handgrade;
}


sub listStudents {
    my ($request) = shift;
    my $cdom      =$ENV{"course.$ENV{'request.course.id'}.domain"};
    my $cnum      =$ENV{"course.$ENV{'request.course.id'}.num"};
    my $getsec    =$ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'};
    my $submitonly=$ENV{'form.submitonly'} eq '' ? 'all' : $ENV{'form.submitonly'};

    my $result='<h2><font color="#339933">&nbsp;View Submissions for a Student or a Group of Students</font></h2>';
    $result.='<table border="0">';
    $result.='<tr><td colspan=3><font size=+1><b>Resource: </b>'.$ENV{'form.url'}.'</font></td></tr>';
    my ($partlist,$handgrade) = &response_type($ENV{'form.url'});
    for (sort keys(%$handgrade)) {
	my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_});
	$ENV{'form.handgrade'} = 'yes' if ($handgrade eq 'yes');
	$result.='<tr><td><b>Part id: </b>'.$_.'</td>'.
	    '<td><b>Type: </b>'.$responsetype.'</td>'.
	    '<td><b>Handgrade: </b>'.$handgrade.'</font></td></tr>';
    }
    $result.='</table>';
    $request->print($result);

    $request->print(<<ENDTABLEST);
<form action="/adm/grades" method="post">
&nbsp;<b>View Problem: </b><input type="radio" name="vProb" value="no" checked> no 
<input type="radio" name="vProb" value="yes"> yes <br />
&nbsp;<b>Submissions: </b>
<input type="radio" name="lastSub" value="hdgrade" checked /> handgrade only
<input type="radio" name="lastSub" value="lastonly" /> last sub only
<input type="radio" name="lastSub" value="last" /> last sub & parts info
<input type="radio" name="lastSub" value="all" /> all details
<input type="hidden" name="section" value="$getsec" />
<input type="hidden" name="submitonly" value="$submitonly" />
<input type="hidden" name="response" value="$ENV{'form.response'}" />
<input type="hidden" name="handgrade" value="$ENV{'form.handgrade'}" /><br />
<input type="hidden" name="showgrading" value="$ENV{'form.showgrading'}" /><br />
<input type="submit" name="submit" value="View/Grade" />
ENDTABLEST
    if ($ENV{'form.url'}) {
	$request->print('<input type="hidden" name="url" value="'.$ENV{'form.url'}.'" />'."\n");
    }
    if ($ENV{'form.symb'}) {
	$request->print('<input type="hidden" name="symb" value="'.$ENV{'form.symb'}.'" />'."\n");
    }
    $request->print('<input type="hidden" name="command" value="processGroup" />'."\n");
    
    my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist($getsec,'0');
    
    $result='<table border="0"><tr><td bgcolor="#777777">'.
	'<table border="0"><tr bgcolor="#e6ffff">'.
	'<td><b>&nbsp;Select&nbsp;</b></td><td><b>&nbsp;Username&nbsp;</b></td>'.
	'<td><b>&nbsp;Fullname&nbsp;</b></td><td><b>&nbsp;Domain&nbsp;</b></td>';
    foreach (sort(@$partlist)) {
	$result.='<td><b>&nbsp;Part ID '.$_.' Status&nbsp;</b></td>';
    }
    $request->print($result.'</tr>'."\n");

    foreach my $student (sort(@{ $$classlist{$getsec} }) ) {
	my ($uname,$udom) = split(/:/,$student);
	my (%status) = &student_gradeStatus($ENV{'form.url'},$udom,$uname,$partlist);
	my $statusflg = '';
	foreach (keys(%status)) {
	    $statusflg = 1 if ($status{$_} ne 'nothing');
	    my ($foo,$partid,$foo) = split(/\./,$_);
	    if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
		$statusflg = '';
		$request->print('<input type="hidden" name="'.
				$student.':submitted_by" value="'.
				$status{'resource.'.$partid.'.submitted_by'}.'" />');
	    }
	}
	next if ($statusflg eq '' && $submitonly eq 'yes');

	if ( $Apache::grades::viewgrades eq 'F' ) {
	    $result='<tr bgcolor="#ffffe6">'.
		'<td align="center"><input type=checkbox name="stuinfo" value="'.
		$student.':'.$$fullname{$student}.'"></td>'."\n".
		'<td>&nbsp;'.$uname.'&nbsp;</td>'."\n".
		'<td>&nbsp;'.$$fullname{$student}.'&nbsp;</td>'."\n".
		'<td align="middle">&nbsp;'.$udom.'&nbsp;</td>'."\n";
	    
	    foreach (sort keys(%status)) {
		next if (/^resource.*?submitted_by$/);
		$result.='<td align="middle">&nbsp;'.$status{$_}.'&nbsp;</td>'."\n";
	    }
	    $request->print($result.'</tr>'."\n");
	}
    }
    $request->print('</table></td></tr></table>');
    $request->print('<input type="submit" name="submit" value="View/Grade" /><form />');
}

sub processGroup {
    my ($request)  = shift;
    my $ctr        = 0;
    my @stuchecked = (ref($ENV{'form.stuinfo'}) ? @{$ENV{'form.stuinfo'}}
		      : ($ENV{'form.stuinfo'}));
    my $total      = scalar(@stuchecked)-1;
    if ($stuchecked[0] eq '') {
	&userError($request,'No student was selected for viewing/grading.');
	return;
    }
    foreach (@stuchecked) {
	my ($uname,$udom,$fullname) = split(/:/);
	$ENV{'form.student'} = $uname;
	$ENV{'form.fullname'} = $fullname;
	&submission($request,$ctr,$total);
	$ctr++;
    }
    return '';
}

sub userError {
    my ($request, $reason, $step) = @_;
    $request->print('<h3><font color="red">LON-CAPA User Error</font></h3><br />'."\n");
    $request->print('<b>Reason: </b>'.$reason.'<br /><br />'."\n");
    $request->print('<b>Step: </b>'.($step ne '' ? $step : 'Use your browser back button to correct')
		    .'<br /><br />'."\n");
    return '';
}

#FIXME - needs to handle multiple matches
sub finduser {
    my ($name) = @_;
    my $domain = '';
    if ( $Apache::grades::viewgrades eq 'F' ) {
	my ($classlist) = &getclasslist('all','0');
	foreach ( sort(@{ $$classlist{'all'} }) ) {
	    my ($posname,$posdomain) = split(/:/);
	    if ($posname =~ $name) { $name=$posname; $domain=$posdomain; last; }
	}
	return ($name,$domain);
    } else {
	return ($ENV{'user.name'},$ENV{'user.domain'});
    }
}

sub getclasslist {
    my ($getsec,$hideexpired) = @_;
    my $now = time;
    my %classlist=&Apache::lonnet::dump('classlist',
					$ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
					$ENV{'course.'.$ENV{'request.course.id'}.'.num'});
    my (@holdsec,@sections,%allids,%stusec,%fullname);
    foreach (keys(%classlist)) {
	my ($end,$start,$id,$section,$fullname)=split(/:/,$classlist{$_});
       # still a student?
	if (($hideexpired) && ($end) && ($end < $now)) {
	    next;
	}
	$section = ($section ne '' ? $section : 'no');
	push @holdsec,$section;
	if ($getsec eq 'all' || $getsec eq $section) {
	    push (@{ $classlist{$getsec} }, $_);
	    $allids{$_}=$id;
	    $stusec{$_}=$section;
	    $fullname{$_}=$fullname;
	}
    }
    my %seen = ();
    foreach my $item (@holdsec) {
	push (@sections, $item) unless $seen{$item}++;
    }
    return (\%classlist,\@sections,\%allids,\%stusec,\%fullname);
}

sub getpartlist {
    my ($url) = @_;
    my @parts =();
    my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys'));
    foreach my $key (@metakeys) {
	if ( $key =~ m/stores_([0-9]+)_.*/) {
	    push(@parts,$key);
	}
    }
    return @parts;
}

#FIXME need to look at the metadata <stores> spec on what type of data to accept and provide an
#interface based on that, also do that to above function.
sub setstudentgrade {
    my ($url,$symb,$courseid,$student,@parts) = @_;
    my $result ='';
    my ($stuname,$domain) = split(/:/,$student);
    my %record=&Apache::lonnet::restore($symb,$courseid,$domain,$stuname);
    my %newrecord;

    foreach my $part (@parts) {
	my ($temp,$part,$type)=split(/_/,$part);
	my $oldscore=$record{"resource.$part.$type"};
	my $newscore=$ENV{"form.GRADE.$student.$part.$type"};
	if ($type eq 'solved') {
	    my $update=0;
	    if ($newscore eq 'nothing' ) {
		if ($oldscore ne '') {
		    $update=1;
		    $newscore = '';
		}
	    } elsif ($oldscore !~ m/^$newscore/) {
		$update=1;
		$result.="Updating $stuname to $newscore<br />\n";
		if ($newscore eq 'correct')   { $newscore = 'correct_by_override'; }
		if ($newscore eq 'incorrect') { $newscore = 'incorrect_by_override'; }
		if ($newscore eq 'excused')   { $newscore = 'excused'; }
		if ($newscore eq 'ungraded')  { $newscore = 'ungraded_attempted'; }
	    } else {
		#$result.="$stuname:$part:$type:unchanged  $oldscore to $newscore:<br />\n";
	    }
	    if ($update) { $newrecord{"resource.$part.$type"}=$newscore; }
	} else {
	    if ($oldscore ne $newscore) {
		$newrecord{"resource.$part.$type"}=$newscore;
		$result.="Updating $student"."'s status for $part.$type to $newscore<br />\n";
	    } else {
		#$result.="$stuname:$part:$type:unchanged  $oldscore to $newscore:<br />\n";
	    }
	}
    }
    if ( scalar(keys(%newrecord)) > 0 ) {
	$newrecord{'resource.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}";
#       &Apache::lonnet::cstore(\%newrecord,$symb,$courseid,$domain,$stuname);

	$result.="Stored away ".scalar(keys(%newrecord))." elements.<br />\n";
    }
    return $result;
}

sub print_hash {
    my ($request, $hash) = @_;
    $request->print('<table border=1><tr><td>Key</td><td>Value</td></tr>');
    for (sort keys (%$hash)) {
	$request->print('<tr><td>'.$_.'</td><td>'.$$hash{$_}.'&nbsp;</td></tr>');
    }
    $request->print('</table>');
    return '';
}
#
# --------------------------- show submissions of a student, option to grade --------
sub submission {
    my ($request,$counter,$total) = @_;

    (my $url=$ENV{'form.url'})=~s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
    if ($ENV{'form.student'} eq '') { &moreinfo($request,'Need student login id'); return ''; }
    my ($uname,$udom) = &finduser($ENV{'form.student'});
    if ($uname eq '') { &moreinfo($request,'Unable to find student'); return ''; }

    my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));
    if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; }
    my $last = ($ENV{'form.lastSub'} eq 'last' ? 'last' : '');
    $ENV{'form.vProb'} = $ENV{'form.vProb'} ne '' ? $ENV{'form.vProb'} : 'yes';
    my ($classlist,$seclist,$ids,$stusec,$fullname);

    # header info
    if ($counter == 0) {
	&sub_page_js($request);
	$request->print('<h2>&nbsp;<font color="#339933">Submission Record</font></h2>'.
			'<font size=+1>&nbsp;<b>Resource: </b>'.$url.'</font>');

	# option to display problem, only once else it cause problems with the form later 
	# since the problem has a form.
	if ($ENV{'form.vProb'} eq 'yes') {
	    my $rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,
							      $ENV{'request.course.id'});
	    my $companswer=&Apache::loncommon::get_student_answers($symb,$uname,$udom,
								   $ENV{'request.course.id'});
	    my $result.='<table border="0" width="100%"><tr><td bgcolor="#777777">';
	    $result.='<table border="0" width="100%"><tr><td bgcolor="#e6ffff">';
	    $result.='<b>Student\'s view of the problem</b></td></tr><tr><td bgcolor="#ffffff">'.$rendered.'<br />';
	    $result.='<b>Correct answer:</b><br />'.$companswer;
	    $result.='</td></tr></table>';
	    $result.='</td></tr></table><br />';
	    $request->print($result);
	}
	
	# kwclr is the only variable that is guaranteed to be non blank if this subroutine has been called once.
	my %keyhash = ();
	if ($ENV{'form.kwclr'} eq '') {
	    %keyhash = &Apache::lonnet::dump('nohist_handgrade',
					     $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
					     $ENV{'course.'.$ENV{'request.course.id'}.'.num'});

	    my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'};
	    $ENV{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
	    $ENV{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
	    $ENV{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
	    $ENV{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
	    $ENV{'form.msgsub'}   = $keyhash{$symb.'_subject'} ne '' ? 
		$keyhash{$symb.'_subject'} : &Apache::lonnet::metadata($url,'title');
	    $ENV{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0';

	}
	$request->print('<form action="/adm/grades" method="post" name="SCORE">'."\n".
			'<input type="hidden" name="command"    value="handgrade" />'."\n".
			'<input type="hidden" name="refresh"    value="off" />'."\n".
			'<input type="hidden" name="symb"       value="'.$symb.'" />'."\n".
			'<input type="hidden" name="url"        value="'.$url.'" />'."\n".
			'<input type="hidden" name="showgrading" value="'.$ENV{'form.showgrading'}.'" />'."\n".
			'<input type="hidden" name="vProb"      value="'.$ENV{'form.vProb'}.'" />'."\n".
			'<input type="hidden" name="lastSub"    value="'.$ENV{'form.lastSub'}.'" />'."\n".
			'<input type="hidden" name="section"    value="'.$ENV{'form.section'}.'">'."\n".
			'<input type="hidden" name="submitonly" value="'.$ENV{'form.submitonly'}.'">'."\n".
			'<input type="hidden" name="response"   value="'.$ENV{'form.response'}.'">'."\n".
			'<input type="hidden" name="handgrade"  value="'.$ENV{'form.handgrade'}.'">'."\n".
			'<input type="hidden" name="keywords"   value="'.$ENV{'form.keywords'}.'" />'."\n".
			'<input type="hidden" name="kwclr"      value="'.$ENV{'form.kwclr'}.'" />'."\n".
			'<input type="hidden" name="kwsize"     value="'.$ENV{'form.kwsize'}.'" />'."\n".
			'<input type="hidden" name="kwstyle"    value="'.$ENV{'form.kwstyle'}.'" />'."\n".
			'<input type="hidden" name="msgsub"     value="'.$ENV{'form.msgsub'}.'" />'."\n".
			'<input type="hidden" name="savemsgN"   value="'.$ENV{'form.savemsgN'}.'" />'."\n".
			'<input type="hidden" name="NCT"'.
			' value="'.($ENV{'form.NTSTU'} ne '' ? $ENV{'form.NTSTU'} : $total+1).'" />'."\n");
	
	my ($cts,$prnmsg) = (1,'');
	while ($cts <= $ENV{'form.savemsgN'}) {
	    $prnmsg.='<input type="hidden" name="savemsg'.$cts.'" value="'.
		($keyhash{$symb.'_savemsg'.$cts} eq '' ? $ENV{'form.savemsg'.$cts} : $keyhash{$symb.'_savemsg'.$cts}).
		'" />'."\n";
	    $cts++;
	}
	$request->print($prnmsg);

	if ($ENV{'form.handgrade'} eq 'yes' && $ENV{'form.showgrading'} eq 'yes') {
	    $request->print(<<KEYWORDS);
&nbsp;<b>Keyword Options:</b>&nbsp;
<a href="javascript:keywords(document.SCORE.keywords)"; TARGET=_self>List</a>&nbsp; &nbsp;
<a href="#" onMouseDown="javascript:getSel(); return false"
 CLASS="page">Paste Selection to List</a>&nbsp; &nbsp;
<a href="javascript:kwhighlight()"; TARGET=_self>Highlight Attribute</a><br /><br />
KEYWORDS
        }
    }
    my %record = &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname);
    my ($partlist,$handgrade) = &response_type($url);
#    &print_hash($request,\%record);

    # Student info
    $request->print(($counter == 0 ? '' : '<br />'));
    my $fullname = ($ENV{'form.fullname'} ne '' ? $ENV{'form.fullname'} : &get_fullname($uname,$udom));
    my $result='<table border="0" width=100%><tr><td bgcolor="#777777">'.
	'<table border="0" width=100%><tr bgcolor="#ffffff"><td>';

    $result.='<table border="0"><tr bgcolor="#ffffff"><td><b>Fullname: </b>'.$fullname.
	'</td><td>&nbsp; &nbsp;<b>Username: </b>'.$uname.
	'</td><td>&nbsp; &nbsp;<b>Domain: </b>'.$udom.'</td></tr>';
    if ($ENV{'form.handgrade'} eq 'yes') {
	my @col_list;
	($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist('all','0');
	for (keys (%$handgrade)) {
	    my $ncol = &Apache::lonnet::EXT('resource.'.$_.'.maxcollaborators',$symb,$udom,$uname);
	    if ($ncol > 0) {
		s/\_/\./g;
		if ($record{'resource.'.$_.'.collaborators'} ne '') {
		    my (@collaborators) = split(/,?\s+/,$record{'resource.'.$_.'.collaborators'});
		    my (@badcollaborators);
		    if (scalar(@collaborators) != 0) {
			$result.='<tr bgcolor="#ffffff"><td colspan=3><b>Collaborators: </b>';
			foreach my $collaborator (@collaborators) {
			    $collaborator = $collaborator =~ /\@|:/ ? 
				(split(/@|:/,$collaborator))[0] : $collaborator;
			    next if ($collaborator eq $uname);
			    if (!grep /^$collaborator:/i,keys %$classlist) {
				push @badcollaborators,$collaborator;
				next;
			    }
			    push @col_list, $collaborator;
			    $result.=$$fullname{$collaborator.':'.$udom}.' ('.$collaborator.') &nbsp; &nbsp;';
			}
			$result.='</td></tr>'."\n";
			$result.='<tr bgcolor="#ffbbbb"><td colspan=2>'.
			    'This student has submitted '.(scalar (@badcollaborators) > 1 ? '' : 'an').
			    ' invalid collaborator'.(scalar (@badcollaborators) > 1 ? 's. ' : '. ').
			    (join ', ',@badcollaborators).'</td></tr>' 
			    if (scalar(@badcollaborators) > 0);

			$result.='<tr bgcolor="#ffbbbb"><td colspan=2>'.
			    'This student has submitted too many collaborators. Maximum is '.
			    $ncol.'.</td></tr>' if (scalar(@collaborators) > $ncol);
			$result.='<input type="hidden" name="collaborator'.$counter.
			    '" value="'.(join ':',@col_list).'" />'."\n";
		    }
		}
	    }
	}
    }
    $request->print($result.'</table>'."\n");

    # print student answer
    if ($ENV{'form.lastSub'} =~ /^(lastonly|hdgrade)$/) {
	if ($ENV{'form.'.$uname.':'.$udom.':submitted_by'}) {
	    my $submitby='</td></tr><tr><td bgcolor="#e6ffff">'.
		'<b>Collaborative submission by: </b>'.
		'<a href="javascript:viewSubmitter(\''.$ENV{'form.'.$uname.':'.$udom.':submitted_by'}.
		'\')"; TARGET=_self>'.
		$$fullname{$ENV{'form.'.$uname.':'.$udom.':submitted_by'}}.'</a>';
	    $submitby.='</td></tr><tr><td bgcolor="#ffffff">'."\n";
	    $request->print($submitby);
	} else {
	    my ($string,$timestamp)=&get_last_submission ($symb,$uname,$udom,$ENV{'request.course.id'});
	    my $lastsubonly.='</td></tr><tr><td bgcolor="#e6ffff"><b>Last Submission Only</b>'.
		($$timestamp eq '' ? '' : '&nbsp; &nbsp; <b>Date Submitted:</b> '.$$timestamp).'</td></tr>';
	    if ($$timestamp eq '') {
		$lastsubonly.='<tr><td bgcolor="#ffffe6">'.$$string[0].'</td></tr>';
	    } else {
		for my $part (sort keys(%$handgrade)) {
		    foreach (@$string) {
			my ($partid,$respid) = /^resource\.(\d+)\.(\d+)\.submission/;
			if ($part eq ($partid.'_'.$respid)) {
			    my ($ressub,$subval) = split(/:/,$_,2);
			    $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part ID</b> '.
				$partid.' <b>Response ID</b> '.$respid.
				' <b>Submission</b> '.&keywords_highlight($subval).'</td></tr>'
				if ($ENV{'form.lastSub'} eq 'lastonly' || 
				    ($ENV{'form.lastSub'} eq 'hdgrade' && $$handgrade{$part} =~ /:yes$/));
			}
		    }
		}
	    }
	    $lastsubonly.='</td></tr><tr><td bgcolor="#ffffff">'."\n";
	    $request->print($lastsubonly);
	}
    } else {
	$request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,
								 $ENV{'request.course.id'},$last,
								 '.submission','Apache::grades::keywords_highlight'));
    }
    
    # view submission with no grading option
    if ($ENV{'form.showgrading'} eq '') {
	$request->print('</td></tr></table></td></tr></table></form>');
	return;
    }

    $result='<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n".
	'<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".
	'<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'.$udom.'" />'."\n";
    $result.='&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.
	',\''.$fullname.'\')"; TARGET=_self>Compose Message</a><br />'."\n" if ($ENV{'form.handgrade'} eq 'yes');
    $request->print($result);

    my %seen = ();
    my @partlist;
    for (sort keys(%$handgrade)) {
	my ($partid,$respid) = split(/_/);
	next if ($seen{$partid} > 0);
	$seen{$partid}++;
	next if ($$handgrade{$_} =~ /:no$/);
	push @partlist,$partid;
	my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);
	my $wgtmsg = ($wgt > 0 ? '(problem weight)' : '<font color="red">problem weight assigned by computer</font>');
	$wgt       = ($wgt > 0 ? $wgt : '1');
	my $score  = ($record{'resource.'.$partid.'.awarded'} eq '' ?
		      '' : $record{'resource.'.$partid.'.awarded'}*$wgt);

	# display grading options
	$result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />';
	$result.='<table border="0"><tr><td><b>Part </b>'.$partid.' <b>Points</b></td><td>';

	my $ctr = 0;
	$result.='<table border="0"><tr>';  # display radio buttons in a nice table 10 across
	while ($ctr<=$wgt) {
	    $result.= '<td><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
		'onclick="javascript:writeBox(this.form.GRADE_BOX'.$counter.'_'.$partid.
		',this.form.GRADE_SEL'.$counter.'_'.$partid.','.$ctr.
		',this.form.stores'.$counter.'_'.$partid.')" '.
		($score eq $ctr ? 'checked':'').' /> '.$ctr."</td>\n";
	    $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
	    $ctr++;
	}
	$result.='</tr></table>';

	$result.='</td><td>&nbsp;<b>or</b>&nbsp;</td>';
	$result.='<td><input type="text" name="GRADE_BOX'.$counter.'_'.$partid.'"'.
	    ($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
	    'onChange="javascript:updateRadio(this.form.RADVAL'.$counter.'_'.$partid.
	    ',this.form.GRADE_BOX'.$counter.'_'.$partid.
	    ',this.form.GRADE_SEL'.$counter.'_'.$partid.
	    ',this.form.stores'.$counter.'_'.$partid.')" /></td>'."\n";
	$result.='<td>/'.$wgt.' '.$wgtmsg.' </td><td>';

	$result.='<select name="GRADE_SEL'.$counter.'_'.$partid.'" '.
	    'onChange="javascript:clearRadBox(this.form.RADVAL'.$counter.'_'.$partid.
	    ',this.form.GRADE_BOX'.$counter.'_'.$partid.
	    ',this.form.GRADE_SEL'.$counter.'_'.$partid.
	    ',this.form.stores'.$counter.'_'.$partid.')" />'."\n".
	    '<option selected="on"> </option>'.
	    '<option>excused</option></select>'."&nbsp&nbsp\n";
	$result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="0" />';
	$result.='</td></tr></table>';
	$request->print($result);
    }
    $request->print('<input type="hidden" name="partlist'.$counter.'" value="'.(join ":",@partlist).'" />'."\n");
    $request->print('</td></tr></table></td></tr></table>'."\n");

    # print end of form
    if ($counter == $total) {
	my $endform.='<table border="0"><tr><td><input type="submit" name="gradeOpt" value="Save & Next" />';
	my $ntstu ='<select name="NTSTU">'.
	    '<option>1</option><option>2</option>'.
	    '<option>3</option><option>5</option>'.
	    '<option>7</option><option>10</option></select>'."\n";
	my $nsel = ($ENV{'form.NTSTU'} ne '' ? $ENV{'form.NTSTU'} : '1');
	$ntstu =~ s/<option>$nsel</<option selected="on">$nsel</;
	$endform.=$ntstu.'student(s) &nbsp;&nbsp;'.
	    '<input type="submit" name="gradeOpt" value="Next" />&nbsp'.
	    '<input type="submit" name="gradeOpt" value="Previous" />&nbsp'.
	    '(Next and Previous do not save the scores.)'.
	    '</td><tr></table></form>';
	$request->print($endform);
    }
    return '';
}

sub get_last_submission {
    my ($symb,$username,$domain,$course)=@_;
    if ($symb) {
	my (@string,$timestamp);
	my (%returnhash)=&Apache::lonnet::restore($symb,$course,$domain,$username);
	if ($returnhash{'version'}) {
	    my %lasthash=();
	    my ($version);
	    for ($version=1;$version<=$returnhash{'version'};$version++) {
		foreach (sort(split(/\:/,$returnhash{$version.':keys'}))) {
		    $lasthash{$_}=$returnhash{$version.':'.$_};
		}
	    }
	    foreach ((keys %lasthash)) {
		if ($_ =~ /\.submission$/) {push @string, (join(':',$_,$lasthash{$_}))}
		if ($_ =~ /timestamp/) {$timestamp = scalar(localtime($lasthash{$_}))};
	    }
	}
	@string = $string[0] eq '' ? 'Nothing submitted - no attempts.' : @string;
	return \@string,\$timestamp;
    }
}

sub keywords_highlight {
    my $string  = shift;
    my $size    = $ENV{'form.kwsize'} eq '0' ? '' : 'size='.$ENV{'form.kwsize'};
    my $styleon = $ENV{'form.kwstyle'} eq ''  ? '' : $ENV{'form.kwstyle'};
    (my $styleoff = $styleon) =~ s/\</\<\//;
    my @keylist = split(/[,\s+]/,$ENV{'form.keywords'});
    foreach (@keylist) {
	$string =~ s/\b$_(\b|\.)/\<font color\=$ENV{'form.kwclr'} $size\>$styleon$_$styleoff\<\/font\>/gi;
    }
    return $string;
}

sub processHandGrade {
    my ($request) = shift;
    my $url    = $ENV{'form.url'};
    my $symb   = $ENV{'form.symb'};
    my $button = $ENV{'form.gradeOpt'};
    my $ngrade = $ENV{'form.NCT'};
    my $ntstu  = $ENV{'form.NTSTU'};

    my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'};
    my %keyhash = ();
    $ENV{'form.keywords'}           =~ s/,\s{0,}|\s+/ /g;
    $ENV{'form.keywords'}           =~ s/^\s+|\s+$//;
    $keyhash{$symb.'_keywords'}     = $ENV{'form.keywords'};
    $keyhash{$symb.'_subject'}      = $ENV{'form.msgsub'};
    $keyhash{$loginuser.'_kwclr'}   = $ENV{'form.kwclr'};
    $keyhash{$loginuser.'_kwsize'}  = $ENV{'form.kwsize'};
    $keyhash{$loginuser.'_kwstyle'} = $ENV{'form.kwstyle'};

    my ($ctr,$idx) = (1,1);
    while ($ctr <= $ENV{'form.savemsgN'}) {
	if ($ENV{'form.savemsg'.$ctr} ne '') {
	    $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.savemsg'.$ctr};
	    $idx++;
	}
	$ctr++;
    }
    $ctr = 0;
    while ($ctr < $ngrade) {
	if ($ENV{'form.newmsg'.$ctr} ne '') {
	    $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.newmsg'.$ctr};
	    $ENV{'form.savemsg'.$idx} = $ENV{'form.newmsg'.$ctr};
	    $idx++;
	}
	$ctr++;
    }
    $ENV{'form.savemsgN'} = --$idx;
    $keyhash{$symb.'_savemsgN'} = $ENV{'form.savemsgN'};
    my $putresult = &Apache::lonnet::put
	('nohist_handgrade',\%keyhash,
	 $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
	 $ENV{'course.'.$ENV{'request.course.id'}.'.num'});

    if ($ENV{'form.refresh'} eq 'on') {
	my $ctr = 0;
	$ENV{'form.NTSTU'}=$ngrade;
	while ($ctr < $ngrade) {
	    ($ENV{'form.student'},my $udom) = split(/:/,$ENV{'form.unamedom'.$ctr});
	    &submission($request,$ctr,$ngrade-1);
	    $ctr++;
	}
	return '';
    }

    if ($button eq 'Save & Next') {
	my $ctr = 0;
	while ($ctr < $ngrade) {
	    my ($uname,$udom) = split(/:/,$ENV{'form.unamedom'.$ctr});
	    my ($errorflg) = &saveHandGrade($request,$url,$symb,$uname,$udom,$ctr);
	    return '' if ($errorflg eq 'error');

	    my $includemsg = $ENV{'form.includemsg'.$ctr};
	    my ($subject,$message,$msgstatus) = ('','','');
	    if ($includemsg =~ /savemsg|new$ctr/) {
		$subject = $ENV{'form.msgsub'} if ($includemsg =~ /^msgsub/);
		my (@msgnum) = split(/,/,$includemsg);
		foreach (@msgnum) {
		    $message.=$ENV{'form.'.$_} if ($_ =~ /savemsg|newmsg/ && $_ ne '');
		}
		$message =~ s/\s+/ /g;
		$msgstatus = &Apache::lonmsg::user_normal_msg ($uname,$udom,
							       $ENV{'form.msgsub'},$message);
	    }
	    if ($ENV{'form.collaborator'.$ctr}) {
		my (@collaborators) = split(/:/,$ENV{'form.collaborator'.$ctr});
		foreach (@collaborators) {
		    &saveHandGrade($request,$url,$symb,$_,$udom,$ctr,
				   $ENV{'form.unamedom'.$ctr});
		    if ($message ne '') {
			$msgstatus = &Apache::lonmsg::user_normal_msg ($_,$udom,
								       $ENV{'form.msgsub'},
								       $message);
		    }
		}
	    }
	    $ctr++;
	}
    }
    my $firststu = $ENV{'form.unamedom0'};
    my $laststu = $ENV{'form.unamedom'.($ngrade-1)};
    $ctr = 2;
    while ($laststu eq '') {
	$laststu  = $ENV{'form.unamedom'.($ngrade-$ctr)};
	$ctr++;
	$laststu = $firststu if ($ctr > $ngrade);
    }
    my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist($ENV{'form.section'},'0');

    my (@parsedlist,@nextlist);
    my ($nextflg) = 0;
    foreach ( sort(@{ $$classlist{$ENV{'form.section'}} }) ) {
	if ($nextflg == 1 && $button =~ /Next$/) {
	    push @parsedlist,$_;
	}
	$nextflg = 1 if ($_ eq $laststu);
	if ($button eq 'Previous') {
	    last if ($_ eq $firststu);
	    push @parsedlist,$_;
	}
    }
    $ctr = 0;
    my ($partlist,$handgrade) = &response_type($ENV{'form.url'});
    @parsedlist = reverse @parsedlist if ($button eq 'Previous');
    foreach my $student (@parsedlist) {
	my ($uname,$udom) = split(/:/,$student);
	if ($ENV{'form.submitonly'} eq 'yes') {
	    my (%status) = &student_gradeStatus($ENV{'form.url'},$udom,$uname,$partlist) ;
	    my $statusflg = '';
	    foreach (keys(%status)) {
		$statusflg = 1 if ($status{$_} ne 'nothing');
		my ($foo,$partid,$foo) = split(/\./,$_);
		$statusflg = '' if ($status{'resource.'.$partid.'.submitted_by'} ne '');
	    }
	    next if ($statusflg eq '');
	}
	push @nextlist,$student if ($ctr < $ntstu);
	$ctr++;
    }

    $ctr = 0;
    my $total = scalar(@nextlist)-1;

    foreach (sort @nextlist) {
	my ($uname,$udom,$submitter) = split(/:/);
	$ENV{'form.student'} = $uname;
	$ENV{'form.fullname'} = $$fullname{$_};
#	$ENV{'form.'.$_.':submitted_by'} = $submitter;
#	print "submitter=$ENV{'form.'.$_.':submitted_by'}= $submitter:<br>";
	&submission($request,$ctr,$total);
	$ctr++;
    }
    if ($total < 0) {
	my $the_end = '<h3><font color="red">LON-CAPA User Message</font></h3><br />'."\n";
	$the_end.='<b>Message: </b> No more students for this section or class.<br /><br />'."\n";
	$the_end.='Click on the button below to return to the grading menu.<br /><br />'."\n";
	$the_end.=&show_grading_menu_form ($symb,$url);
	$request->print($the_end);
    }
    return '';
}

sub saveHandGrade {
    my ($request,$url,$symb,$stuname,$domain,$newflg,$submitter) = @_;
#   my %record=&Apache::lonnet::restore($symb,$ENV{'request.course.id'},$domain,$stuname);
    my %newrecord;
    foreach (split(/:/,$ENV{'form.partlist'.$newflg})) {
	if ($ENV{'form.GRADE_SEL'.$newflg.'_'.$_} eq 'excused') {
	    $newrecord{'resource.'.$_.'.solved'} = 'excused';
	} else {
	    my $pts    = ($ENV{'form.GRADE_BOX'.$newflg.'_'.$_} ne '' ? 
			  $ENV{'form.GRADE_BOX'.$newflg.'_'.$_} : $ENV{'form.RADVAL'.$newflg.'_'.$_});
	    if ($pts eq '') {
		&userError($request,'No point was assigned for part id '.$_.' and for username '.$stuname.'.');
		return 'error';
	    }
	    my $wgt    = $ENV{'form.WGT'.$newflg.'_'.$_} eq '' ? 1 : $ENV{'form.WGT'.$newflg.'_'.$_};
	    my $partial= $pts/$wgt;
	    $newrecord{'resource.'.$_.'.awarded'}  = $partial;
	    if ($partial == 0) {
		$newrecord{'resource.'.$_.'.solved'} = 'incorrect_by_override';
	    } else {
		$newrecord{'resource.'.$_.'.solved'} = 'correct_by_override';
	    }
	    $newrecord{'resource.'.$_.'.submitted_by'} = $submitter if ($submitter);
	}
    }
    
    if ( scalar(keys(%newrecord)) > 0 ) {
	$newrecord{'resource.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}";
#	&print_hash($request,\%newrecord);
	&Apache::lonnet::cstore(\%newrecord,$symb,$ENV{'request.course.id'},$domain,$stuname);
    }
    return '';
}

sub get_symb_and_url {
    my ($request) = @_;
    (my $url=$ENV{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
    my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));
    if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; }
    return ($symb,$url);
}

sub show_grading_menu_form {
    my ($symb,$url)=@_;
    my $result.='<form action="/adm/grades" method="post">'."\n".
	'<input type="hidden" name="symb" value="'.$symb.'" />'."\n".
	'<input type="hidden" name="url" value="'.$url.'" />'."\n".
	'<input type="hidden" name="command" value="gradingmenu" />'."\n".
	'<input type="submit" name="submit" value="Grading Menu" />'."\n".
	'</form>'."\n";
    return $result;
}

sub gradingmenu {
    my ($request) = @_;
    my ($symb,$url)=&get_symb_and_url($request);
    if (!$symb) {return '';}
    my $result='<h2>&nbsp;<font color="#339933">Select a Grading Method</font></h2>';
    $result.='<table border="0">';
    $result.='<tr><td colspan=3><font size=+1><b>Resource: </b>'.$url.'</font></td></tr>';
    my ($partlist,$handgrade) = &response_type($url);
    my ($resptype,$hdgrade)=('','no');
    for (sort keys(%$handgrade)) {
	my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_});
	$resptype = $responsetype;
	$hdgrade = $handgrade if ($handgrade eq 'yes');
	$result.='<tr><td><b>Part id: </b>'.$_.'</td>'.
	    '<td><b>Type: </b>'.$responsetype.'</td>'.
	    '<td><b>Handgrade: </b>'.$handgrade.'</font></td></tr>';
    }
    $result.='</table>';
    $result.=&view_edit_entire_class_form($symb,$url).'<br />';
    $result.=&upcsvScores_form($symb,$url).'<br />';
    $result.=&viewGradeaStu_form($symb,$url,$resptype,$hdgrade).'<br />';
    $result.=&verifyReceipt_form($symb,$url).'<br />';
    $result.=&view_classlist_form($symb,$url);

    return $result;
}

sub view_classlist_form {
    my ($symb,$url)=@_;
    my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";
    $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";
    $result.='&nbsp;<b>View Class List</b></td></tr>'."\n";
    $result.='<tr bgcolor=#ffffe6><td>'."\n";
    $result.='<form action="/adm/grades" method="post">'."\n".
	'<input type="hidden" name="symb" value="'.$symb.'" />'."\n".
	'<input type="hidden" name="url" value="'.$url.'" />'."\n".
	'<input type="hidden" name="command" value="viewclasslist" />'."\n";
    $result.='&nbsp;<input type="submit" name="submit" value="View Class" /></form>'."\n";
    $result.='</td></tr></table>'."\n";
    $result.='</td></tr></table>'."\n";
    return $result;
}

sub viewclasslist {
    my ($request) = shift;
    my ($coursedomain,$coursenum) = split(/_/,$ENV{'request.course.id'});
    my %classlist=&Apache::lonnet::dump('classlist',$coursedomain,$coursenum);
    $request->print('<table border=1>');
    foreach (sort keys(%classlist)) {
#    my ($unam,$udom) = split(/:/,$_,2);
#    my $section = &Apache::lonnet::usection($udom,$unam,$ENV{'request.course.id'});
#    my $fullname = &get_fullname ($unam,$udom);
#    my @uname;
#    $uname[0]=$unam;
#    my %userid=&Apache::lonnet::idrget($udom,@uname);
#    my $value=$classlist{$_}.':'.$userid{$unam}.':'.$section.':'.$fullname;
#    $classlist{$_}=$value;
	$request->print('<tr><td>'.$_.' </td><td><pre> '.$classlist{$_}.'</pre></td></tr>');
    }
    $request->print('</table>');
#  my $putresult = &Apache::lonnet::put
#      ('classlist',\%classlist,
#       $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
#       $ENV{'course.'.$ENV{'request.course.id'}.'.num'});

    return '';
}

sub view_edit_entire_class_form {
    my ($symb,$url)=@_;
    my ($classlist,$sections) = &getclasslist('all','0');
    my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";
    $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";
    $result.='&nbsp;<b>View/Grade Entire Section/Class</b></td></tr>'."\n";
    $result.='<tr bgcolor=#ffffe6><td>'."\n";
    $result.='<form action="/adm/grades" method="post">'."\n".
	'<input type="hidden" name="symb" value="'.$symb.'" />'."\n".
	'<input type="hidden" name="url" value="'.$url.'" />'."\n".
	'<input type="hidden" name="command" value="viewgrades" />'."\n";
    $result.='&nbsp;<b>Select section:</b> <select name="section">'."\n";
    foreach (sort (@$sections)) {
	$result.= '<option>'.$_.'</option>'."\n";
    }
    $result.= '<option selected="on">all</select>'."<br />\n";
#    $result.='&nbsp;<b>Display students who has: </b>'.
#	'<input type="radio" name="submitonly" value="yes" checked> submitted'.
#	'<input type="radio" name="submitonly" value="all"> everybody <br /><br />';
    $result.='&nbsp;<input type="submit" name="submit" value="View/Grade" /></form>'."\n";
    $result.='</td></tr></table>'."\n";
    $result.='</td></tr></table>'."\n";
    return $result;
}

sub upcsvScores_form {
    my ($symb,$url) = @_;
    if (!$symb) {return '';}
    my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";
    $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";
    $result.='&nbsp;<b>Specify a file containing the class scores for above resource</b></td></tr>'."\n";
    $result.='<tr bgcolor=#ffffe6><td>'."\n";
    my $upfile_select=&Apache::loncommon::upfile_select_html();
  $result.=<<ENDUPFORM;
<form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
<input type="hidden" name="symb" value="$symb" />
<input type="hidden" name="url" value="$url" />
<input type="hidden" name="command" value="csvuploadmap" />
$upfile_select
<br />&nbsp;<input type="submit" name="submit" value="Upload Grades" />
</form>
ENDUPFORM
    $result.='</td></tr></table>'."\n";
    $result.='</td></tr></table>'."\n";
    return $result;
}

sub viewGradeaStu_form {
    my ($symb,$url,$response,$handgrade) = @_;
    my ($classlist,$sections) = &getclasslist('all','0');
    my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";
    $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";
    $result.='&nbsp;<b>View/Grade an Individual Student\'s Submission</b></td></tr>'."\n";
    $result.='<tr bgcolor=#ffffe6><td>'."\n";
    $result.='<form action="/adm/grades" method="post">'."\n".
	'<input type="hidden" name="symb" value="'.$symb.'" />'."\n".
	'<input type="hidden" name="url" value="'.$url.'" />'."\n".
	'<input type="hidden" name="response" value="'.$response.'" />'."\n".
	'<input type="hidden" name="handgrade" value="'.$handgrade.'" />'."\n".
	'<input type="hidden" name="showgrading" value="yes" />'."\n".
	'<input type="hidden" name="command" value="submission" />'."\n";

    $result.='&nbsp;<b>Select section:</b> <select name="section">'."\n";
    foreach (sort (@$sections)) {
	$result.= '<option>'.$_.'</option>'."\n";
    }
    $result.= '<option selected="on">all</select>'."\n";
    $result.='&nbsp;&nbsp;<b>Display students who has: </b>'.
	'<input type="radio" name="submitonly" value="yes" checked> submitted'.
	'<input type="radio" name="submitonly" value="all"> everybody <br />';
    $result.='&nbsp;(Section "no" implies the students were not assigned a section.)<br />' 
	if (grep /no/,@$sections);
    
    $result.='<br />&nbsp;<input type="submit" name="submit" value="View/Grade" />'."\n".
	'</form>'."\n";
    $result.='</td></tr></table>'."\n";
    $result.='</td></tr></table>'."\n";
    return $result;
}

sub verifyReceipt_form {
    my ($symb,$url) = @_;
    my $cdom=$ENV{"course.$ENV{'request.course.id'}.domain"};
    my $cnum=$ENV{"course.$ENV{'request.course.id'}.num"};
    my $hostver=unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'});

    my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";
    $result.='<table width=100% border=0><tr><td bgcolor=#e6ffff>'."\n";
    $result.='&nbsp;<b>Verify a Submission Receipt Issued by this Server</td></tr>'."\n";
    $result.='<tr bgcolor=#ffffe6><td>'."\n";
    $result.='<form action="/adm/grades" method="post">'."\n";
    $result.='&nbsp;<tt>'.$hostver.'-<input type="text" name="receipt" size="4"></tt><br />'."\n";
    $result.='&nbsp;<input type="submit" name="submit" value="Verify Receipt">'."\n";
    $result.='<input type="hidden" name="command" value="verify">'."\n";
    if ($ENV{'form.url'}) {
	$result.='<input type="hidden" name="url" value="'.$ENV{'form.url'}.'" />';
    }
    if ($ENV{'form.symb'}) {
	$result.='<input type="hidden" name="symb" value="'.$ENV{'form.symb'}.'" />';
    }
    $result.='</form>';
    $result.='</td></tr></table>'."\n";
    $result.='</td></tr></table>'."\n";
    return $result;
}

sub viewgrades {
    my ($request) = @_;
    $request->print(<<VIEWJAVASCRIPT);
<script type="text/javascript" language="javascript">
    function viewOneStudent(user) {
	document.onestudent.student.value = user;
	document.onestudent.submit();
    }


    function writePoint(partid,weight,point) {
	for (i=0;i<document.classgrade.total.value;i++) {
	    var user = eval("document.classgrade.counter"+i+".value");
	    var scorename = eval("document.classgrade.GRADE_"+user+"_"+partid+"_awarded");
	    scorename.value = point;
	}
}
</script>
VIEWJAVASCRIPT

    my ($symb,$url) = ($ENV{'form.symb'},$ENV{'form.url'}); 
    $request->print ('<h2><font color="#339933">Manual Grading</font></h2>');

    my $result='<table border="0">';
    $result.='<tr><td colspan=3><font size=+1><b>Resource: </b>'.$ENV{'form.url'}.
	'</font></td></tr>'."\n";
    my ($partlist,$handgrade) = &response_type($ENV{'form.url'});
    my %weight = ();
    for (sort keys(%$handgrade)) {
	my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_});
	my ($partid,$respid) = split (/_/);
	my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb);
	$weight{$partid} = $wgt eq '' ? '1' : $wgt;
	$result.='<tr><td><b>Part id: </b>'.$partid.'</td>'.
	    '<td><b>Type: </b>'.$responsetype.'</td>'.
	    '<td><b>Handgrade: </b>'.$handgrade.'</font></td></tr>'."\n";
    }
    $request->print($result.'</table>'."\n");

    #view individual student submission form - called using Javascript viewOneStudent
    $result = '<form action="/adm/grades" method="post" name="onestudent">'."\n".
	'<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
	'<input type="hidden" name="url"     value="'.$url.'" />'."\n".
	'<input type="hidden" name="command" value="submission" />'."\n".
	'<input type="hidden" name="student" value="" />'."\n".
	'</form>'."\n";

    #start the form
    $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n".
	'<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
	'<input type="hidden" name="url"     value="'.$url.'" />'."\n".
	'<input type="hidden" name="command" value="editgrades" />'."\n".
	'<input type="hidden" name="section" value="'.$ENV{'form.section'}.'" />'."\n";
    $result.='To assign the same score for all the students use the radio buttons or box below. '.
	'To assign individual score fill in the score for each student in the table below.<br />';
    $result.='<table border="0">';
    for (sort keys (%weight)) {
	my $ctr = 0;
	$result.='<tr><td><b>Part</b> '.$_.'</td><td>';
	$result.='<table border="0"><tr>';  # display radio buttons in a nice table 10 across
	while ($ctr<=$weight{$_}) {
	    $result.= '<td><input type="radio" name="RADVAL_'.$_.'" '.
		'onclick="javascript:writePoint('.$_.','.$weight{$_}.
		','.$ctr.')" />'.$ctr."</td>\n";
	    $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
	    $ctr++;
	}
	$result.='</tr></table>';
    }
    $result.='</tr></table>';

    $result.= '<input type="submit" name="submit"  value="Submit Changes" />'."\n".
	'<table border=0><tr><td bgcolor="#777777">'."\n".
	'<table border=0><tr bgcolor="#deffff">'.
	'<td><b>Username</b></td><td><b>Fullname</b></td><td><b>Domain</b></td>'."\n";
    #get list of parts for this problem
    my (@parts) = sort(&getpartlist($url));
    foreach my $part (@parts) {
	my $display=&Apache::lonnet::metadata($url,$part.'.display');
	next if ($display =~ /^Number of Attempts/);
	if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }
	if ($display =~ /^Partial Credit Factor/) {
	    $_ = $display;
	    my ($partid) = /.*?(\d+).*/;
	    $result.='<td><b>Score Part '.$partid.'<br>(weight = '.$weight{$partid}.')</b></td>'."\n";
	    next;
	}
	$display =~ s/Problem Status/Grade Status/;
	$result.='<td><b>'.$display.'</b></td>'."\n";
    }
    $result.='</tr>';
    #get info for each student
    my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist($ENV{'form.section'},'0');
    my $ctr = 0;
    foreach ( sort(@{ $$classlist{$ENV{'form.section'}} }) ) {
	(my $username = $_) = split(/:/);
	$result.='<input type="hidden" name="counter'.$ctr.'" value="'.$username.'" />'."\n";
	$result.=&viewstudentgrade($url,$symb,$ENV{'request.course.id'},
				   $_,$$fullname{$_},\@parts,\%weight);
	$ctr++;
    }
    $result.='</table></td></tr></table>';
    $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";
    $result.='<input type="submit" name="submit" value="Submit Changes" /></form>';
    $result.=&show_grading_menu_form($symb,$url);
    return $result;
}

sub viewstudentgrade {
    my ($url,$symb,$courseid,$student,$fullname,$parts,$weight) = @_;
    my ($username,$domain) = split(/:/,$student);
    my %record=&Apache::lonnet::restore($symb,$courseid,$domain,$username);
    my $result='<tr bgcolor="#ffffdd"><td>'.
	'<a href="javascript:viewOneStudent(\''.$username.'\')"; TARGET=_self>'.$username.'</a>'.
	'</td><td>'.$fullname.'</td><td align="middle">'.$domain.'</td>'."\n";
    foreach my $part (@$parts) {
	my ($temp,$part,$type)=split(/_/,$part);
	my $score=$record{"resource.$part.$type"};
	next if $type eq 'tries';
	if ($type eq 'awarded') {
	    my $pts = $score*$$weight{$part};
	    $result.='<td align="middle"><input type="text" name="GRADE_'.$username.'_'.$part.'_'.$type.
		'" value="'.$pts.'" size="4" /></td>'."\n";
#	    $result.='<td align="middle"><input type="text" name="GRADE.'.$student.'.'.$part.'.'.$type.
#		'" value="'.$pts.'" size="4" /></td>'."\n";
#	} elsif ($type eq 'tries') {
#	    $result.='<td align="middle">'.$score.'&nbsp;</td>'."\n";
	} elsif ($type eq 'solved') {
	    my ($status,$foo)=split(/_/,$score,2);
	    $result.="<td align=\"middle\"><select name=\"GRADE.$student.$part.$type\">\n";
	    my $optsel = '<option>correct</option><option>incorrect</option><option>excused</option>'.
		'<option>ungraded</option><option>nothing</option>'."\n";
	    $status = 'nothing' if ($status eq '');
	    $optsel =~ s/<option>$status/<option selected="on">$status/;
	    $result.=$optsel;
	    $result.="</select></td>\n";
	}
    }
    $result.='</tr>';
    return $result;
}


sub editgrades {
    my ($request) = @_;

    my $symb=$ENV{'form.symb'};
    if ($symb eq '') { $request->print("Unable to handle ambiguous references:$symb:$ENV{'form.url'}"); return ''; }
    my $url=$ENV{'form.url'};

    my $result.='<form action="/adm/grades" method="post">'."\n".
	'<input type="hidden" name="symb" value="'.$symb.'" />'."\n".
	'<input type="hidden" name="url" value="'.$url.'" />'."\n".
	'<input type="hidden" name="command" value="viewgrades" />'."\n".
	'<input type="submit" name="submit" value="See Grades" /> <br />'."\n";
    
    my (@parts) = &getpartlist($url);
    my ($classlist) = &getclasslist($ENV{'form.section'},'0');
    foreach my $student ( sort(@{ $$classlist{$ENV{'form.section'}} }) ) {
	$result.=&setstudentgrade($url,$symb,$ENV{'request.course.id'},$student,@parts);
    }

    $result.='<input type="submit" name="submit" value="See Grades" /></table></form>';
    return $result;
}

sub sub_page_js {
    my $request = shift;
    $request->print(<<SUBJAVASCRIPT);
<script type="text/javascript" language="javascript">
  function updateRadio(radioButton,formtextbox,formsel,scores) {
     var pts = formtextbox.value;
     var resetbox =false;
     if (isNaN(pts) || pts < 0) {
	alert("A number equal or greater than 0 is expected. Entered value = "+pts);
	for (var i=0; i<radioButton.length; i++) {
	   if (radioButton[i].checked) {
	      formtextbox.value = i;
	      resetbox = true;
	   }
	}
	if (!resetbox) {
	   formtextbox.value = "";
	}
	return;
    }

    for (var i=0; i<radioButton.length; i++) {
	radioButton[i].checked=false;
	if (pts == i) {
	   radioButton[i].checked=true;
	}
    }
    updateSelect(formsel);
    scores.value = "0";
  }

  function writeBox(formrad,formsel,pts,scores) {
    formrad.value = pts;
    scores.value = "0";
    updateSelect(formsel,pts);
    return;
  }

  function clearRadBox(radioButton,formbox,formsel,scores) {
    for (var i=0; i<formsel.length; i++) {
	if (formsel[i].selected) {
	    var selectx=i;
	}
    }
    if (selectx == scores.value) { return };
    formbox.value = "";
    for (var i=0; i<radioButton.length; i++) {
	radioButton[i].checked=false;
    }
    scores.value = selectx;
  }

  function updateSelect(formsel) {
    formsel[0].selected = true;
    return;
  }

//===================== Show list of keywords ====================
  function keywords(keyform) {
    var keywds = keyform.value;
    var nret = prompt("Keywords list, separated by a space. Add/delete to list if desired.",keywds);
    if (nret==null) return;
    keyform.value = nret;
    return;
  }

//===================== Script to view submitted by ==================
  function viewSubmitter(submitter) {
    document.SCORE.refresh.value = "on";
    document.SCORE.NCT.value = "1";
    document.SCORE.unamedom0.value = submitter;
    document.SCORE.submit();
    return;
  }

//===================== Script to add keyword(s) ==================
  function getSel() {
    if (document.getSelection) txt = document.getSelection();
    else if (document.selection) txt = document.selection.createRange().text;
    else return;
    var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");
    if (cleantxt=="") {
	alert("Select a word or group of words from document and then click this link.");
	return;
    }
    var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);
    if (nret==null) return;
    var curlist = document.SCORE.keywords.value;
    document.SCORE.keywords.value = curlist+" "+nret;
    return;
  }

//====================== Script for composing message ==============
  function msgCenter(msgform,usrctr,fullname) {
    var Nmsg  = msgform.savemsgN.value;
    savedMsgHeader(Nmsg,usrctr,fullname);
    var subject = msgform.msgsub.value;
    var rtrchk  = eval("document.SCORE.includemsg"+usrctr);
    var msgchk = rtrchk.value;
//    alert("checked=>"+msgchk);
    re = /msgsub/;
    var shwsel = "";
    if (re.test(msgchk)) { shwsel = "checked" }
    displaySubject(subject,shwsel);
    for (var i=1; i<=Nmsg; i++) {
	var testpt = "savemsg"+i+",";
	re = /testpt/;
	shwsel = "";
	if (re.test(msgchk)) { shwsel = "checked" }
	var message = eval("document.SCORE.savemsg"+i+".value");
	displaySavedMsg(i,message,shwsel);
    }
    newmsg = eval("document.SCORE.newmsg"+usrctr+".value");
    shwsel = "";
    re = /newmsg/;
    if (re.test(msgchk)) { shwsel = "checked" }
    newMsg(newmsg,shwsel);
    msgTail(); 
    return;
  }

  function savedMsgHeader(Nmsg,usrctr,fullname) {
    var height = 30*Nmsg+250;
    var scrollbar = "no";
    if (height > 600) {
	height = 600;
	scrollbar = "yes";
    }
/*    if (window.pWin)
	window.pWin.close(); */
    pWin = window.open('', 'MessageCenter', 'toolbar=no,location=no,scrollbars='+scrollbar+',screenx=70,screeny=75,width=600,height='+height);
    pWin.document.write("<html><head>");
    pWin.document.write("<title>Message Central</title>");

    pWin.document.write("<script language=javascript>");
    pWin.document.write("function checkInput() {");
    pWin.document.write("  opener.document.SCORE.msgsub.value = document.msgcenter.msgsub.value;");
    pWin.document.write("  var nmsg   = opener.document.SCORE.savemsgN.value;");
    pWin.document.write("  var usrctr = document.msgcenter.usrctr.value;");
    pWin.document.write("  var newval = eval(\\"opener.document.SCORE.newmsg\\"+usrctr);");
    pWin.document.write("  newval.value = document.msgcenter.newmsg.value;");

    pWin.document.write("  var msgchk = \\"\\";");
    pWin.document.write("  if (document.msgcenter.subchk.checked) {");
    pWin.document.write("     msgchk = \\"msgsub,\\";");
    pWin.document.write("  }");
    pWin.document.write(   "for (var i=1; i<=nmsg; i++) {");
    pWin.document.write("      var opnmsg = eval(\\"opener.document.SCORE.savemsg\\"+i);");
    pWin.document.write("      var frmmsg = eval(\\"document.msgcenter.msg\\"+i);");
    pWin.document.write("      opnmsg.value = frmmsg.value;");
    pWin.document.write("      var chkbox = eval(\\"document.msgcenter.msgn\\"+i);");
    pWin.document.write("      if (chkbox.checked) {");
    pWin.document.write("         msgchk += \\"savemsg\\"+i+\\",\\";");
    pWin.document.write("      }");
    pWin.document.write("  }");
    pWin.document.write("  if (document.msgcenter.newmsgchk.checked) {");
    pWin.document.write("     msgchk += \\"newmsg\\"+usrctr;");
    pWin.document.write("  }");
    pWin.document.write("  var includemsg = eval(\\"opener.document.SCORE.includemsg\\"+usrctr);");
    pWin.document.write("  includemsg.value = msgchk;");

//    pWin.document.write("  alert(\\"slected=\\"+msgchk)");
    pWin.document.write("  self.close()");

    pWin.document.write("}");

    pWin.document.write("<");
    pWin.document.write("/script>");

    pWin.document.write("</head><body bgcolor=white>");

    pWin.document.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");
    pWin.document.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");
    pWin.document.write("<font color=\\"green\\" size=+1>&nbsp;Compose Message for \"+fullname+\"</font><br><br>");

    pWin.document.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
    pWin.document.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
    pWin.document.write("<td><b>Type</b></td><td><b>Include</b></td><td><b>Message</td></tr>");
}
    function displaySubject(msg,shwsel) {
    pWin.document.write("<tr bgcolor=\\"#ffffdd\\">");
    pWin.document.write("<td>Subject</td>");
    pWin.document.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"></td>");
    pWin.document.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+" \\"size=\\"60\\" maxlength=\\"80\\"></td></tr>");
}

function displaySavedMsg(ctr,msg,shwsel) {
    pWin.document.write("<tr bgcolor=\\"#ffffdd\\">");
    pWin.document.write("<td align=\\"center\\">"+ctr+"</td>");
    pWin.document.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"></td>");
    pWin.document.write("<td><input name=\\"msg"+ctr+"\\" type=\\"text\\" value=\\""+msg+" \\" size=\\"60\\" maxlength=\\"80\\"></td></tr>");
}

  function newMsg(newmsg,shwsel) {
    pWin.document.write("<tr bgcolor=\\"#ffffdd\\">");
    pWin.document.write("<td align=\\"center\\">New</td>");
    pWin.document.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"></td>");
    pWin.document.write("<td><input name=\\"newmsg\\" type=\\"text\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" value=\\""+newmsg+" \\" size=\\"60\\" maxlength=\\"80\\"></td></tr>");
}

  function msgTail() {
    pWin.document.write("</table>");
    pWin.document.write("</td></tr></table>&nbsp;");
    pWin.document.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");
    pWin.document.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br><br>");
    pWin.document.write("</form>");
    pWin.document.write("</body></html>");
}

//====================== Script for keyword highlight options ==============
  function kwhighlight() {
    var kwclr    = document.SCORE.kwclr.value;
    var kwsize   = document.SCORE.kwsize.value;
    var kwstyle  = document.SCORE.kwstyle.value;
    var redsel = "";
    var grnsel = "";
    var blusel = "";
    if (kwclr=="red")   {var redsel="checked"};
    if (kwclr=="green") {var grnsel="checked"};
    if (kwclr=="blue")  {var blusel="checked"};
    var sznsel = "";
    var sz1sel = "";
    var sz2sel = "";
    if (kwsize=="0")  {var sznsel="checked"};
    if (kwsize=="+1") {var sz1sel="checked"};
    if (kwsize=="+2") {var sz2sel="checked"};
    var synsel = "";
    var syisel = "";
    var sybsel = "";
    if (kwstyle=="")    {var synsel="checked"};
    if (kwstyle=="<i>") {var syisel="checked"};
    if (kwstyle=="<b>") {var sybsel="checked"};
    highlightCentral();
    highlightbody('red','red',redsel,'0','normal',sznsel,'','normal',synsel);
    highlightbody('green','green',grnsel,'+1','+1',sz1sel,'<i>','italic',syisel);
    highlightbody('blue','blue',blusel,'+2','+2',sz2sel,'<b>','bold',sybsel);
    highlightend();
    return;
  }


  function highlightCentral() {
    hwdWin = window.open('', 'KeywordHighlightCentral', 'toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx=100,screeny=75');
    hwdWin.document.write("<html><head>");
    hwdWin.document.write("<title>Highlight Central</title>");

    hwdWin.document.write("<script language=javascript>");
    hwdWin.document.write("function updateChoice(flag) {");
    hwdWin.document.write("  opener.document.SCORE.kwclr.value = radioSelection(document.hlCenter.kwdclr);");
    hwdWin.document.write("  opener.document.SCORE.kwsize.value = radioSelection(document.hlCenter.kwdsize);");
    hwdWin.document.write("  opener.document.SCORE.kwstyle.value = radioSelection(document.hlCenter.kwdstyle);");
//    hwdWin.document.write("     var kwords=opener.document.SCORE.keywords.value;");
//    hwdWin.document.write("     alert(\\"keywords=\\"+opener.document.SCORE.keywords.value);");
//    hwdWin.document.write("     return;");

    hwdWin.document.write("  if (flag==1){");
    hwdWin.document.write("     opener.document.SCORE.refresh.value = \\"on\\";");
    hwdWin.document.write("     if (opener.document.SCORE.keywords.value!=\\"\\"){");
    hwdWin.document.write("        opener.document.SCORE.submit();");
    hwdWin.document.write("     }");
    hwdWin.document.write("   }");
    hwdWin.document.write("  self.close()");
    hwdWin.document.write("}");

    hwdWin.document.write("function radioSelection(radioButton) {");
    hwdWin.document.write("    var selection=null;");
    hwdWin.document.write("    for (var i=0; i<radioButton.length; i++) {");
    hwdWin.document.write("        if (radioButton[i].checked) {");
    hwdWin.document.write("            selection=radioButton[i].value;");
    hwdWin.document.write("            return selection;");
    hwdWin.document.write("        }");
    hwdWin.document.write("    }");
    hwdWin.document.write("}");

    hwdWin.document.write("<");
    hwdWin.document.write("/script>");

    hwdWin.document.write("</head><body bgcolor=white>");

    hwdWin.document.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");
    hwdWin.document.write("<font color=\\"green\\" size=+1>&nbsp;Keyword Highlight Options</font><br><br>");

    hwdWin.document.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
    hwdWin.document.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
    hwdWin.document.write("<td><b>Text Color</b></td><td><b>Font Size</b></td><td><b>Font Style</td></tr>");
  }

  function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) { 
    hwdWin.document.write("<tr bgcolor=\\"#ffffdd\\">");
    hwdWin.document.write("<td align=\\"left\\">");
    hwdWin.document.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+">&nbsp;"+clrtxt+"</td>");
    hwdWin.document.write("<td align=\\"left\\">");
    hwdWin.document.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+">&nbsp;"+sztxt+"</td>");
    hwdWin.document.write("<td align=\\"left\\">");
    hwdWin.document.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+">&nbsp;"+sytxt+"</td>");
    hwdWin.document.write("</tr>");
  }

  function highlightend() { 
    hwdWin.document.write("</table>");
    hwdWin.document.write("</td></tr></table>&nbsp;");
    hwdWin.document.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:updateChoice(0)\\">&nbsp;&nbsp;");
    hwdWin.document.write("<input type=\\"button\\" value=\\"Save & Refresh\\" onClick=\\"javascript:updateChoice(1)\\">&nbsp;&nbsp;");
    hwdWin.document.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br><br>");
    hwdWin.document.write("</form>");
    hwdWin.document.write("</body></html>");
  }

</script>
SUBJAVASCRIPT
}

sub csvupload_javascript_reverse_associate {
  return(<<ENDPICK);
  function verify(vf) {
    var foundsomething=0;
    var founduname=0;
    var founddomain=0;
    for (i=0;i<=vf.nfields.value;i++) {
      tw=eval('vf.f'+i+'.selectedIndex');
      if (i==0 && tw!=0) { founduname=1; }
      if (i==1 && tw!=0) { founddomain=1; }
      if (i!=0 && i!=1 && tw!=0) { foundsomething=1; }
    }
    if (founduname==0 || founddomain==0) {
      alert('You need to specify at both the username and domain');
      return;
    }
    if (foundsomething==0) {
      alert('You need to specify at least one grading field');
      return;
    }
    vf.submit();
  }
  function flip(vf,tf) {
    var nw=eval('vf.f'+tf+'.selectedIndex');
    var i;
    for (i=0;i<=vf.nfields.value;i++) {
      //can not pick the same destination field for both name and domain
      if (((i ==0)||(i ==1)) && 
          ((tf==0)||(tf==1)) && 
          (i!=tf) &&
          (eval('vf.f'+i+'.selectedIndex')==nw)) {
        eval('vf.f'+i+'.selectedIndex=0;')
      }
    }
  }
ENDPICK
}

sub csvupload_javascript_forward_associate {
  return(<<ENDPICK);
  function verify(vf) {
    var foundsomething=0;
    var founduname=0;
    var founddomain=0;
    for (i=0;i<=vf.nfields.value;i++) {
      tw=eval('vf.f'+i+'.selectedIndex');
      if (tw==1) { founduname=1; }
      if (tw==2) { founddomain=1; }
      if (tw>2) { foundsomething=1; }
    }
    if (founduname==0 || founddomain==0) {
      alert('You need to specify at both the username and domain');
      return;
    }
    if (foundsomething==0) {
      alert('You need to specify at least one grading field');
      return;
    }
    vf.submit();
  }
  function flip(vf,tf) {
    var nw=eval('vf.f'+tf+'.selectedIndex');
    var i;
    //can not pick the same destination field twice
    for (i=0;i<=vf.nfields.value;i++) {
      if ((i!=tf) && (eval('vf.f'+i+'.selectedIndex')==nw)) {
        eval('vf.f'+i+'.selectedIndex=0;')
      }
    }
  }
ENDPICK
}

sub csvuploadmap_header {
    my ($request,$symb,$url,$datatoken,$distotal)= @_;
    my $result;
    my $javascript;
    if ($ENV{'form.upfile_associate'} eq 'reverse') {
	$javascript=&csvupload_javascript_reverse_associate();
    } else {
	$javascript=&csvupload_javascript_forward_associate();
    }
    $request->print(<<ENDPICK);
<form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
<h3>Uploading Class Grades for resource $url</h3>
<hr>
<h3>Identify fields</h3>
Total number of records found in file: $distotal <hr />
Enter as many fields as you can. The system will inform you and bring you back
to this page if the data selected is insufficient to run your class.<hr />
<input type="button" value="Reverse Association" onClick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" />
<input type="hidden" name="associate"  value="" />
<input type="hidden" name="phase"      value="three" />
<input type="hidden" name="datatoken"  value="$datatoken" />
<input type="hidden" name="fileupload" value="$ENV{'form.fileupload'}" />
<input type="hidden" name="upfiletype" value="$ENV{'form.upfiletype'}" />
<input type="hidden" name="upfile_associate" 
                                       value="$ENV{'form.upfile_associate'}" />
<input type="hidden" name="symb"       value="$symb" />
<input type="hidden" name="url"        value="$url" />
<input type="hidden" name="command"    value="csvuploadassign" />
<hr />
<script type="text/javascript" language="Javascript">
$javascript
</script>
ENDPICK
return '';

}

sub csvupload_fields {
    my ($url) = @_;
    my (@parts) = &getpartlist($url);
    my @fields=(['username','Student Username'],['domain','Student Domain']);
    foreach my $part (sort(@parts)) {
	my @datum;
	my $display=&Apache::lonnet::metadata($url,$part.'.display');
	my $name=$part;
	if  (!$display) { $display = $name; }
	@datum=($name,$display);
	push(@fields,\@datum);
    }
    return (@fields);
}

sub csvuploadmap_footer {
    my ($request,$i,$keyfields) =@_;
    $request->print(<<ENDPICK);
</table>
<input type="hidden" name="nfields" value="$i" />
<input type="hidden" name="keyfields" value="$keyfields" />
<input type="button" onClick="javascript:verify(this.form)" value="Assign Grades" /><br />
</form>
ENDPICK
}

sub csvuploadmap {
    my ($request)= @_;
    my ($symb,$url)=&get_symb_and_url($request);
    if (!$symb) {return '';}
    my $datatoken;
    if (!$ENV{'form.datatoken'}) {
	$datatoken=&Apache::loncommon::upfile_store($request);
    } else {
	$datatoken=$ENV{'form.datatoken'};
	&Apache::loncommon::load_tmp_file($request);
    }
    my @records=&Apache::loncommon::upfile_record_sep();
    &csvuploadmap_header($request,$symb,$url,$datatoken,$#records+1);
    my ($i,$keyfields);
    if (@records) {
	my @fields=&csvupload_fields($url);
	if ($ENV{'form.upfile_associate'} eq 'reverse') {	
	    &Apache::loncommon::csv_print_samples($request,\@records);
	    $i=&Apache::loncommon::csv_print_select_table($request,\@records,
							  \@fields);
	    foreach (@fields) { $keyfields.=$_->[0].','; }
	    chop($keyfields);
	} else {
	    unshift(@fields,['none','']);
	    $i=&Apache::loncommon::csv_samples_select_table($request,\@records,
							    \@fields);
	    my %sone=&Apache::loncommon::record_sep($records[0]);
	    $keyfields=join(',',sort(keys(%sone)));
	}
    }
    &csvuploadmap_footer($request,$i,$keyfields);
    return '';
}

sub csvuploadassign {
    my ($request)= @_;
    my ($symb,$url)=&get_symb_and_url($request);
    if (!$symb) {return '';}
    &Apache::loncommon::load_tmp_file($request);
    my @gradedata=&Apache::loncommon::upfile_record_sep();
    my @keyfields = split(/\,/,$ENV{'form.keyfields'});
    my %fields=();
    for (my $i=0; $i<=$ENV{'form.nfields'}; $i++) {
	if ($ENV{'form.upfile_associate'} eq 'reverse') {
	    if ($ENV{'form.f'.$i} ne 'none') {
		$fields{$keyfields[$i]}=$ENV{'form.f'.$i};
	    }
	} else {
	    if ($ENV{'form.f'.$i} ne 'none') {
		$fields{$ENV{'form.f'.$i}}=$keyfields[$i];
	    }
	}
    }
    $request->print('<h3>Assigning Grades</h3>');
    my $courseid=$ENV{'request.course.id'};
#  my $cdom=$ENV{"course.$courseid.domain"};
#  my $cnum=$ENV{"course.$courseid.num"};
    my ($classlist) = &getclasslist('all','1');
    my @skipped;
    my $countdone=0;
    foreach my $grade (@gradedata) {
	my %entries=&Apache::loncommon::record_sep($grade);
	my $username=$entries{$fields{'username'}};
	my $domain=$entries{$fields{'domain'}};
	if (!exists($$classlist{"$username:$domain"})) {
	    push(@skipped,"$username:$domain");
	    next;
	}
	my %grades;
	foreach my $dest (keys(%fields)) {
	    if ($dest eq 'username' || $dest eq 'domain') { next; }
	    if ($entries{$fields{$dest}} eq '') { next; }
	    my $store_key=$dest;
	    $store_key=~s/^stores/resource/;
	    $store_key=~s/_/\./g;
	    $grades{$store_key}=$entries{$fields{$dest}};
	}
	$grades{"resource.regrader"}="$ENV{'user.name'}:$ENV{'user.domain'}";
	&Apache::lonnet::cstore(\%grades,$symb,$ENV{'request.course.id'},
				$domain,$username);
	$request->print('.');
	$request->rflush();
	$countdone++;
    }
    $request->print("<br />Stored $countdone students\n");
    if (@skipped) {
	$request->print('<br /><font size="+1"><b>Skipped Students</b></font><br />');
	foreach my $student (@skipped) { $request->print("<br />$student"); }
    }
    $request->print(&view_edit_entire_class_form($symb,$url));
    $request->print(&show_grading_menu_form($symb,$url));
    return '';
}

sub send_header {
    my ($request)= @_;
    $request->print(&Apache::lontexconvert::header());
#  $request->print("
#<script>
#remotewindow=open('','homeworkremote');
#remotewindow.close();
#</script>"); 
    $request->print('<body bgcolor="#FFFFFF">');
}

sub send_footer {
    my ($request)= @_;
    $request->print('</body>');
    $request->print(&Apache::lontexconvert::footer());
}

sub handler {
    my $request=$_[0];
    
    if ($ENV{'browser.mathml'}) {
	$request->content_type('text/xml');
    } else {
	$request->content_type('text/html');
    }
    $request->send_http_header;
    return OK if $request->header_only;
    &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});
    my $url=$ENV{'form.url'};
    my $symb=$ENV{'form.symb'};
    my $command=$ENV{'form.command'};
    if (!$url) {
	my ($temp1,$temp2);
	($temp1,$temp2,$ENV{'form.url'})=split(/___/,$symb);
	$url = $ENV{'form.url'};
    }
    &send_header($request);
    if ($url eq '' && $symb eq '') {
	if ($ENV{'user.adv'}) {
	    if (($ENV{'form.codeone'}) && ($ENV{'form.codetwo'}) &&
		($ENV{'form.codethree'})) {
		my $token=$ENV{'form.codeone'}.'*'.$ENV{'form.codetwo'}.'*'.
		    $ENV{'form.codethree'};
		my ($tsymb,$tuname,$tudom,$tcrsid)=
		    &Apache::lonnet::checkin($token);
		if ($tsymb) {
		    my ($map,$id,$url)=split(/\_\_\_/,$tsymb);
		    if (&Apache::lonnet::allowed('mgr',$tcrsid)) {
			$request->print(
					&Apache::lonnet::ssi('/res/'.$url,
							     ('grade_username' => $tuname,
							      'grade_domain' => $tudom,
							      'grade_courseid' => $tcrsid,
							      'grade_symb' => $tsymb)));
		    } else {
			$request->print('<h1>Not authorized: '.$token.'</h1>');
		    }           
		} else {
		    $request->print('<h1>Not a valid DocID: '.$token.'</h1>');
		}
	    } else {
		$request->print(&Apache::lonxml::tokeninputfield());
	    }
	}
    } else {
	#&Apache::lonhomework::showhashsubset(\%ENV,'^form');
	$Apache::grades::viewgrades=&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'});
	if ($command eq 'submission') {
	    &listStudents($request) if ($ENV{'form.student'} eq '');
	    &submission($request,0,0) if ($ENV{'form.student'} ne '');
	} elsif ($command eq 'processGroup') {
	    &processGroup($request);
	} elsif ($command eq 'gradingmenu') {
	    $request->print(&gradingmenu($request));
	} elsif ($command eq 'viewgrades') {
	    $request->print(&viewgrades($request));
	} elsif ($command eq 'handgrade') {
	    $request->print(&processHandGrade($request));
	} elsif ($command eq 'editgrades') {
	    $request->print(&editgrades($request));
	} elsif ($command eq 'verify') {
	    $request->print(&verifyreceipt($request));
	} elsif ($command eq 'csvupload') {
	    $request->print(&csvupload($request));
	} elsif ($command eq 'viewclasslist') {
	    $request->print(&viewclasslist($request));
	} elsif ($command eq 'csvuploadmap') {
	    $request->print(&csvuploadmap($request));
#    } elsif ($command eq 'receiptInput') {
#      &receiptInput($request);
	} elsif ($command eq 'csvuploadassign') {
	    if ($ENV{'form.associate'} ne 'Reverse Association') {
		$request->print(&csvuploadassign($request));
	    } else {
		if ( $ENV{'form.upfile_associate'} ne 'reverse' ) {
		    $ENV{'form.upfile_associate'} = 'reverse';
		} else {
		    $ENV{'form.upfile_associate'} = 'forward';
		}
		$request->print(&csvuploadmap($request));
	    }
	} else {
	    $request->print("Unknown action: $command:");
	}
    }
    &send_footer($request);
    return OK;
}

1;

__END__;

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