# The LearningOnline Network with CAPA
# The LON-CAPA Grading handler
#
# $Id: grades.pm,v 1.46.2.1 2002/09/06 21:01:09 albertel 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-August 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);
# ----- These first few routines are general use routines.-----
#
# --- Retrieve the parts that matches stores_\d+ from the metadata file.---
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;
}
# --- Get the symbolic name of a problem and the url
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);
}
# --- Retrieve the fullname for a user. Return lastname, first middle ---
# --- Generation is attached next to the lastname if it exists. ---
sub get_fullname {
my ($uname,$udom) = @_;
my %name=&Apache::lonnet::get('environment', ['lastname','generation',
'firstname','middlename'],$udom,$uname);
my $fullname;
my ($tmp) = keys(%name);
if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
$fullname=$name{'lastname'}.$name{'generation'};
if ($fullname =~ /[^\s]+/) { $fullname.=', '; }
$fullname.=$name{'firstname'}.' '.$name{'middlename'};
}
return $fullname;
}
#--- Get the partlist and the response type for a given problem. ---
#--- Indicate if a response type is coded handgraded or not. ---
sub response_type {
my ($url) = shift;
my $allkeys = &Apache::lonnet::metadata($url,'keys');
my %seen = ();
my (@partlist,%handgrade);
foreach (split(/,/,&Apache::lonnet::metadata($url,'packages'))) {
if (/^\w+response_\d+.*/) {
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;
}
#--- Dumps the class list with usernames,list of sections,
#--- section, ids and fullnames for each user.
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'});
# codes to check for fields in the classlist
# should contain end:start:id:section:fullname
for (keys %classlist) {
my (@fields) = split(/:/,$classlist{$_});
%classlist = &reformat_classlist(\%classlist) if (scalar(@fields) <= 2);
last;
}
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);
}
# add id, section and fullname to the classlist.db
# done to maintain backward compatibility with older versions
sub reformat_classlist {
my ($classlist) = shift;
foreach (sort keys(%$classlist)) {
my ($unam,$udom) = split(/:/);
my $section = &Apache::lonnet::usection($udom,$unam,$ENV{'request.course.id'});
my $fullname = &get_fullname ($unam,$udom);
my %userid = &Apache::lonnet::idrget($udom,($unam));
$$classlist{$_} = $$classlist{$_}.':'.$userid{$unam}.':'.$section.':'.$fullname;
}
my $putresult = &Apache::lonnet::put
('classlist',\%$classlist,
$ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
$ENV{'course.'.$ENV{'request.course.id'}.'.num'});
return %$classlist;
}
#find user domain
sub finduser {
my ($name) = @_;
my $domain = '';
if ( $Apache::grades::viewgrades eq 'F' ) {
my %classlist=&Apache::lonnet::dump('classlist',
$ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
$ENV{'course.'.$ENV{'request.course.id'}.'.num'});
my (@fields) = grep /^$name:/, keys %classlist;
($name, $domain) = split(/:/,$fields[0]);
return ($name,$domain);
} else {
return ($ENV{'user.name'},$ENV{'user.domain'});
}
}
#--- Prompts a user to enter a username.
sub moreinfo {
my ($request,$reason) = @_;
$request->print("Unable to process request: $reason");
if ( $Apache::grades::viewgrades eq 'F' ) {
$request->print('
');
}
return '';
}
#--- Retrieve the grade status of a student for all the parts
sub student_gradeStatus {
my ($url,$symb,$udom,$uname,$partlist) = @_;
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;
my $subkey = "resource.$_.submitted_by";
$partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
}
return %partstatus;
}
# hidden form and javascript that calls the form
# Use by verifyscript and viewgrades
# Shows a student's view of problem and submission
sub jscriptNform {
my ($url,$symb) = @_;
my $jscript=''."\n";
$jscript.= ''."\n";
return $jscript;
}
#------------------ End of general use routines --------------------
#-------------------------------------------------------------------
#------------------------------------ Receipt Verification Routines
#
#--- Check whether a receipt number is valid.---
sub verifyreceipt {
my $request = shift;
my $courseid = $ENV{'request.course.id'};
my $receipt = unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'}).'-'.
$ENV{'form.receipt'};
$receipt =~ s/[^\-\d]//g;
my $url = $ENV{'form.url'};
my $symb = $ENV{'form.symb'};
unless ($symb) {
$symb = &Apache::lonnet::symbread($url);
}
my $title.='
Verifying Submission Receipt '.
$receipt.'
'."\n".
'Resource: '.$ENV{'form.url'}.'
'."\n";
my ($string,$contents,$matches) = ('','',0);
my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist('all','0');
foreach (sort {$$fullname{$a} cmp $$fullname{$b} } keys %$fullname) {
my ($uname,$udom)=split(/\:/);
if ($receipt eq
&Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb)) {
$contents.='
'."\n";
$matches++;
}
}
if ($matches == 0) {
$string = $title.'No match found for the above receipt.';
} else {
$string = &jscriptNform($url,$symb).$title.
'The above receipt matches the following student'.
($matches <= 1 ? '.' : 's.')."\n".
'
'."\n".
'
'."\n".
'
Fullname
'."\n".
'
Username
'."\n".
'
Domain
'."\n".
$contents.
'
'."\n";
}
return $string.&show_grading_menu_form ($symb,$url);
}
#--- This is called by a number of programs.
#--- Called from the Grading Menu - View/Grade an individual student
#--- Also called directly when one clicks on the subm button
# on the problem page.
sub listStudents {
my ($request) = shift;
$request->print(<
function checkSelect(checkBox) {
var ctr=0;
var sense="";
if (checkBox.length > 1) {
for (var i=0; i
LISTJAVASCRIPT
my ($symb,$url) = &get_symb_and_url();
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='
'.
'View/Grade Submissions for a Student or a Group of Students
';
$result.='
';
$result.='
'.
'Resource: '.$url.'
';
my ($partlist,$handgrade) = &response_type($url);
for (sort keys(%$handgrade)) {
my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_});
$ENV{'form.handgrade'} = 'yes' if ($handgrade eq 'yes');
$result.='
Part '.(split(/_/))[0].'
'.
'
Type: '.$responsetype.'
'.
'
Handgrade: '.$handgrade.'
';
}
$result.='
';
$request->print($result);
my $checkhdgrade = $ENV{'form.handgrade'} eq 'yes' ? 'checked' : '';
my $checklastsub = $ENV{'form.handgrade'} eq 'yes' ? '' : 'checked';
my $gradeTable=''."\n";
if ($ctr == 0) {
$gradeTable=' '.
'No submission found for this resource. ';
$gradeTable.=&show_grading_menu_form($symb,$url);
} elsif ($ctr == 1) {
$gradeTable =~ s/type=checkbox/type=checkbox checked/;
}
$request->print($gradeTable);
return '';
}
#---- Called from the listStudents routine
# Displays the submissions for one student or a group of students
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;
foreach (@stuchecked) {
my ($uname,$udom,$fullname) = split(/:/);
$ENV{'form.student'} = $uname;
$ENV{'form.userdom'} = $udom;
$ENV{'form.fullname'} = $fullname;
&submission($request,$ctr,$total);
$ctr++;
}
return '';
}
#------------------------------------------------------------------------------------
#
#-------------------------- Next few routines handles grading by student, essentially
# handles essay response type problem/part
#
#--- Javascript to handle the submission page functionality ---
sub sub_page_js {
my $request = shift;
$request->print(<
function updateRadio(radioButton,formtextbox,formsel,scores,weight) {
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 weight) {
var resp = confirm("You entered a value ("+pts+
") greater than the weight for the part. Accept?");
if (resp == false) {
formtextbox.value = "";
return;
}
}
for (var i=0; i 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("");
pWin.document.write("Message Central");
pWin.document.write("
SUBJAVASCRIPT
}
# --------------------------- 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) = ($ENV{'form.student'},$ENV{'form.userdom'});
($uname,$udom) = &finduser($uname) if $udom eq '';
$ENV{'form.fullname'} = &get_fullname ($uname,$udom) if $ENV{'form.fullname'} eq '';
# 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('
Submission Record
'."\n".
' Resource: '.$url.''."\n");
# 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.='
';
$result.='
';
$result.=' View of the problem - '.$ENV{'form.fullname'}.
'
';
$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(''."\n");
return;
}
# Grading options
$result=''."\n".
''."\n".
''."\n";
my ($lastname,$givenn) = split(/,/,$ENV{'form.fullname'});
my $msgfor = $givenn.' '.$lastname;
if (scalar(@col_fullnames) > 0) {
my $lastone = pop @col_fullnames;
$msgfor .= ', '.(join ', ',@col_fullnames).' and '.$lastone.'.';
}
$result.='
'."\n".
' '.
'Compose Message to student'.(scalar(@col_fullnames) >= 1 ? 's' : '').''.
' (Message will be sent when you click on Save & Next below.)'."\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)' :
'problem weight assigned by computer');
$wgt = ($wgt > 0 ? $wgt : '1');
my $score = ($record{'resource.'.$partid.'.awarded'} eq '' ?
'' : $record{'resource.'.$partid.'.awarded'}*$wgt);
$result='';
$result.='
Part '.$partid.' Points:
';
my $ctr = 0;
$result.='
'; # display radio buttons in a nice table 10 across
while ($ctr<=$wgt) {
$result.= '
'."\n";
$request->print($result);
}
$result=''."\n";
my $ctr = 0;
while ($ctr < scalar(@partlist)) {
$result.=''."\n";
$ctr++;
}
$request->print($result.'
'."\n");
# print end of form
if ($counter == $total) {
my $endform='
'.
''."\n";
if ($ENV{'form.handgrade'} eq 'yes') {
$endform.=' '."\n";
my $ntstu =''."\n";
my $nsel = ($ENV{'form.NTSTU'} ne '' ? $ENV{'form.NTSTU'} : '1');
$ntstu =~ s/
';
$request->print($endform);
}
return '';
}
#--- Retrieve the last submission for all the parts
sub get_last_submission {
my (%returnhash)=@_;
my (@string,$timestamp);
if ($returnhash{'version'}) {
my %lasthash=();
my ($version);
for ($version=1;$version<=$returnhash{'version'};$version++) {
foreach (sort(split(/\:/,$returnhash{$version.':keys'}))) {
$lasthash{$_}=$returnhash{$version.':'.$_};
if ($returnhash{$version.':'.$_} =~ /(SUBMITTED|DRAFT)$/) {
$timestamp = scalar(localtime($returnhash{$version.':timestamp'}));
}
}
}
foreach ((keys %lasthash)) {
if ($_ =~ /\.submission$/) {
my ($partid,$foo) = split(/submission$/,$_);
my $draft = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ?
'Draft Copy ' : '';
push @string, (join(':',$_,$draft.$lasthash{$_}));
}
}
}
@string = $string[0] eq '' ? 'Nothing submitted - no attempts.' : @string;
return \@string,\$timestamp;
}
#--- High light keywords, with style choosen by user.
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|\.)/\$styleon$_$styleoff\<\/font\>/gi;
}
return $string;
}
#--- Called from submission routine
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'};
if ($button eq 'Save & Next') {
my $ctr = 0;
while ($ctr < $ngrade) {
my ($uname,$udom) = split(/:/,$ENV{'form.unamedom'.$ctr});
my ($errorflag) = &saveHandGrade($request,$url,$symb,$uname,$udom,$ctr);
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++;
}
}
# Keywords sorted in alphabatical order
my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'};
my %keyhash = ();
$ENV{'form.keywords'} =~ s/,\s{0,}|\s+/ /g;
$ENV{'form.keywords'} =~ s/^\s+|\s+$//;
my (@keywords) = sort(split(/\s+/,$ENV{'form.keywords'}));
$ENV{'form.keywords'} = join(' ',@keywords);
$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'};
# message center - Order of message gets changed. Blank line is eliminated.
# New messages are saved in ENV for the next student.
# All messages are saved in nohist_handgrade.db
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'});
# Called by Save & Refresh from Highlight Attribute Window
if ($ENV{'form.refresh'} eq 'on') {
my $ctr = 0;
$ENV{'form.NTSTU'}=$ngrade;
while ($ctr < $ngrade) {
($ENV{'form.student'},$ENV{'form.userdom'}) = split(/:/,$ENV{'form.unamedom'.$ctr});
&submission($request,$ctr,$ngrade-1);
$ctr++;
}
return '';
}
# Get the next/previous one or group of students
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 {$$fullname{$a} cmp $$fullname{$b} } keys %$fullname) {
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'},$symb,$udom,$uname,$partlist) ;
my $statusflg = '';
foreach (keys(%status)) {
$statusflg = 1 if ($status{$_} ne 'nothing');
my ($foo,$partid,$foo1) = 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.userdom'} = $udom;
$ENV{'form.fullname'} = $$fullname{$_};
# $ENV{'form.'.$_.':submitted_by'} = $submitter;
# print "submitter=$ENV{'form.'.$_.':submitted_by'}= $submitter: ";
&submission($request,$ctr,$total);
$ctr++;
}
if ($total < 0) {
my $the_end = '
LON-CAPA User Message
'."\n";
$the_end.='Message: No more students for this section or class.
'."\n";
$the_end.='Click on the button below to return to the grading menu.
'."\n";
$the_end.=&show_grading_menu_form ($symb,$url);
$request->print($the_end);
}
return '';
}
#---- Save the score and award for each student, if changed
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.GD_SEL'.$newflg.'_'.$_} eq 'excused') {
$newrecord{'resource.'.$_.'.solved'} = 'excused'
if ($record{'resource.'.$_.'.solved'} ne 'excused');
} else {
my $pts = ($ENV{'form.GD_BOX'.$newflg.'_'.$_} ne '' ?
$ENV{'form.GD_BOX'.$newflg.'_'.$_} :
$ENV{'form.RADVAL'.$newflg.'_'.$_});
my $wgt = $ENV{'form.WGT'.$newflg.'_'.$_} eq '' ? 1 :
$ENV{'form.WGT'.$newflg.'_'.$_};
my $partial= $pts/$wgt;
$newrecord{'resource.'.$_.'.awarded'} = $partial
if ($record{'resource.'.$_.'.awarded'} ne $partial);
my $reckey = 'resource.'.$_.'.solved';
if ($partial == 0) {
$newrecord{$reckey} = 'incorrect_by_override'
if ($record{$reckey} ne 'incorrect_by_override');
} else {
$newrecord{$reckey} = 'correct_by_override'
if ($record{$reckey} ne 'correct_by_override');
}
$newrecord{'resource.'.$_.'.submitted_by'} = $submitter
if ($submitter && ($record{'resource.'.$_.'.submitted_by'} ne $submitter));
}
}
if (scalar(keys(%newrecord)) > 0) {
$newrecord{'resource.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}";
&Apache::lonnet::cstore(\%newrecord,$symb,
$ENV{'request.course.id'},$domain,$stuname);
}
return '';
}
#--------------------------------------------------------------------------------------
#
#-------------------------- Next few routines handles grading by section or whole class
#
#--- Javascript to handle grading by section or whole class
sub viewgrades_js {
my ($request) = shift;
$request->print(<
function writePoint(partid,weight,point) {
var radioButton = eval("document.classgrade.RADVAL_"+partid);
var textbox = eval("document.classgrade.TEXTVAL_"+partid);
if (point == "textval") {
var point = eval("document.classgrade.TEXTVAL_"+partid+".value");
if (isNaN(point) || point < 0) {
alert("A number equal or greater than 0 is expected. Entered value = "+point);
var resetbox = false;
for (var i=0; i weight) {
var resp = confirm("You entered a value ("+point+
") greater than the weight for the part. Accept?");
if (resp == false) {
textbox.value = "";
return;
}
}
for (var i=0; i weight) {
var resp = confirm("You entered a value ("+point+
") greater than the weight of the part. Accept?");
if (resp == false) {
textbox.value = "";
return;
}
}
selval[0].selected = true;
}
function changeOneScore(partid,user) {
var selval = eval("document.classgrade.GD_"+user+'_'+partid+"_sv");
if (selval[1].selected) {
var boxval = eval("document.classgrade.GD_"+user+'_'+partid+"_aw");
boxval.value = "";
}
}
function resetEntry(numpart) {
for (ctpart=0;ctpart
VIEWJAVASCRIPT
}
#--- show scores for a section or whole class w/ option to change/update a score
sub viewgrades {
my ($request) = shift;
&viewgrades_js($request);
my ($symb,$url) = ($ENV{'form.symb'},$ENV{'form.url'});
my $result='
Manual Grading
';
$result.='Resource: '.$ENV{'form.url'}.''."\n";
#view individual student submission form - called using Javascript viewOneStudent
$result.=&jscriptNform($url,$symb);
#beginning of class grading form
$result.= ''."\n";
$result.=&show_grading_menu_form($symb,$url);
return $result;
}
#--- call by previous routine to display each student
sub viewstudentgrade {
my ($url,$symb,$courseid,$student,$fullname,$parts,$weight) = @_;
my ($uname,$udom) = split(/:/,$student);
my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
my $result='
'."\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 eq '' ? '' : $score*$$weight{$part};
$result.=''."\n";
$result.='
'."\n";
} elsif ($type eq 'solved') {
my ($status,$foo)=split(/_/,$score,2);
$status = 'nothing' if ($status eq '');
$result.=''."\n";
$result.='
\n";
}
}
$result.='
';
return $result;
}
#--- change scores for all the students in a section/class
# record does not get update if unchanged
sub editgrades {
my ($request) = @_;
my $symb=$ENV{'form.symb'};
my $url =$ENV{'form.url'};
my $title='
'."\n";
my %scoreptr = (
'correct' =>'correct_by_override',
'incorrect'=>'incorrect_by_override',
'excused' =>'excused',
'ungraded' =>'ungraded_attempted',
'nothing' => '',
);
my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist($ENV{'form.section'},'0');
my (@partid);
my %weight = ();
my ($i,$ctr,$count,$rec_update) = (0,0,0,0);
while ($ctr < $ENV{'form.totalparts'}) {
my $partid = $ENV{'form.partid_'.$ctr};
push @partid,$partid;
$weight{$partid} = $ENV{'form.weight_'.$partid};
$ctr++;
$result .= '
Part '.$partid.
' (Weight = '.$weight{$partid}.')
';
}
$result .= '
';
foreach (@partid) {
$result .= '
Old Score
'.
'
New Score
';
}
$result .= '
'."\n";
for ($i=0; $i<$ENV{'form.total'}; $i++) {
my $user = $ENV{'form.ctr'.$i};
my %newrecord;
my $updateflag = 0;
my @userdom = grep /^$user:/,keys %$classlist;
my ($foo,$udom) = split(/:/,$userdom[0]);
$result .= '
'.$user.'
'.
$$fullname{$userdom[0]}.'
';
foreach (@partid) {
my $old_aw = $ENV{'form.GD_'.$user.'_'.$_.'_aw_s'};
my $old_part = $old_aw eq '' ? '' : $old_aw/$weight{$_};
my $old_score = $scoreptr{$ENV{'form.GD_'.$user.'_'.$_.'_sv_s'}};
my $awarded = $ENV{'form.GD_'.$user.'_'.$_.'_aw'};
my $partial = $awarded eq '' ? '' : $awarded/$weight{$_};
my $score;
if ($partial eq '') {
$score = $scoreptr{$ENV{'form.GD_'.$user.'_'.$_.'_sv_s'}};
} elsif ($partial > 0) {
$score = 'correct_by_override';
} elsif ($partial == 0) {
$score = 'incorrect_by_override';
}
$score = 'excused' if (($ENV{'form.GD_'.$user.'_'.$_.'_sv'} eq 'excused') &&
($score ne 'excused'));
$result .= '
'.$old_aw.'
'.
'
'.$awarded.
($score eq 'excused' ? $score : '').'
';
next if ($old_part eq $partial && $old_score eq $score);
$updateflag = 1;
$newrecord{'resource.'.$_.'.awarded'} = $partial if $partial ne '';
$newrecord{'resource.'.$_.'.solved'} = $score;
$rec_update++;
}
$result .= '
'."\n";
my $msg = 'Number of records updated = '.$rec_update.
' for '.$count.' student'.($count <= 1 ? '' : 's').'. '.
'Total number of students = '.$ENV{'form.total'}.' ';
return $title.$msg.$result;
}
#------------- end of section for handling grading by section/class ---------
#
#----------------------------------------------------------------------------
#----------------------------------------------------------------------------
#
#-------------------------- Next few routines handles grading by csv upload
#
#--- Javascript to handle csv upload
sub csvupload_javascript_reverse_associate {
return(<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 $javascript;
if ($ENV{'form.upfile_associate'} eq 'reverse') {
$javascript=&csvupload_javascript_reverse_associate();
} else {
$javascript=&csvupload_javascript_forward_associate();
}
my $result='
';
$result.='
Resource: '.$url.'
';
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.='
Part '.(split(/_/))[0].'
'.
'
Type: '.$responsetype.'
'.
'
Handgrade: '.$handgrade.'
';
}
$result.='
';
$request->print(<
Uploading Class Grades
$result
Identify fields
Total number of records found in file: $distotal
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.
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
}
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('
Assigning Grades
');
my $courseid=$ENV{'request.course.id'};
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(" Stored $countdone students\n");
if (@skipped) {
$request->print(' Skipped Students ');
foreach my $student (@skipped) { $request->print(" $student"); }
}
$request->print(&view_edit_entire_class_form($symb,$url));
$request->print(&show_grading_menu_form($symb,$url));
return '';
}
#------------- end of section for handling csv file upload ---------
#
#-------------------------------------------------------------------
#-------------------------- Menu interface -------------------------
#
#--- Show a Grading Menu button - Calls the next routine ---
sub show_grading_menu_form {
my ($symb,$url)=@_;
my $result.=''."\n";
return $result;
}
#--- Displays the main menu page -------
sub gradingmenu {
my ($request) = @_;
my ($symb,$url)=&get_symb_and_url($request);
if (!$symb) {return '';}
my $result='
Select a Grading Method
';
$result.='
';
$result.='
Resource: '.$url.'
';
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.='
Part '.(split(/_/))[0].'
'.
'
Type: '.$responsetype.'
'.
'
Handgrade: '.$handgrade.'
';
}
$result.='
';
$result.=&view_edit_entire_class_form($symb,$url).' ';
$result.=&upcsvScores_form($symb,$url).' ';
$result.=&viewGradeaStu_form($symb,$url,$resptype,$hdgrade).' ';
$result.=&verifyReceipt_form($symb,$url)
if ((&Apache::lonnet::allowed('mgr',$ENV{'request.course.id'})) && ($symb));
return $result;
}
#--- Menu for grading a section or the whole class ---
sub view_edit_entire_class_form {
my ($symb,$url)=@_;
my ($classlist,$sections) = &getclasslist('all','0');
my $result.='
'."\n";
return $result;
}
#--- Menu to upload a csv scores ---
sub upcsvScores_form {
my ($symb,$url) = @_;
if (!$symb) {return '';}
my $result = ''."\n";
$result.='
'."\n";
$result.='
'."\n";
$result.=' Specify a file containing the class scores for above resource
'."\n";
$result.='
'."\n";
my $upfile_select=&Apache::loncommon::upfile_select_html();
$result.=<
$upfile_select
ENDUPFORM
$result.='
'."\n";
$result.='
'."\n";
return $result;
}
#--- Handgrading problems ---
sub viewGradeaStu_form {
my ($symb,$url,$response,$handgrade) = @_;
my ($classlist,$sections) = &getclasslist('all','0');
my $result.='
'."\n";
$result.='
'."\n";
$result.=' View/Grade an Individual Student\'s Submission
'."\n";
$result.='
'."\n";
$result.=''."\n";
$result.='
'."\n";
$result.='
'."\n";
return $result;
}
#--- Form to input a receipt number ---
sub verifyReceipt_form {
my ($symb,$url) = @_;
my $result = ''."\n";
my $hostver=unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'});
$result.='
'."\n";
$result.='
'."\n";
$result.=' Verify a Submission Receipt Issued by this Server
'."\n";
$result.='
'."\n";
$result.='';
$result.='
'."\n";
$result.='
'."\n";
return $result;
}
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 '' 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('