--- loncom/homework/grades.pm 2003/09/27 01:59:10 1.130.2.1.2.4
+++ loncom/homework/grades.pm 2007/06/14 21:36:12 1.404
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# The LON-CAPA Grading handler
#
-# $Id: grades.pm,v 1.130.2.1.2.4 2003/09/27 01:59:10 albertel Exp $
+# $Id: grades.pm,v 1.404 2007/06/14 21:36:12 www Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -25,16 +25,6 @@
#
# 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
-# Year 2003
-# February, March H.K. Ng
-# July, H. K. Ng
-#
package Apache::grades;
use strict;
@@ -46,54 +36,65 @@ use Apache::lonhtmlcommon;
use Apache::lonnavmaps;
use Apache::lonhomework;
use Apache::loncoursedata;
-use Apache::lonmsg qw(:user_normal_msg);
+use Apache::lonmsg();
use Apache::Constants qw(:common);
+use Apache::lonlocal;
+use Apache::lonenc;
use String::Similarity;
+use lib '/home/httpd/lib/perl';
+use LONCAPA;
+
+use POSIX qw(floor);
my %oldessays=();
my %perm=();
# ----- These first few routines are general use routines.----
#
-# --- Retrieve the parts that matches stores_\d+ from the metadata file.---
+# --- Retrieve the parts from the metadata file.---
sub getpartlist {
- my ($url) = @_;
- my @parts =();
- my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys'));
- foreach my $key (@metakeys) {
- if ( $key =~ m/stores_(\w+)_.*/) {
- push(@parts,$key);
+ my ($symb) = @_;
+ my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
+ my $partorder = &Apache::lonnet::metadata($url, 'partorder');
+ my @parts;
+ if ($partorder) {
+ for my $part (split (/,/,$partorder)) {
+ if (!&Apache::loncommon::check_if_partid_hidden($part,$symb)) {
+ push(@parts, $part);
+ }
+ }
+ } else {
+ my $metadata = &Apache::lonnet::metadata($url, 'packages');
+ foreach (split(/\,/,$metadata)) {
+ if ($_ =~ /^part_(.*)$/) {
+ if (!&Apache::loncommon::check_if_partid_hidden($1,$symb)) {
+ push(@parts, $1);
+ }
+ }
+ }
+ }
+ my @stores;
+ foreach my $part (@parts) {
+ my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys'));
+ foreach my $key (@metakeys) {
+ if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }
}
}
- return @parts;
+ return @stores;
}
# --- Get the symbolic name of a problem and the url
-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 = &Apache::loncoursedata::ProcessFullName
- (@name{qw/lastname generation firstname middlename/});
- } else {
- &Apache::lonnet::logthis('grades.pm: no name data for '.$uname.
- '@'.$udom.':'.$tmp);
+sub get_symb {
+ my ($request,$silent) = @_;
+ (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 '') {
+ if (!$silent) {
+ $request->print("Unable to handle ambiguous references:$url:.");
+ return ();
+ }
}
- return $fullname;
+ return ($symb);
}
#--- Format fullname, username:domain if different for display
@@ -101,91 +102,232 @@ sub get_fullname {
sub nameUserString {
my ($type,$fullname,$uname,$udom) = @_;
if ($type eq 'header') {
- return ' Fullname (Username) ';
+ return ' Fullname (Username) ';
} else {
- return ' '.$fullname.' ('.$uname.
- ($ENV{'user.domain'} eq $udom ? '' : ' ('.$udom.')').') ';
+ return ' '.$fullname.' ('.$uname.
+ ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').') ';
}
}
#--- 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,$symb) = shift;
- $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url))) if ($symb eq '');
- my $allkeys = &Apache::lonnet::metadata($url,'keys');
- my %seen = ();
- my (@partlist,%handgrade);
- foreach (split(/,/,&Apache::lonnet::metadata($url,'packages'))) {
- if (/^\w+response_\w+.*/) {
- my ($responsetype,$part) = split(/_/,$_,2);
- my ($partid,$respid) = split(/_/,$part);
- $responsetype =~ s/response$//; # make it compatible w/ navmaps - should move to that!!
- my ($value) = &Apache::lonnet::EXT('resource.'.$part.'.handgrade',$symb);
- $handgrade{$part} = $responsetype.':'.($value eq 'yes' ? 'yes' : 'no');
- next if ($seen{$partid} > 0);
- $seen{$partid}++;
- push @partlist,$partid;
- }
+ my ($symb) = shift;
+
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ my $res = $navmap->getBySymb($symb);
+ my $partlist = $res->parts();
+ my %vPart =
+ map { $_ => 1 } (&Apache::loncommon::get_env_multiple('form.vPart'));
+ my (%response_types,%handgrade);
+ foreach my $part (@{ $partlist }) {
+ next if (%vPart && !exists($vPart{$part}));
+
+ my @types = $res->responseType($part);
+ my @ids = $res->responseIds($part);
+ for (my $i=0; $i < scalar(@ids); $i++) {
+ $response_types{$part}{$ids[$i]} = $types[$i];
+ $handgrade{$part.'_'.$ids[$i]} =
+ &Apache::lonnet::EXT('resource.'.$part.'_'.$ids[$i].
+ '.handgrade',$symb);
+ }
+ }
+ return ($partlist,\%handgrade,\%response_types);
+}
+
+sub flatten_responseType {
+ my ($responseType) = @_;
+ my @part_response_id =
+ map {
+ my $part = $_;
+ map {
+ [$part,$_]
+ } sort(keys(%{ $responseType->{$part} }));
+ } sort(keys(%$responseType));
+ return @part_response_id;
+}
+
+sub get_display_part {
+ my ($partID,$symb)=@_;
+ my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);
+ if (defined($display) and $display ne '') {
+ $display.= " (id $partID )";
+ } else {
+ $display=$partID;
}
- return \@partlist,\%handgrade;
+ return $display;
}
#--- Show resource title
#--- and parts and response type
sub showResourceInfo {
- my ($url,$probTitle) = @_;
- my $result ='
'.
- 'Current Resource: '.$probTitle.' '."\n";
- my ($partlist,$handgrade) = &response_type($url);
+ my ($symb,$probTitle,$checkboxes) = @_;
+ my $col=3;
+ if ($checkboxes) { $col=4; }
+ my $result = ''.&mt('Current Resource').': '.$probTitle.' '."\n";
+ $result .=''."\n";
- return $result,\%resptype,$hdgrade,$partlist,$handgrade;
+ return $result,$responseType,$hdgrade,$partlist,$handgrade;
}
+
+sub get_order {
+ my ($partid,$respid,$symb,$uname,$udom)=@_;
+ my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
+ $url=&Apache::lonnet::clutter($url);
+ my $subresult=&Apache::lonnet::ssi($url,
+ ('grade_target' => 'analyze'),
+ ('grade_domain' => $udom),
+ ('grade_symb' => $symb),
+ ('grade_courseid' =>
+ $env{'request.course.id'}),
+ ('grade_username' => $uname));
+ (undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
+ my %analyze=&Apache::lonnet::str2hash($subresult);
+ return ($analyze{"$partid.$respid.shown"});
+}
#--- Clean response type for display
-#--- Currently filters option response type only.
+#--- Currently filters option/rank/radiobutton/match/essay/Task
+# response types only.
sub cleanRecord {
- my ($answer,$response,$symb) = @_;
- if ($response eq 'option') {
- my (@IDs,@ans);
- foreach (split(/\&/,&Apache::lonnet::unescape($answer))) {
- my ($optionID,$ans) = split(/=/);
- push @IDs,$optionID.'';
- push @ans,$ans;
+ my ($answer,$response,$symb,$partid,$respid,$record,$order,$version,
+ $uname,$udom) = @_;
+ my $grayFont = '';
+ if ($response =~ /^(option|rank)$/) {
+ my %answer=&Apache::lonnet::str2hash($answer);
+ my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
+ my ($toprow,$bottomrow);
+ foreach my $foil (@$order) {
+ if ($grading{$foil} == 1) {
+ $toprow.=' '.$answer{$foil}.' ';
+ } else {
+ $toprow.=''.$answer{$foil}.' ';
+ }
+ $bottomrow.=''.$grayFont.$foil.' ';
}
- my $grayFont = '';
return ''.
- 'Answer '.
- (join ' ',@ans).' '.
- ''.$grayFont.'Option ID '.$grayFont.
- (join ' '.$grayFont,@IDs).' '.
- '
';
- }
- if ($response eq 'essay') {
- if (! exists ($ENV{'form.'.$symb})) {
+ 'Answer '.$toprow.' '.
+ ''.$grayFont.'Option ID '.
+ $grayFont.$bottomrow.' '.'
';
+ } elsif ($response eq 'match') {
+ my %answer=&Apache::lonnet::str2hash($answer);
+ my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
+ my @items=&Apache::lonnet::str2array($record->{$version."resource.$partid.$respid.submissionitems"});
+ my ($toprow,$middlerow,$bottomrow);
+ foreach my $foil (@$order) {
+ my $item=shift(@items);
+ if ($grading{$foil} == 1) {
+ $toprow.=''.$item.' ';
+ $middlerow.=''.$grayFont.$answer{$foil}.' ';
+ } else {
+ $toprow.=''.$item.' ';
+ $middlerow.=''.$grayFont.$answer{$foil}.' ';
+ }
+ $bottomrow.=''.$grayFont.$foil.' ';
+ }
+ return ''.
+ 'Answer '.$toprow.' '.
+ ''.$grayFont.'Item ID '.
+ $middlerow.' '.
+ ''.$grayFont.'Option ID '.
+ $bottomrow.' '.'
';
+ } elsif ($response eq 'radiobutton') {
+ my %answer=&Apache::lonnet::str2hash($answer);
+ my ($toprow,$bottomrow);
+ my $correct=($order->[0])+1;
+ for (my $i=1;$i<=$#$order;$i++) {
+ my $foil=$order->[$i];
+ if (exists($answer{$foil})) {
+ if ($i == $correct) {
+ $toprow.='true ';
+ } else {
+ $toprow.='true ';
+ }
+ } else {
+ $toprow.='false ';
+ }
+ $bottomrow.=''.$grayFont.$foil.' ';
+ }
+ return ''.
+ 'Answer '.$toprow.' '.
+ ''.$grayFont.'Option ID '.
+ $grayFont.$bottomrow.' '.'
';
+ } elsif ($response eq 'essay') {
+ if (! exists ($env{'form.'.$symb})) {
my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade',
- $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
- $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
+ $env{'course.'.$env{'request.course.id'}.'.domain'},
+ $env{'course.'.$env{'request.course.id'}.'.num'});
- my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'};
- $ENV{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
- $ENV{'form.kwclr'} = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
- $ENV{'form.kwsize'} = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
- $ENV{'form.kwstyle'} = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
- $ENV{'form.'.$symb} = 1; # so that we don't have to read it from disk for multiple sub of the same prob.
+ my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
+ $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
+ $env{'form.kwclr'} = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
+ $env{'form.kwsize'} = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
+ $env{'form.kwstyle'} = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
+ $env{'form.'.$symb} = 1; # so that we don't have to read it from disk for multiple sub of the same prob.
+ }
+ $answer =~ s-\n- -g;
+ return ''.&keywords_highlight($answer).' ';
+ } elsif ( $response eq 'organic') {
+ my $result='Smile representation: "'.$answer.' "';
+ my $jme=$record->{$version."resource.$partid.$respid.molecule"};
+ $result.=&Apache::chemresponse::jme_img($jme,$answer,400);
+ return $result;
+ } elsif ( $response eq 'Task') {
+ if ( $answer eq 'SUBMITTED') {
+ my $files = $record->{$version."resource.$respid.$partid.bridgetask.portfiles"};
+ my $result = &Apache::bridgetask::file_list($files,$uname,$udom);
+ return $result;
+ } elsif ( grep(/^\Q$version\E.*?\.instance$/, keys(%{$record})) ) {
+ my @matches = grep(/^\Q$version\E.*?\.instance$/,
+ keys(%{$record}));
+ return join(' ',($version,@matches));
+
+
+ } else {
+ my $result =
+ ''
+ .&mt('Overall result: [_1]',
+ $record->{$version."resource.$respid.$partid.status"})
+ .'
';
+
+ $result .= '';
+ my @grade = grep(/^\Q${version}resource.$respid.$partid.\E[^.]*[.]status$/,
+ keys(%{$record}));
+ foreach my $grade (sort(@grade)) {
+ my ($dim) = ($grade =~/[.]([^.]+)[.]status$/);
+ $result.= ''.&mt("Dimension: [_1], status [_2] ",
+ $dim, $record->{$grade}).
+ ' ';
+ }
+ $result.=' ';
+ return $result;
}
- return ''.&keywords_highlight($answer).' ';
+
}
return $answer;
}
@@ -218,7 +360,8 @@ sub commonJSfunctions {
}
}
} else {
- if (selectOne.selected) return selectOne.value;
+ // only one value it must be the selected one
+ return selectOne.value;
}
}
@@ -229,34 +372,52 @@ COMMONJSFUNCTIONS
#--- section, ids and fullnames for each user.
sub getclasslist {
my ($getsec,$filterlist) = @_;
- $getsec = $getsec eq '' ? 'all' : $getsec;
+ my @getsec;
+ if (!ref($getsec)) {
+ if ($getsec ne '' && $getsec ne 'all') {
+ @getsec=($getsec);
+ }
+ } else {
+ @getsec=@{$getsec};
+ }
+ if (grep(/^all$/,@getsec)) { undef(@getsec); }
+
my $classlist=&Apache::loncoursedata::get_classlist();
# Bail out if we were unable to get the classlist
return if (! defined($classlist));
#
my %sections;
my %fullnames;
- foreach (keys(%$classlist)) {
- # the following undefs are for 'domain', and 'username' respectively.
- my (undef,undef,$end,$start,$id,$section,$fullname,$status)=
- @{$classlist->{$_}};
+ foreach my $student (keys(%$classlist)) {
+ my $end =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_END()];
+ my $start =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_START()];
+ my $id =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_ID()];
+ my $section =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
+ my $fullname =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()];
+ my $status =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()];
# filter students according to status selected
- if ($filterlist && $ENV{'form.Status'} ne 'Any') {
- if ($ENV{'form.Status'} ne $status) {
- delete ($classlist->{$_});
+ if ($filterlist && $env{'form.Status'} ne 'Any') {
+ if ($env{'form.Status'} ne $status) {
+ delete ($classlist->{$student});
next;
}
}
- $section = ($section ne '' ? $section : 'no');
+ $section = ($section ne '' ? $section : 'none');
if (&canview($section)) {
- if ($getsec eq 'all' || $getsec eq $section) {
+ if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {
$sections{$section}++;
- $fullnames{$_}=$fullname;
+ $fullnames{$student}=$fullname;
} else {
- delete($classlist->{$_});
+ delete($classlist->{$student});
}
} else {
- delete($classlist->{$_});
+ delete($classlist->{$student});
}
}
my %seen = ();
@@ -306,8 +467,8 @@ sub canview {
#--- 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 ($symb,$udom,$uname,$partlist) = @_;
+ my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
my %partstatus = ();
foreach (@$partlist) {
my ($status,undef) = split(/_/,$record{"resource.$_.solved"},2);
@@ -323,7 +484,7 @@ sub student_gradeStatus {
# Use by verifyscript and viewgrades
# Shows a student's view of problem and submission
sub jscriptNform {
- my ($url,$symb) = @_;
+ my ($symb) = @_;
my $jscript=''."\n";
$jscript.= ''."\n";
- $studentTable.=&show_grading_menu_form($symb,$url);
+ $studentTable.=&show_grading_menu_form($symb);
$request->print($studentTable);
return '';
@@ -2798,26 +3923,24 @@ LISTJAVASCRIPT
sub getSymbMap {
my ($request) = @_;
- my $navmap = Apache::lonnavmaps::navmap-> new($ENV{'request.course.fn'}.'.db',
- $ENV{'request.course.fn'}.'_parms.db');
-# $navmap->init();
+ my $navmap = Apache::lonnavmaps::navmap->new();
my %symbx = ();
my @titles = ();
my $minder = 0;
# Gather every sequence that has problems.
- my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); }, 1);
+ my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); },
+ 1,0,1);
for my $sequence ($navmap->getById('0.0'), @sequences) {
if ($navmap->hasResource($sequence, sub { shift->is_problem(); }, 0) ) {
- my $title = $minder.'.'.$sequence->compTitle();
- push @titles, $title; # minder in case two titles are identical
- $symbx{$title} = $sequence->symb();
+ my $title = $minder.'.'.
+ &HTML::Entities::encode($sequence->compTitle(),'"\'&');
+ push(@titles, $title); # minder in case two titles are identical
+ $symbx{$title} = &HTML::Entities::encode($sequence->symb(),'"\'&');
$minder++;
}
}
-
- $navmap->untieHashes();
return \@titles,\%symbx;
}
@@ -2826,45 +3949,65 @@ sub getSymbMap {
sub displayPage {
my ($request) = shift;
- my ($symb,$url) = &get_symb_and_url($request);
- my $cdom = $ENV{"course.$ENV{'request.course.id'}.domain"};
- my $cnum = $ENV{"course.$ENV{'request.course.id'}.num"};
- my $getsec = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'};
- my $pageTitle = $ENV{'form.page'};
+ my ($symb) = &get_symb($request);
+ my $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ my $cnum = $env{"course.$env{'request.course.id'}.num"};
+ my $getsec = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
+ my $pageTitle = $env{'form.page'};
my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
- my ($uname,$udom) = split(/:/,$ENV{'form.student'});
- my $usec=$classlist->{$ENV{'form.student'}}[5];
+ my ($uname,$udom) = split(/:/,$env{'form.student'});
+ my $usec=$classlist->{$env{'form.student'}}[5];
+
+ #need to make sure we have the correct data for later EXT calls,
+ #thus invalidate the cache
+ &Apache::lonnet::devalidatecourseresdata(
+ $env{'course.'.$env{'request.course.id'}.'.num'},
+ $env{'course.'.$env{'request.course.id'}.'.domain'});
+ &Apache::lonnet::clear_EXT_cache_status();
+
if (!&canview($usec)) {
- $request->print('Unable to view requested student.('.$ENV{'form.student'}.') ');
- $request->print(&show_grading_menu_form($symb,$url));
+ $request->print('Unable to view requested student.('.$env{'form.student'}.') ');
+ $request->print(&show_grading_menu_form($symb));
return;
}
- my $result=' '.$ENV{'form.title'}.' ';
- $result.=' Student: '.&nameUserString(undef,$$fullname{$ENV{'form.student'}},$uname,$udom).
+ my $result=' '.$env{'form.title'}.' ';
+ $result.=' Student: '.&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom).
' '."\n";
+ if (&Apache::lonnet::validCODE($env{'form.CODE'})) {
+ $result.=' CODE: '.$env{'form.CODE'}.' '."\n";
+ } else {
+ delete($env{'form.CODE'});
+ }
&sub_page_js($request);
$request->print($result);
- my $navmap = Apache::lonnavmaps::navmap-> new($ENV{'request.course.fn'}.'.db',
- $ENV{'request.course.fn'}.'_parms.db',1, 1);
- my ($mapUrl, $id, $resUrl) = split(/___/, $ENV{'form.page'});
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'});
my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
-
+ if (!$map) {
+ $request->print('Unable to view requested sequence. ('.$resUrl.') ');
+ $request->print(&show_grading_menu_form($symb));
+ return;
+ }
my $iterator = $navmap->getIterator($map->map_start(),
$map->map_finish());
my $studentTable=' '."\n".
' '."\n".
- ' '."\n".
- ' '."\n".
+ ' '."\n".
+ ' '."\n".
' '."\n".
- ' '."\n".
- ' '."\n".
+ ' '."\n".
' '."\n".
' '."\n".
- ' '."\n";
+ ' '."\n";
- my $checkIcon = ' '."\n";
+ }
+ my $checkIcon = ' ';
$studentTable.=' Note: Problems graded correct by the computer are marked with a '.$checkIcon.
@@ -2872,9 +4015,10 @@ sub displayPage {
''.
''.
' Prob. '.
- ' '.($ENV{'form.vProb'} eq 'no' ? 'Title' : 'Problem Text').'/Grade ';
+ ' '.($env{'form.vProb'} eq 'no' ? 'Title' : 'Problem Text').'/Grade ';
- my ($depth,$question) = (1,1);
+ &Apache::lonxml::clear_problem_counter();
+ my ($depth,$question,$prob) = (1,1,1);
$iterator->next(); # skip the first BEGIN_MAP
my $curRes = $iterator->next(); # for "current resource"
while ($depth > 0) {
@@ -2885,39 +4029,48 @@ sub displayPage {
my $parts = $curRes->parts();
my $title = $curRes->compTitle();
my $symbx = $curRes->symb();
- $studentTable.=''.$question.
- (scalar(@{$parts}) == 1 ? '' : ' ('.scalar(@{$parts}).' parts)').' ';
+ $studentTable.=''.$prob.
+ (scalar(@{$parts}) == 1 ? '' : ' ('.scalar(@{$parts}).' parts)').' ';
$studentTable.='';
- if ($ENV{'form.vProb'} eq 'yes') {
- $studentTable.=&show_problem($request,$symbx,$uname,$udom,1);
+ my %form = ('CODE' => $env{'form.CODE'},);
+ if ($env{'form.vProb'} eq 'yes' ) {
+ $studentTable.=&show_problem($request,$symbx,$uname,$udom,1,
+ undef,'both',\%form);
} else {
- my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$ENV{'request.course.id'});
+ my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'},%form);
$companswer =~ s|||g;
$companswer =~ s| ||g;
# while ($companswer =~ /()/s) { # \n");
+# $request->print('match='.$1." \n");
# }
# $companswer =~ s| ||g;
- $studentTable.=' '.$title.' Correct answer: '.$companswer;
+ $studentTable.=' '.$title.' Correct answer: '.$companswer;
}
- my %record = &Apache::lonnet::restore($symbx,$ENV{'request.course.id'},$udom,$uname);
+ my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
- if ($ENV{'form.lastSub'} eq 'datesub') {
+ if ($env{'form.lastSub'} eq 'datesub') {
if ($record{'version'} eq '') {
- $studentTable.=' No recorded submission for this problem ';
+ $studentTable.=' No recorded submission for this problem ';
} else {
my %responseType = ();
foreach my $partid (@{$parts}) {
- $responseType{$partid} = $curRes->responseType($partid);
+ my @responseIds =$curRes->responseIds($partid);
+ my @responseType =$curRes->responseType($partid);
+ my %responseIds;
+ for (my $i=0;$i<=$#responseIds;$i++) {
+ $responseIds{$responseIds[$i]}=$responseType[$i];
+ }
+ $responseType{$partid} = \%responseIds;
}
- $studentTable.= &displaySubByDates(\$symbx,\%record,$parts,\%responseType,$checkIcon);
+ $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom);
+
}
- } elsif ($ENV{'form.lastSub'} eq 'all') {
- my $last = ($ENV{'form.lastSub'} eq 'last' ? 'last' : '');
+ } elsif ($env{'form.lastSub'} eq 'all') {
+ my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
$studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom,
- $ENV{'request.course.id'},
+ $env{'request.course.id'},
'','.submission');
}
@@ -2927,6 +4080,7 @@ sub displayPage {
$studentTable.=' '."\n";
$question++;
}
+ $prob++;
}
$studentTable.='';
@@ -2934,54 +4088,116 @@ sub displayPage {
$curRes = $iterator->next();
}
- $navmap->untieHashes();
-
- $studentTable.='
'."\n".
+ $studentTable.='
'."\n".
' '.
+ 'onClick="javascript:checkSubmitPage(this.form,'.$question.');" />'.
' '."\n";
- $studentTable.=&show_grading_menu_form($symb,$url);
+ $studentTable.=&show_grading_menu_form($symb);
$request->print($studentTable);
return '';
}
sub displaySubByDates {
- my ($symbx,$record,$parts,$responseType,$checkIcon) = @_;
+ my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;
+ my $isCODE=0;
+ my $isTask = ($symb =~/\.task$/);
+ if (exists($record->{'resource.CODE'})) { $isCODE=1; }
my $studentTable=''.
''.
'Date/Time '.
+ ($isCODE?'CODE ':'').
'Submission '.
'Status ';
my ($version);
my %mark;
+ my %orders;
$mark{'correct_by_student'} = $checkIcon;
- return ' Nothing submitted - no attempts '
- if (!exists($$record{'1:timestamp'}));
+ if (!exists($$record{'1:timestamp'})) {
+ return ' Nothing submitted - no attempts ';
+ }
+
+ my $interaction;
for ($version=1;$version<=$$record{'version'};$version++) {
my $timestamp = scalar(localtime($$record{$version.':timestamp'}));
+ if (exists($$record{$version.':resource.0.version'})) {
+ $interaction = $$record{$version.':resource.0.version'};
+ }
+
+ my $where = ($isTask ? "$version:resource.$interaction"
+ : "$version:resource");
+ #&Apache::lonnet::logthis(" got $where");
$studentTable.=''.$timestamp.' ';
+ if ($isCODE) {
+ $studentTable.=''.$record->{$version.':resource.CODE'}.' ';
+ }
my @versionKeys = split(/\:/,$$record{$version.':keys'});
my @displaySub = ();
foreach my $partid (@{$parts}) {
- my @matchKey = grep /^resource\.$partid\..*?\.submission$/,@versionKeys;
+ my @matchKey = ($isTask ? sort(grep /^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys)
+ : sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys));
+
+
# next if ($$record{"$version:resource.$partid.solved"} eq '');
- $displaySub[0].=(exists $$record{$version.':'.$matchKey[0]}) ?
- 'Part '.$partid.' '.
- ($$record{"$version:resource.$partid.tries"} eq '' ? 'Trial not counted' :
- 'Trial '.$$record{"$version:resource.$partid.tries"}).' '.
- &cleanRecord($$record{$version.':'.$matchKey[0]},$$responseType{$partid},$$symbx).' ' : '';
- $displaySub[1].=(exists $$record{"$version:resource.$partid.award"}) ?
- 'Part '.$partid.' '.
- lc($$record{"$version:resource.$partid.award"}).' '.
- $mark{$$record{"$version:resource.$partid.solved"}}.' ' : '';
- $displaySub[2].=(exists $$record{"$version:resource.$partid.regrader"}) ?
- $$record{"$version:resource.$partid.regrader"}.' (Part: '.$partid.')' : '';
- }
- $displaySub[2].=(exists $$record{"$version:resource.regrader"}) ?
- $$record{"$version:resource.regrader"} : ''; # needed because old essay regrader has not parts info
- $studentTable.=''.$displaySub[0].' '.$displaySub[1].
- ($displaySub[2] eq '' ? '' : 'Manually graded by '.$displaySub[2]).' ';
+ my $display_part=&get_display_part($partid,$symb);
+ foreach my $matchKey (@matchKey) {
+ if (exists($$record{$version.':'.$matchKey}) &&
+ $$record{$version.':'.$matchKey} ne '') {
+
+ my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
+ : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
+ #&Apache::lonnet::logthis("match $matchKey $responseId (".$$record{$version.':'.$matchKey});
+ $displaySub[0].='Part: '.$display_part.' ';
+ $displaySub[0].='(ID '.
+ $responseId.') ';
+ if ($$record{"$where.$partid.tries"} eq '') {
+ $displaySub[0].='Trial not counted';
+ } else {
+ $displaySub[0].='Trial '.
+ $$record{"$where.$partid.tries"};
+ }
+ my $responseType=($isTask ? 'Task'
+ : $responseType->{$partid}->{$responseId});
+ if (!exists($orders{$partid})) { $orders{$partid}={}; }
+ if (!exists($orders{$partid}->{$responseId})) {
+ $orders{$partid}->{$responseId}=
+ &get_order($partid,$responseId,$symb,$uname,$udom);
+ }
+ $displaySub[0].=' '.
+ &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom).' ';
+ }
+ }
+ if (exists($$record{"$where.$partid.checkedin"})) {
+ $displaySub[1].='Checked in by '.
+ $$record{"$where.$partid.checkedin"}.' into slot '.
+ $$record{"$where.$partid.checkedin.slot"}.
+ ' ';
+ }
+ if (exists $$record{"$where.$partid.award"}) {
+ $displaySub[1].='Part: '.$display_part.' '.
+ lc($$record{"$where.$partid.award"}).' '.
+ $mark{$$record{"$where.$partid.solved"}}.
+ ' ';
+ }
+ if (exists $$record{"$where.$partid.regrader"}) {
+ $displaySub[2].=$$record{"$where.$partid.regrader"}.
+ ' ('.&mt('Part').': '.$display_part.')';
+ } elsif ($$record{"$version:resource.$partid.regrader"} =~ /\S/) {
+ $displaySub[2].=
+ $$record{"$version:resource.$partid.regrader"}.
+ ' ('.&mt('Part').': '.$display_part.')';
+ }
+ }
+ # needed because old essay regrader has not parts info
+ if (exists $$record{"$version:resource.regrader"}) {
+ $displaySub[2].=$$record{"$version:resource.regrader"};
+ }
+ $studentTable.=''.$displaySub[0].' '.$displaySub[1];
+ if ($displaySub[2]) {
+ $studentTable.='Manually graded by '.$displaySub[2];
+ }
+ $studentTable.=' ';
+
}
$studentTable.='
';
return $studentTable;
@@ -2990,29 +4206,33 @@ sub displaySubByDates {
sub updateGradeByPage {
my ($request) = shift;
- my $cdom = $ENV{"course.$ENV{'request.course.id'}.domain"};
- my $cnum = $ENV{"course.$ENV{'request.course.id'}.num"};
- my $getsec = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'};
- my $pageTitle = $ENV{'form.page'};
+ my $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ my $cnum = $env{"course.$env{'request.course.id'}.num"};
+ my $getsec = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
+ my $pageTitle = $env{'form.page'};
my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
- my ($uname,$udom) = split(/:/,$ENV{'form.student'});
- my $usec=$classlist->{$ENV{'form.student'}}[5];
+ my ($uname,$udom) = split(/:/,$env{'form.student'});
+ my $usec=$classlist->{$env{'form.student'}}[5];
if (!&canmodify($usec)) {
- $request->print('Unable to modify requested student.('.$ENV{'form.student'}.' ');
- $request->print(&show_grading_menu_form($ENV{'form.symb'},$ENV{'form.url'}));
+ $request->print('Unable to modify requested student.('.$env{'form.student'}.' ');
+ $request->print(&show_grading_menu_form($env{'form.symb'}));
return;
}
- my $result=' '.$ENV{'form.title'}.' ';
- $result.=' Student: '.&nameUserString(undef,$ENV{'form.fullname'},$uname,$udom).
+ my $result=' '.$env{'form.title'}.' ';
+ $result.=' Student: '.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
' '."\n";
$request->print($result);
- my $navmap = Apache::lonnavmaps::navmap-> new($ENV{'request.course.fn'}.'.db',
- $ENV{'request.course.fn'}.'_parms.db',1, 1);
- my ($mapUrl, $id, $resUrl) = split(/___/, $ENV{'form.page'});
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'});
my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
-
+ if (!$map) {
+ $request->print('Unable to grade requested sequence. ('.$resUrl.') ');
+ my ($symb)=&get_symb($request);
+ $request->print(&show_grading_menu_form($symb));
+ return;
+ }
my $iterator = $navmap->getIterator($map->map_start(),
$map->map_finish());
@@ -3025,27 +4245,29 @@ sub updateGradeByPage {
$iterator->next(); # skip the first BEGIN_MAP
my $curRes = $iterator->next(); # for "current resource"
- my ($depth,$question,$changeflag)= (1,1,0);
+ my ($depth,$question,$prob,$changeflag)= (1,1,1,0);
while ($depth > 0) {
if($curRes == $iterator->BEGIN_MAP) { $depth++; }
if($curRes == $iterator->END_MAP) { $depth--; }
- if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout) {
+ if (ref($curRes) && $curRes->is_problem()) {
my $parts = $curRes->parts();
my $title = $curRes->compTitle();
my $symbx = $curRes->symb();
- $studentTable.=''.$question.
- (scalar(@{$parts}) == 1 ? '' : ' ('.scalar(@{$parts}).' parts)').' ';
+ $studentTable.=''.$prob.
+ (scalar(@{$parts}) == 1 ? '' : ' ('.scalar(@{$parts}).' parts)').' ';
$studentTable.=' '.$title.' ';
my %newrecord=();
my @displayPts=();
+ my %aggregate = ();
+ my $aggregateflag = 0;
foreach my $partid (@{$parts}) {
- my $newpts = $ENV{'form.GD_BOX'.$question.'_'.$partid};
- my $oldpts = $ENV{'form.oldpts'.$question.'_'.$partid};
+ my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};
+ my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};
- my $wgt = $ENV{'form.WGT'.$question.'_'.$partid} != 0 ?
- $ENV{'form.WGT'.$question.'_'.$partid} : 1;
+ my $wgt = $env{'form.WGT'.$question.'_'.$partid} != 0 ?
+ $env{'form.WGT'.$question.'_'.$partid} : 1;
my $partial = $newpts/$wgt;
my $score;
if ($partial > 0) {
@@ -3053,56 +4275,82 @@ sub updateGradeByPage {
} elsif ($newpts ne '') { #empty is taken as 0
$score = 'incorrect_by_override';
}
- my $dropMenu = $ENV{'form.GD_SEL'.$question.'_'.$partid};
+ my $dropMenu = $env{'form.GD_SEL'.$question.'_'.$partid};
if ($dropMenu eq 'excused') {
$partial = '';
$score = 'excused';
} elsif ($dropMenu eq 'reset status'
- && $ENV{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists
+ && $env{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists
$newrecord{'resource.'.$partid.'.tries'} = 0;
$newrecord{'resource.'.$partid.'.solved'} = '';
$newrecord{'resource.'.$partid.'.award'} = '';
$newrecord{'resource.'.$partid.'.awarded'} = 0;
- $newrecord{'resource.'.$partid.'.regrader'} = "$ENV{'user.name'}:$ENV{'user.domain'}";
+ $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}";
$changeflag++;
$newpts = '';
+
+ my $aggtries = $env{'form.aggtries'.$question.'_'.$partid};
+ my $totaltries = $env{'form.totaltries'.$question.'_'.$partid};
+ my $solvedstatus = $env{'form.solved'.$question.'_'.$partid};
+ if ($aggtries > 0) {
+ &decrement_aggs($symbx,$partid,\%aggregate,$aggtries,$totaltries,$solvedstatus);
+ $aggregateflag = 1;
+ }
}
-
- my $oldstatus = $ENV{'form.solved'.$question.'_'.$partid};
- $displayPts[0].=' Part '.$partid.' = '.
+ my $display_part=&get_display_part($partid,$curRes->symb());
+ my $oldstatus = $env{'form.solved'.$question.'_'.$partid};
+ $displayPts[0].=' Part: '.$display_part.' = '.
(($oldstatus eq 'excused') ? 'excused' : $oldpts).
- ' ';
- $displayPts[1].=' Part '.$partid.' = '.
+ ' ';
+ $displayPts[1].=' Part: '.$display_part.' = '.
(($score eq 'excused') ? 'excused' : $newpts).
- ' ';
-
+ ' ';
$question++;
- next if ($dropMenu eq 'reset status' || ($newpts == $oldpts && $score ne 'excused'));
+ next if ($dropMenu eq 'reset status' || ($newpts eq $oldpts && $score ne 'excused'));
$newrecord{'resource.'.$partid.'.awarded'} = $partial if $partial ne '';
$newrecord{'resource.'.$partid.'.solved'} = $score if $score ne '';
- $newrecord{'resource.'.$partid.'.regrader'} = "$ENV{'user.name'}:$ENV{'user.domain'}"
+ $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}"
if (scalar(keys(%newrecord)) > 0);
$changeflag++;
}
if (scalar(keys(%newrecord)) > 0) {
- &Apache::lonnet::cstore(\%newrecord,$symbx,$ENV{'request.course.id'},
+ my %record =
+ &Apache::lonnet::restore($symbx,$env{'request.course.id'},
+ $udom,$uname);
+
+ if (&Apache::lonnet::validCODE($env{'form.CODE'})) {
+ $newrecord{'resource.CODE'} = $env{'form.CODE'};
+ } elsif (&Apache::lonnet::validCODE($record{'resource.CODE'})) {
+ $newrecord{'resource.CODE'} = '';
+ }
+ &Apache::lonnet::cstore(\%newrecord,$symbx,$env{'request.course.id'},
$udom,$uname);
+ %record = &Apache::lonnet::restore($symbx,
+ $env{'request.course.id'},
+ $udom,$uname);
+ &check_and_remove_from_queue($parts,\%record,undef,$symbx,
+ $cdom,$cnum,$udom,$uname);
}
+
+ if ($aggregateflag) {
+ &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
+ $env{'course.'.$env{'request.course.id'}.'.domain'},
+ $env{'course.'.$env{'request.course.id'}.'.num'});
+ }
$studentTable.=''.$displayPts[0].' '.
''.$displayPts[1].' '.
' ';
+ $prob++;
}
$curRes = $iterator->next();
}
- $navmap->untieHashes();
-
$studentTable.='';
- $studentTable.=&show_grading_menu_form($ENV{'form.symb'},$ENV{'form.url'});
+ $studentTable.=&show_grading_menu_form($env{'form.symb'});
my $grademsg=($changeflag == 0 ? 'No score was changed or updated.' :
'The scores were changed for '.
$changeflag.' problem'.($changeflag == 1 ? '.' : 's.'));
@@ -3120,24 +4368,23 @@ sub updateGradeByPage {
#------ start of section for handling grading by page/sequence ---------
sub defaultFormData {
- my ($symb,$url)=@_;
+ my ($symb)=@_;
return '
'."\n".
- ' '."\n".
- ' '."\n".
- ' '."\n";
+ ' '."\n".
+ ' '."\n";
}
sub getSequenceDropDown {
my ($request,$symb)=@_;
my $result=''."\n";
my ($titles,$symbx) = &getSymbMap($request);
- my ($curpage,$type,$mapId) = ($symb =~ /(.*?\.(page|sequence))___(\d+)___/);
+ my ($curpage)=&Apache::lonnet::decode_symb($symb);
my $ctr=0;
foreach (@$titles) {
my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
$result.=''.$showtitle.' '."\n";
$ctr++;
}
@@ -3145,18 +4392,28 @@ sub getSequenceDropDown {
return $result;
}
+sub scantron_filenames {
+ my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
+ my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
+ my @files=&Apache::lonnet::dirlist('userfiles',$cdom,$cname,
+ &propath($cdom,$cname));
+ my @possiblenames;
+ foreach my $filename (sort(@files)) {
+ ($filename)=split(/&/,$filename);
+ if ($filename!~/^scantron_orig_/) { next ; }
+ $filename=~s/^scantron_orig_//;
+ push(@possiblenames,$filename);
+ }
+ return @possiblenames;
+}
+
sub scantron_uploads {
- #FIXME need to support scantron files put in another location,
- # maybe the course directory? a scantron dir in the course directory?
- if (!-e $Apache::lonnet::perlvar{'lonScansDir'}) { return ''};
+ my ($file2grade) = @_;
my $result= '';
- opendir(DIR,$Apache::lonnet::perlvar{'lonScansDir'});
- my @files=sort(readdir(DIR));
- foreach my $filename (@files) {
- if ($filename eq '.' or $filename eq '..') { next; }
- $result.="$filename \n";
+ $result.=" ";
+ foreach my $filename (sort(&scantron_filenames())) {
+ $result.="$filename \n";
}
- closedir(DIR);
$result.=" ";
return $result;
}
@@ -3164,6 +4421,7 @@ sub scantron_uploads {
sub scantron_scantab {
my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
my $result=''."\n";
+ $result.=' '."\n";
foreach my $line (<$fh>) {
my ($name,$descrip)=split(/:/,$line);
if ($name =~ /^\#/) { next; }
@@ -3174,56 +4432,175 @@ sub scantron_scantab {
return $result;
}
+sub scantron_CODElist {
+ my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+ my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+ my @names=&Apache::lonnet::getkeys('CODEs',$cdom,$cnum);
+ my $namechoice=' ';
+ foreach my $name (sort {uc($a) cmp uc($b)} @names) {
+ if ($name =~ /^error: 2 /) { next; }
+ if ($name =~ /^type\0/) { next; }
+ $namechoice.=''.$name.' ';
+ }
+ $namechoice=''.$namechoice.' ';
+ return $namechoice;
+}
+
+sub scantron_CODEunique {
+ my $result='
+ Yes
+
+
+ No
+ ';
+ return $result;
+}
+
sub scantron_selectphase {
- my ($r) = @_;
- my ($symb,$url)=&get_symb_and_url($r);
+ my ($r,$file2grade) = @_;
+ my ($symb)=&get_symb($r);
if (!$symb) {return '';}
my $sequence_selector=&getSequenceDropDown($r,$symb);
- my $default_form_data=&defaultFormData($symb,$url);
- my $grading_menu_button=&show_grading_menu_form($symb,$url);
- my $file_selector=&scantron_uploads();
+ my $default_form_data=&defaultFormData($symb);
+ my $grading_menu_button=&show_grading_menu_form($symb);
+ my $file_selector=&scantron_uploads($file2grade);
my $format_selector=&scantron_scantab();
+ my $CODE_selector=&scantron_CODElist();
+ my $CODE_unique=&scantron_CODEunique();
my $result;
#FIXME allow instructor to be able to download the scantron file
# and to upload it,
$result.= <
-
- $default_form_data
-
+
+
+
+ $default_form_data
+
+
+
+SCANTRONFORM
+
+ $r->print($result);
+
+ if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
+ &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
+
+ $r->print(<
+
+
+SCANTRONFORM
+ }
+ $r->print(<
+
+
+ $default_form_data
+
+
+
+
+
+SCANTRONFORM
+
+ $r->print(<
-
-
$grading_menu_button
SCANTRONFORM
- return $result;
+ return
}
sub get_scantron_config {
@@ -3269,30 +4646,92 @@ sub username_to_idmap {
}
sub scantron_fixup_scanline {
- my ($scantron_config,$line,$field,$newvalue) = @_;
+ my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
if ($field eq 'ID') {
- if ($newvalue > $$scantron_config{'IDlength'}) {
- return ($line,1,'New value to large');
+ if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {
+ return ($line,1,'New value too large');
}
- if ($newvalue < $$scantron_config{'IDlength'}) {
- $newvalue=sprintf('%-'.$$scantron_config{'IDlength'}.'s',
- $newvalue);
+ if (length($args->{'newid'}) < $$scantron_config{'IDlength'}) {
+ $args->{'newid'}=sprintf('%-'.$$scantron_config{'IDlength'}.'s',
+ $args->{'newid'});
}
substr($line,$$scantron_config{'IDstart'}-1,
- $$scantron_config{'IDlength'})=$newvalue;
+ $$scantron_config{'IDlength'})=$args->{'newid'};
+ if ($args->{'newid'}=~/^\s*$/) {
+ &scan_data($scan_data,"$whichline.user",
+ $args->{'username'}.':'.$args->{'domain'});
+ }
+ } elsif ($field eq 'CODE') {
+ if ($args->{'CODE_ignore_dup'}) {
+ &scan_data($scan_data,"$whichline.CODE_ignore_dup",'1');
+ }
+ &scan_data($scan_data,"$whichline.useCODE",'1');
+ if ($args->{'CODE'} ne 'use_unfound') {
+ if (length($args->{'CODE'}) > $$scantron_config{'CODElength'}) {
+ return ($line,1,'New CODE value too large');
+ }
+ if (length($args->{'CODE'}) < $$scantron_config{'CODElength'}) {
+ $args->{'CODE'}=sprintf('%-'.$$scantron_config{'CODElength'}.'s',$args->{'CODE'});
+ }
+ substr($line,$$scantron_config{'CODEstart'}-1,
+ $$scantron_config{'CODElength'})=$args->{'CODE'};
+ }
+ } elsif ($field eq 'answer') {
+ my $length=$scantron_config->{'Qlength'};
+ my $off=$scantron_config->{'Qoff'};
+ my $on=$scantron_config->{'Qon'};
+ my $answer=${off}x$length;
+ if ($args->{'response'} eq 'none') {
+ &scan_data($scan_data,
+ "$whichline.no_bubble.".$args->{'question'},'1');
+ } else {
+ if ($on eq 'letter') {
+ my @alphabet=('A'..'Z');
+ $answer=$alphabet[$args->{'response'}];
+ } elsif ($on eq 'number') {
+ $answer=$args->{'response'}+1;
+ if ($answer == 10) { $answer = '0'; }
+ } else {
+ substr($answer,$args->{'response'},1)=$on;
+ }
+ &scan_data($scan_data,
+ "$whichline.no_bubble.".$args->{'question'},undef,'1');
+ }
+ my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'};
+ substr($line,$where-1,$length)=$answer;
}
return $line;
}
+sub scan_data {
+ my ($scan_data,$key,$value,$delete)=@_;
+ my $filename=$env{'form.scantron_selectfile'};
+ if (defined($value)) {
+ $scan_data->{$filename.'_'.$key} = $value;
+ }
+ if ($delete) { delete($scan_data->{$filename.'_'.$key}); }
+ return $scan_data->{$filename.'_'.$key};
+}
+
sub scantron_parse_scanline {
- my ($line,$scantron_config)=@_;
+ my ($line,$whichline,$scantron_config,$scan_data,$justHeader)=@_;
my %record;
my $questions=substr($line,$$scantron_config{'Qstart'}-1);
my $data=substr($line,0,$$scantron_config{'Qstart'}-1);
- if ($$scantron_config{'CODElocation'} ne 0) {
- if ($$scantron_config{'CODElocation'} < 0) {
- $record{'scantron.CODE'}=substr($data,$$scantron_config{'CODEstart'}-1,
+ if (!($$scantron_config{'CODElocation'} eq 0 ||
+ $$scantron_config{'CODElocation'} eq 'none')) {
+ if ($$scantron_config{'CODElocation'} < 0 ||
+ $$scantron_config{'CODElocation'} eq 'letter' ||
+ $$scantron_config{'CODElocation'} eq 'number') {
+ $record{'scantron.CODE'}=substr($data,
+ $$scantron_config{'CODEstart'}-1,
$$scantron_config{'CODElength'});
+ if (&scan_data($scan_data,"$whichline.useCODE")) {
+ $record{'scantron.useCODE'}=1;
+ }
+ if (&scan_data($scan_data,"$whichline.CODE_ignore_dup")) {
+ $record{'scantron.CODE_ignore_dup'}=1;
+ }
} else {
#FIXME interpret first N questions
}
@@ -3308,6 +4747,8 @@ sub scantron_parse_scanline {
$record{'scantron.LastName'}=
substr($data,$$scantron_config{'LastName'}-1,
$$scantron_config{'LastNamelength'});
+ if ($justHeader) { return \%record; }
+
my @alphabet=('A'..'Z');
my $questnum=0;
while ($questions) {
@@ -3315,19 +4756,63 @@ sub scantron_parse_scanline {
my $currentquest=substr($questions,0,$$scantron_config{'Qlength'});
substr($questions,0,$$scantron_config{'Qlength'})='';
if (length($currentquest) < $$scantron_config{'Qlength'}) { next; }
- my (@array)=split(/$$scantron_config{'Qon'}/,$currentquest);
- if (length($array[0]) eq $$scantron_config{'Qlength'}) {
- $record{"scantron.$questnum.answer"}='';
- } else {
- $record{"scantron.$questnum.answer"}=$alphabet[length($array[0])];
- }
- if (scalar(@array) gt 2) {
- push(@{$record{'scantron.doubleerror'}},$currentquest);
- my @ans=@array;
- my $i=length($ans[0]);shift(@ans);
- while (@ans) {
- $i+=length($ans[0])+1;
- $record{"scantron.$questnum.answer"}.=$alphabet[$i];
+ if ($$scantron_config{'Qon'} eq 'letter') {
+ if ($currentquest eq '?'
+ || $currentquest eq '*') {
+ push(@{$record{'scantron.doubleerror'}},$questnum);
+ $record{"scantron.$questnum.answer"}='';
+ } elsif (!defined($currentquest)
+ || $currentquest eq $$scantron_config{'Qoff'}
+ || $currentquest !~ /^[A-Z]$/) {
+ $record{"scantron.$questnum.answer"}='';
+ if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
+ push(@{$record{"scantron.missingerror"}},$questnum);
+ }
+ } else {
+ $record{"scantron.$questnum.answer"}=$currentquest;
+ }
+ } elsif ($$scantron_config{'Qon'} eq 'number') {
+ if ($currentquest eq '?'
+ || $currentquest eq '*') {
+ push(@{$record{'scantron.doubleerror'}},$questnum);
+ $record{"scantron.$questnum.answer"}='';
+ } elsif (!defined($currentquest)
+ || $currentquest eq $$scantron_config{'Qoff'}
+ || $currentquest !~ /^\d$/) {
+ $record{"scantron.$questnum.answer"}='';
+ if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
+ push(@{$record{"scantron.missingerror"}},$questnum);
+ }
+ } else {
+ # wrap zero back to J
+ if ($currentquest eq '0') {
+ $record{"scantron.$questnum.answer"}=
+ $alphabet[9];
+ } else {
+ $record{"scantron.$questnum.answer"}=
+ $alphabet[$currentquest-1];
+ }
+ }
+ } else {
+ my @array=split($$scantron_config{'Qon'},$currentquest,-1);
+ if (length($array[0]) eq $$scantron_config{'Qlength'}) {
+ $record{"scantron.$questnum.answer"}='';
+ if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
+ push(@{$record{"scantron.missingerror"}},$questnum);
+ }
+ } else {
+ $record{"scantron.$questnum.answer"}=
+ $alphabet[length($array[0])];
+ }
+ if (scalar(@array) gt 2) {
+ push(@{$record{'scantron.doubleerror'}},$questnum);
+ my @ans=@array;
+ my $i=length($ans[0]);shift(@ans);
+ while ($#ans) {
+ $i+=length($ans[0])+1;
+ $record{"scantron.$questnum.answer"}.=$alphabet[$i];
+ shift(@ans);
+ }
}
}
}
@@ -3337,7 +4822,6 @@ sub scantron_parse_scanline {
sub scantron_add_delay {
my ($delayqueue,$scanline,$errormessage,$errorcode)=@_;
- Apache->request->print('add_delay_error '.$_[2] );
push(@$delayqueue,
{'line' => $scanline, 'emsg' => $errormessage,
'ecode' => $errorcode }
@@ -3345,188 +4829,481 @@ sub scantron_add_delay {
}
sub scantron_find_student {
- my ($scantron_record,$idmap)=@_;
+ my ($scantron_record,$scan_data,$idmap,$line)=@_;
my $scanID=$$scantron_record{'scantron.ID'};
+ if ($scanID =~ /^\s*$/) {
+ return &scan_data($scan_data,"$line.user");
+ }
foreach my $id (keys(%$idmap)) {
- #Apache->request->print('checking studnet -'.$id.'- againt -'.$scanID.'- ');
- if (lc($id) eq lc($scanID)) {
- #Apache->request->print('success');
- return $$idmap{$id};
- }
+ if (lc($id) eq lc($scanID)) {
+ return $$idmap{$id};
+ }
}
return undef;
}
sub scantron_filter {
my ($curres)=@_;
- if (ref($curres) && $curres->is_problem() && !$curres->randomout) {
+
+ if (ref($curres) && $curres->is_problem()) {
+ # if the user has asked to not have either hidden
+ # or 'randomout' controlled resources to be graded
+ # don't include them
+ if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
+ && $curres->randomout) {
+ return 0;
+ }
return 1;
}
return 0;
}
-#FIXME I think I am doing this in the wrong order, I think it would be
-#better to make a several passes analyzing all of the lines in the
-#file for common errors wrong/invalid PID/username duplicated
-#PID/username, missing bubbles, double bubbles, missing/invalid CODE
-#and then get the instructor to fix all of these errors, then grade
-#the corrected one, I'll still need to catch error conditions, but
-#maybe most will taken care even before we start
-
sub scantron_process_corrections {
my ($r) = @_;
- if ($ENV{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) {
- my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'});
- my $scanlines=&scantron_getfile();
- my $classlist=&Apache::loncoursedata::get_classlist();
- my $which=$ENV{'form.scantron_line'};
- my $line=&scantron_get_line($scanlines,$which);
- my ($skip,$err,$errmsg);
- if ($ENV{'form.scantron_skip_record'}) {
- $skip=1;
- } else {
- my $newstudent=$ENV{'form.scantron_username'}.':'.
- $ENV{'form.scantron_domain'};
- my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID];
+ my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+ my ($scanlines,$scan_data)=&scantron_getfile();
+ my $classlist=&Apache::loncoursedata::get_classlist();
+ my $which=$env{'form.scantron_line'};
+ my $line=&scantron_get_line($scanlines,$scan_data,$which);
+ my ($skip,$err,$errmsg);
+ if ($env{'form.scantron_skip_record'}) {
+ $skip=1;
+ } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) {
+ my $newstudent=$env{'form.scantron_username'}.':'.
+ $env{'form.scantron_domain'};
+ my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID];
+ ($line,$err,$errmsg)=
+ &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
+ 'ID',{'newid'=>$newid,
+ 'username'=>$env{'form.scantron_username'},
+ 'domain'=>$env{'form.scantron_domain'}});
+ } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)CODE$/) {
+ my $resolution=$env{'form.scantron_CODE_resolution'};
+ my $newCODE;
+ my %args;
+ if ($resolution eq 'use_unfound') {
+ $newCODE='use_unfound';
+ } elsif ($resolution eq 'use_found') {
+ $newCODE=$env{'form.scantron_CODE_selectedvalue'};
+ } elsif ($resolution eq 'use_typed') {
+ $newCODE=$env{'form.scantron_CODE_newvalue'};
+ } elsif ($resolution =~ /^use_closest_(\d+)/) {
+ $newCODE=$env{"form.scantron_CODE_closest_$1"};
+ }
+ if ($env{'form.scantron_corrections'} eq 'duplicateCODE') {
+ $args{'CODE_ignore_dup'}=1;
+ }
+ $args{'CODE'}=$newCODE;
+ ($line,$err,$errmsg)=
+ &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
+ 'CODE',\%args);
+ } elsif ($env{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) {
+ foreach my $question (split(',',$env{'form.scantron_questions'})) {
($line,$err,$errmsg)=
- &scantron_fixup_scanline(\%scantron_config,$line,'ID',$newid);
+ &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,
+ $which,'answer',
+ { 'question'=>$question,
+ 'response'=>$env{"form.scantron_correct_Q_$question"}});
+ if ($err) { last; }
}
- if ($err) {
- $r->print("Unable to accept last correction, an error occurred :$errmsg:");
- } else {
- &scantron_put_line($scanlines,$which,$line,$skip);
- &scantron_putfile($scanlines);
+ }
+ if ($err) {
+ $r->print("Unable to accept last correction, an error occurred :$errmsg: ");
+ } else {
+ &scantron_put_line($scanlines,$scan_data,$which,$line,$skip);
+ &scantron_putfile($scanlines,$scan_data);
+ }
+}
+
+sub reset_skipping_status {
+ my ($scanlines,$scan_data)=&scantron_getfile();
+ &scan_data($scan_data,'remember_skipping',undef,1);
+ &scantron_putfile(undef,$scan_data);
+}
+
+sub start_skipping {
+ my ($scan_data,$i)=@_;
+ my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
+ if ($env{'form.scantron_options_redo'} =~ /^redo_/) {
+ $remembered{$i}=2;
+ } else {
+ $remembered{$i}=1;
+ }
+ &scan_data($scan_data,'remember_skipping',join(':',%remembered));
+}
+
+sub should_be_skipped {
+ my ($scanlines,$scan_data,$i)=@_;
+ if ($env{'form.scantron_options_redo'} !~ /^redo_/) {
+ # not redoing old skips
+ if ($scanlines->{'skipped'}[$i]) { return 1; }
+ return 0;
+ }
+ my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
+
+ if (exists($remembered{$i}) && $remembered{$i} != 2 ) {
+ return 0;
+ }
+ return 1;
+}
+
+sub remember_current_skipped {
+ my ($scanlines,$scan_data)=&scantron_getfile();
+ my %to_remember;
+ for (my $i=0;$i<=$scanlines->{'count'};$i++) {
+ if ($scanlines->{'skipped'}[$i]) {
+ $to_remember{$i}=1;
}
}
+
+ &scan_data($scan_data,'remember_skipping',join(':',%to_remember));
+ &scantron_putfile(undef,$scan_data);
+}
+
+sub check_for_error {
+ my ($r,$result)=@_;
+ if ($result ne 'ok' && $result ne 'not_found' ) {
+ $r->print("An error occurred ($result) when trying to Remove the existing corrections.");
+ }
+}
+
+sub scantron_warning_screen {
+ my ($button_text)=@_;
+ my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
+ my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+ my $CODElist;
+ if ($scantron_config{'CODElocation'} &&
+ $scantron_config{'CODEstart'} &&
+ $scantron_config{'CODElength'}) {
+ $CODElist=$env{'form.scantron_CODElist'};
+ if ($env{'form.scantron_CODElist'} eq '') { $CODElist='None '; }
+ $CODElist=
+ 'List of CODES to validate against: '.
+ $env{'form.scantron_CODElist'}.' ';
+ }
+ return (<
+Please double check the information
+ below before clicking on '$button_text'
+
+
+Sequence to be Graded: $title
+Data File that will be used: $env{'form.scantron_selectfile'}
+$CODElist
+
+
+ If this information is correct, please click on '$button_text'.
+ If something is incorrect, please click the 'Grading Menu' button to start over.
+
+
+STUFF
+}
+
+sub scantron_do_warning {
+ my ($r)=@_;
+ my ($symb)=&get_symb($r);
+ if (!$symb) {return '';}
+ my $default_form_data=&defaultFormData($symb);
+ $r->print(&scantron_form_start().$default_form_data);
+ if ( $env{'form.selectpage'} eq '' ||
+ $env{'form.scantron_selectfile'} eq '' ||
+ $env{'form.scantron_format'} eq '' ) {
+ $r->print("You have forgetten to specify some information. Please go Back and try again.
");
+ if ( $env{'form.selectpage'} eq '') {
+ $r->print('You have not selected a Sequence to grade
');
+ }
+ if ( $env{'form.scantron_selectfile'} eq '') {
+ $r->print('You have not selected a file that contains the student\'s response data.
');
+ }
+ if ( $env{'form.scantron_format'} eq '') {
+ $r->print('You have not selected a the format of the student\'s response data.
');
+ }
+ } else {
+ my $warning=&scantron_warning_screen('Grading: Validate Records');
+ $r->print(<
+
+STUFF
+ }
+ $r->print(" ".&show_grading_menu_form($symb));
+ return '';
+}
+
+sub scantron_form_start {
+ my ($max_bubble)=@_;
+ my $result= <
+
+
+
+
+
+
+
+
+
+SCANTRONFORM
+ return $result;
}
sub scantron_validate_file {
my ($r) = @_;
- my ($symb,$url)=&get_symb_and_url($r);
+ my ($symb)=&get_symb($r);
if (!$symb) {return '';}
- my $default_form_data=&defaultFormData($symb,$url);
+ my $default_form_data=&defaultFormData($symb);
+
+ # do the detection of only doing skipped records first befroe we delete
+ # them when doing the corrections reset
+ if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') {
+ &reset_skipping_status();
+ }
+ if ($env{'form.scantron_options_redo'} eq 'redo_skipped') {
+ &remember_current_skipped();
+ $env{'form.scantron_options_redo'}='redo_skipped_ready';
+ }
+
+ if ($env{'form.scantron_options_ignore'} eq 'ignore_corrections') {
+ &check_for_error($r,&scantron_remove_file('corrected'));
+ &check_for_error($r,&scantron_remove_file('skipped'));
+ &check_for_error($r,&scantron_remove_scan_data());
+ $env{'form.scantron_options_ignore'}='done';
+ }
- if ($ENV{'form.scantron_corrections'}) {
+ if ($env{'form.scantron_corrections'}) {
&scantron_process_corrections($r);
}
+ $r->print("Gathering neccessary info.
");$r->rflush();
#get the student pick code ready
$r->print(&Apache::loncommon::studentbrowser_javascript());
- my $result= <
-
-
-
-
- $default_form_data
-SCANTRONFORM
+ my $max_bubble=&scantron_get_maxbubble();
+ my $result=&scantron_form_start($max_bubble).$default_form_data;
$r->print($result);
- my @validate_phases=( 'ID',
+ my @validate_phases=( 'sequence',
+ 'ID',
'CODE',
'doublebubble',
'missingbubbles');
- if (!$ENV{'form.validatepass'}) {
- $ENV{'form.valiadatepass'} = 0;
+ if (!$env{'form.validatepass'}) {
+ $env{'form.validatepass'} = 0;
}
- my $currentphase=$ENV{'form.valiadatepass'};
+ my $currentphase=$env{'form.validatepass'};
- if ($ENV{'form.scantron_selectfile'}=~m-^/-) {
- #first pass copy file to classdir
-
- }
my $stop=0;
while (!$stop && $currentphase < scalar(@validate_phases)) {
+ $r->print(" Validating ".$validate_phases[$currentphase]."
");
+ $r->rflush();
my $which="scantron_validate_".$validate_phases[$currentphase];
{
no strict 'refs';
($stop,$currentphase)=&$which($r,$currentphase);
}
}
- $r->print(" ");
+ if (!$stop) {
+ my $warning=&scantron_warning_screen('Start Grading');
+ $r->print(<
+$warning
+
+
+STUFF
+
+ } else {
+ $r->print(' ');
+ $r->print(" ");
+ }
+ if ($stop) {
+ if ($validate_phases[$currentphase] eq 'sequence') {
+ $r->print(' ');
+ $r->print(' this error ');
+
+ $r->print(" Or click the 'Grading Menu' button to start over.
");
+ } else {
+ $r->print(' ');
+ $r->print(' using corrected info ');
+ $r->print(" ");
+ $r->print(" this scanline saving it for later.");
+ }
+ }
+ $r->print(" ".&show_grading_menu_form($symb));
return '';
}
+sub scantron_remove_file {
+ my ($which)=@_;
+ my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
+ my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
+ my $file='scantron_';
+ if ($which eq 'corrected' || $which eq 'skipped') {
+ $file.=$which.'_';
+ } else {
+ return 'refused';
+ }
+ $file.=$env{'form.scantron_selectfile'};
+ return &Apache::lonnet::removeuserfile($cname,$cdom,$file);
+}
+
+sub scantron_remove_scan_data {
+ my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
+ my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
+ my @keys=&Apache::lonnet::getkeys('nohist_scantrondata',$cdom,$cname);
+ my @todelete;
+ my $filename=$env{'form.scantron_selectfile'};
+ foreach my $key (@keys) {
+ if ($key=~/^\Q$filename\E_/) {
+ if ($env{'form.scantron_options_redo'} eq 'redo_skipped_ready' &&
+ $key=~/remember_skipping/) {
+ next;
+ }
+ push(@todelete,$key);
+ }
+ }
+ my $result;
+ if (@todelete) {
+ $result=&Apache::lonnet::del('nohist_scantrondata',\@todelete,$cdom,$cname);
+ }
+ return $result;
+}
+
sub scantron_getfile {
- #my $scanlines=Apache::File->new($Apache::lonnet::perlvar{'lonScansDir'}."/$ENV{'form.scantron_selectfile'}");
- #FIXME really would prefer a scantron directory but tokenwrapper
- # doesn't allow access to subdirs of userfiles
+ #FIXME really would prefer a scantron directory
+ my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
+ my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
my $lines;
- $lines=&Apache::lonnet::getfile('/uploaded/'.
- $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.'/'.
- $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.'/'.
- 'scantron_orig_'.$ENV{'form.scantron_selectfile'});
- if ($lines eq '-1') {
- #FIXME need to actually replicate file to course space
- }
+ $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
+ 'scantron_orig_'.$env{'form.scantron_selectfile'});
my %scanlines;
- $scanlines{'orig'}=[split("\n",$lines)];
+ $scanlines{'orig'}=[(split("\n",$lines,-1))];
my $temp=$scanlines{'orig'};
$scanlines{'count'}=$#$temp;
- $lines=&Apache::lonnet::getfile('/uploaded/'.
- $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.'/'.
- $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.'/'.
- 'scantron_corrected_'.$ENV{'form.scantron_selectfile'});
+ $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
+ 'scantron_corrected_'.$env{'form.scantron_selectfile'});
if ($lines eq '-1') {
$scanlines{'corrected'}=[];
} else {
- $scanlines{'corrected'}=[split("\n",$lines)];
+ $scanlines{'corrected'}=[(split("\n",$lines,-1))];
}
- $lines=&Apache::lonnet::getfile('/uploaded/'.
- $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.'/'.
- $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.'/'.
- 'scantron_skipped_'.$ENV{'form.scantron_selectfile'});
+ $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
+ 'scantron_skipped_'.$env{'form.scantron_selectfile'});
if ($lines eq '-1') {
$scanlines{'skipped'}=[];
} else {
- $scanlines{'skipped'}=[split("\n",$lines)];
+ $scanlines{'skipped'}=[(split("\n",$lines,-1))];
}
- return \%scanlines;
+ my @tmp=&Apache::lonnet::dump('nohist_scantrondata',$cdom,$cname);
+ if ($tmp[0] =~ /^(error:|no_such_host)/) { @tmp=(); }
+ my %scan_data = @tmp;
+ return (\%scanlines,\%scan_data);
}
sub lonnet_putfile {
my ($contents,$filename)=@_;
- my $docuname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'};
- my $docudom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'};
- my $docuhome=$ENV{'course.'.$ENV{'request.course.id'}.'.home'};
- $ENV{'form.sillywaytopassafilearound'}=$contents;
- &Apache::lonnet::finishuserfileupload($docuname,$docudom,$docuhome,'sillywaytopassafilearound',$filename);
+ my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};
+ my $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'};
+ $env{'form.sillywaytopassafilearound'}=$contents;
+ &Apache::lonnet::finishuserfileupload($docuname,$docudom,'sillywaytopassafilearound',$filename);
}
sub scantron_putfile {
- my ($scanlines) = @_;
- #FIXME really would prefer a scantron directory but tokenwrapper
- # doesn't allow access to subdirs of userfiles
- my $prefix='/uploaded/'.
- $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.'/'.
- $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.'/'.
- 'scantron_';
- my $prefix='scantron_';
+ my ($scanlines,$scan_data) = @_;
+ #FIXME really would prefer a scantron directory
+ my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
+ my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
+ if ($scanlines) {
+ my $prefix='scantron_';
# no need to update orig, shouldn't change
# &lonnet_putfile(join("\n",@{$scanlines->{'orig'}}),$prefix.'orig_'.
-# $ENV{'form.scantron_selectfile'});
- &lonnet_putfile(join("\n",@{$scanlines->{'corrected'}}),
- $prefix.'corrected_'.
- $ENV{'form.scantron_selectfile'});
- &lonnet_putfile(join("\n",@{$scanlines->{'skipped'}}),
- $prefix.'skipped_'.
- $ENV{'form.scantron_selectfile'});
+# $env{'form.scantron_selectfile'});
+ &lonnet_putfile(join("\n",@{$scanlines->{'corrected'}}),
+ $prefix.'corrected_'.
+ $env{'form.scantron_selectfile'});
+ &lonnet_putfile(join("\n",@{$scanlines->{'skipped'}}),
+ $prefix.'skipped_'.
+ $env{'form.scantron_selectfile'});
+ }
+ &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname);
}
sub scantron_get_line {
- my ($scanlines,$i)=@_;
- if ($scanlines->{'skipped'}[$i]) {return undef;}
+ my ($scanlines,$scan_data,$i)=@_;
+ if (&should_be_skipped($scanlines,$scan_data,$i)) { return undef; }
+ #if ($scanlines->{'skipped'}[$i]) { return undef; }
if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$i];}
return $scanlines->{'orig'}[$i];
}
+sub get_todo_count {
+ my ($scanlines,$scan_data)=@_;
+ my $count=0;
+ for (my $i=0;$i<=$scanlines->{'count'};$i++) {
+ my $line=&scantron_get_line($scanlines,$scan_data,$i);
+ if ($line=~/^[\s\cz]*$/) { next; }
+ $count++;
+ }
+ return $count;
+}
+
sub scantron_put_line {
- my ($scanlines,$i,$newline,$skip)=@_;
- if ($skip) { $scanlines->{'skipped'}[$i]=$newline;return; }
+ my ($scanlines,$scan_data,$i,$newline,$skip)=@_;
+ if ($skip) {
+ $scanlines->{'skipped'}[$i]=$newline;
+ &start_skipping($scan_data,$i);
+ return;
+ }
$scanlines->{'corrected'}[$i]=$newline;
}
+sub scantron_clear_skip {
+ my ($scanlines,$scan_data,$i)=@_;
+ if (exists($scanlines->{'skipped'}[$i])) {
+ undef($scanlines->{'skipped'}[$i]);
+ return 1;
+ }
+ return 0;
+}
+
+sub scantron_filter_not_exam {
+ my ($curres)=@_;
+
+ if (ref($curres) && $curres->is_problem() && !$curres->is_exam()) {
+ # if the user has asked to not have either hidden
+ # or 'randomout' controlled resources to be graded
+ # don't include them
+ if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
+ && $curres->randomout) {
+ return 0;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+sub scantron_validate_sequence {
+ my ($r,$currentphase) = @_;
+
+ my $navmap=Apache::lonnavmaps::navmap->new();
+ my (undef,undef,$sequence)=
+ &Apache::lonnet::decode_symb($env{'form.selectpage'});
+
+ my $map=$navmap->getResourceByUrl($sequence);
+
+ $r->print(' ');
+ if ($env{'form.validate_sequence_exam'} ne 'ignore') {
+ my @resources=
+ $navmap->retrieveResources($map,\&scantron_filter_not_exam,1,0);
+ if (@resources) {
+ $r->print("".&mt('Some resources in the sequence currently are not set to exam mode. Grading these resources currently may not work correctly.')."
");
+ return (1,$currentphase);
+ }
+ }
+
+ return (0,$currentphase+1);
+}
+
sub scantron_validate_ID {
my ($r,$currentphase) = @_;
@@ -3535,39 +5312,54 @@ sub scantron_validate_ID {
my %idmap=&username_to_idmap($classlist);
#get scantron line setup
- my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'});
- my $scanlines=&scantron_getfile();
+ my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+ my ($scanlines,$scan_data)=&scantron_getfile();
my %found=('ids'=>{},'usernames'=>{});
for (my $i=0;$i<=$scanlines->{'count'};$i++) {
- my $line=&scantron_get_line($scanlines,$i);
- if (!$line) { next; }
- my $scan_record=&scantron_parse_scanline($line,\%scantron_config);
+ my $line=&scantron_get_line($scanlines,$scan_data,$i);
+ if ($line=~/^[\s\cz]*$/) { next; }
+ my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
+ $scan_data);
my $id=$$scan_record{'scantron.ID'};
- $r->print("Checking ID ".$$scan_record{'scantron.ID'}.
- " on paper ID ".$$scan_record{'scantron.PaperID'}."
\n");
my $found;
foreach my $checkid (keys(%idmap)) {
- if (lc($checkid) eq lc($id)) {
- if ($checkid ne $id) {
- $r->print("Using $checkid for encoded $id
\n");
- }
- $found=$checkid;last;
- }
+ if (lc($checkid) eq lc($id)) { $found=$checkid;last; }
}
if ($found) {
+ my $username=$idmap{$found};
if ($found{'ids'}{$found}) {
- #FIXME store away line we prviously saw the ID on
- &scantron_get_correction($r,$i,$scan_record,$line,
- 'duplicateID',$found);
- return(1);
+ &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
+ $line,'duplicateID',$found);
+ return(1,$currentphase);
+ } elsif ($found{'usernames'}{$username}) {
+ &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
+ $line,'duplicateID',$username);
+ return(1,$currentphase);
+ }
+ #FIXME store away line we previously saw the ID on to use above
+ $found{'ids'}{$found}++;
+ $found{'usernames'}{$username}++;
+ } else {
+ if ($id =~ /^\s*$/) {
+ my $username=&scan_data($scan_data,"$i.user");
+ if (defined($username) && $found{'usernames'}{$username}) {
+ &scantron_get_correction($r,$i,$scan_record,
+ \%scantron_config,
+ $line,'duplicateID',$username);
+ return(1,$currentphase);
+ } elsif (!defined($username)) {
+ &scantron_get_correction($r,$i,$scan_record,
+ \%scantron_config,
+ $line,'incorrectID');
+ return(1,$currentphase);
+ }
+ $found{'usernames'}{$username}++;
} else {
- $found{'ids'}{$found}++;
+ &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
+ $line,'incorrectID');
+ return(1,$currentphase);
}
- } else {
- &scantron_get_correction($r,$i,$scan_record,$line,
- 'incorrectID');
- return(1);
}
}
@@ -3575,59 +5367,245 @@ sub scantron_validate_ID {
}
sub scantron_get_correction {
- my ($r,$i,$scan_record,$line,$error,$arg)=@_;
+ my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;
#FIXME in the case of a duplicated ID the previous line, probaly need
#to show both the current line and the previous one and allow skipping
#the previous one or the current one
- $r->print("This scantron record has an error.");
- if ( defined($$scan_record{'scantron.PaperID'}) ) {
- $r->print("The current PaperID is ".
+ $r->print("
An error was detected ($error) ");
+ if ( $$scan_record{'scantron.PaperID'} =~ /\S/) {
+ $r->print(" for PaperID ".
$$scan_record{'scantron.PaperID'}." \n");
} else {
- $r->print("The current scanline is
".
+ $r->print(" in scanline $i ".
$line." \n");
}
+ my $message="The ID on the form is ".
+ $$scan_record{'scantron.ID'}." \n".
+ "The name on the paper is ".
+ $$scan_record{'scantron.LastName'}.",".
+ $$scan_record{'scantron.FirstName'}."
";
+
$r->print(' '."\n");
$r->print(' '."\n");
if ($error =~ /ID$/) {
- if ($error eq 'unknownID') {
+ if ($error eq 'incorrectID') {
$r->print("The encoded ID is not in the classlist\n");
} elsif ($error eq 'duplicateID') {
$r->print("The encoded ID has also been used by a previous paper $arg\n");
}
- $r->print("Original ID is ".$$scan_record{'scantron.ID'}.
- " \n");
- $r->print("Name on paper is ".$$scan_record{'scantron.LastName'}.",".
- $$scan_record{'scantron.FirstName'}."
");
- $r->print("Please correct \n");
- $r->print("\n
");
+
+}
+
+sub scantron_bubble_selector {
+ my ($r,$scan_config,$quest,@selected)=@_;
+ my $max=$$scan_config{'Qlength'};
+
+ my $scmode=$$scan_config{'Qon'};
+ if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }
+
+ my @alphabet=('A'..'Z');
+ $r->print("');
+}
+
+sub num_matches {
+ my ($orig,$code) = @_;
+ my @code=split(//,$code);
+ my @orig=split(//,$orig);
+ my $same=0;
+ for (my $i=0;$iprint(" Skip this scanline saving it for later ");
- $r->print("\n ");
- &scantron_end_validate_form($r);
+ if (!$cdom) {
+ $cdom =$env{'course.'.$env{'request.course.id'}.'.domain'};
+ }
+ if (!$cnum) {
+ $cnum =$env{'course.'.$env{'request.course.id'}.'.num'};
+ }
+ my %result=&Apache::lonnet::get('CODEs',[$old_name,"type\0$old_name"],
+ $cdom,$cnum);
+ my %allcodes;
+ if ($result{"type\0$old_name"} eq 'number') {
+ %allcodes=map {($_,1)} split(',',$result{$old_name});
+ } else {
+ %allcodes=map {(&Apache::lonprintout::num_to_letters($_),1)} split(',',$result{$old_name});
+ }
+ return %allcodes;
}
sub scantron_validate_CODE {
my ($r,$currentphase) = @_;
- #FIXME doesn't do anything yet
+ my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+ if ($scantron_config{'CODElocation'} &&
+ $scantron_config{'CODEstart'} &&
+ $scantron_config{'CODElength'}) {
+ if (!defined($env{'form.scantron_CODElist'})) {
+ &FIXME_blow_up()
+ }
+ } else {
+ return (0,$currentphase+1);
+ }
+
+ my %usedCODEs;
+
+ my %allcodes=&get_codes();
+
+ my ($scanlines,$scan_data)=&scantron_getfile();
+ for (my $i=0;$i<=$scanlines->{'count'};$i++) {
+ my $line=&scantron_get_line($scanlines,$scan_data,$i);
+ if ($line=~/^[\s\cz]*$/) { next; }
+ my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
+ $scan_data);
+ my $CODE=$$scan_record{'scantron.CODE'};
+ my $error=0;
+ if (!&Apache::lonnet::validCODE($CODE)) {
+ &scantron_get_correction($r,$i,$scan_record,
+ \%scantron_config,
+ $line,'incorrectCODE',\%allcodes);
+ return(1,$currentphase);
+ }
+ if (%allcodes && !exists($allcodes{$CODE})
+ && !$$scan_record{'scantron.useCODE'}) {
+ &scantron_get_correction($r,$i,$scan_record,
+ \%scantron_config,
+ $line,'incorrectCODE',\%allcodes);
+ return(1,$currentphase);
+ }
+ if (exists($usedCODEs{$CODE})
+ && $env{'form.scantron_CODEunique'} eq 'yes'
+ && !$$scan_record{'scantron.CODE_ignore_dup'}) {
+ &scantron_get_correction($r,$i,$scan_record,
+ \%scantron_config,
+ $line,'duplicateCODE',$usedCODEs{$CODE});
+ return(1,$currentphase);
+ }
+ push (@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});
+ }
return (0,$currentphase+1);
}
@@ -3638,38 +5616,92 @@ sub scantron_validate_doublebubble {
my %idmap=&username_to_idmap($classlist);
#get scantron line setup
- my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'});
- my $scanlines=&scantron_getfile();
+ my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+ my ($scanlines,$scan_data)=&scantron_getfile();
for (my $i=0;$i<=$scanlines->{'count'};$i++) {
- my $line=&scantron_get_line($scanlines,$i);
- if (!$line) { next; }
- my $scan_record=&scantron_parse_scanline($line,\%scantron_config);
+ my $line=&scantron_get_line($scanlines,$scan_data,$i);
+ if ($line=~/^[\s\cz]*$/) { next; }
+ my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
+ $scan_data);
if (!defined($$scan_record{'scantron.doubleerror'})) { next; }
- &scantron_get_correction($r,$i,$scan_record,$line,'double',
+ &scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line,
+ 'doublebubble',
$$scan_record{'scantron.doubleerror'});
return (1,$currentphase);
}
return (0,$currentphase+1);
}
-sub scantron_end_validate_form {
- my ($r) = @_;
- $r->print(' ');
+sub scantron_get_maxbubble {
+ if (defined($env{'form.scantron_maxbubble'}) &&
+ $env{'form.scantron_maxbubble'}) {
+ return $env{'form.scantron_maxbubble'};
+ }
+
+ my $navmap=Apache::lonnavmaps::navmap->new();
+ my (undef,undef,$sequence)=
+ &Apache::lonnet::decode_symb($env{'form.selectpage'});
+
+ my $map=$navmap->getResourceByUrl($sequence);
+ my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
+
+ &Apache::lonxml::clear_problem_counter();
+
+ foreach my $resource (@resources) {
+ my $result=&Apache::lonnet::ssi($resource->src(),
+ ('symb' => $resource->symb()));
+ }
+ &Apache::lonnet::delenv('scantron\.');
+ $env{'form.scantron_maxbubble'} =
+ &Apache::lonxml::get_problem_counter()-1;
+
+ return $env{'form.scantron_maxbubble'};
+}
+
+sub scantron_validate_missingbubbles {
+ my ($r,$currentphase) = @_;
+ #get student info
+ my $classlist=&Apache::loncoursedata::get_classlist();
+ my %idmap=&username_to_idmap($classlist);
+
+ #get scantron line setup
+ my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+ my ($scanlines,$scan_data)=&scantron_getfile();
+ my $max_bubble=&scantron_get_maxbubble();
+ if (!$max_bubble) { $max_bubble=2**31; }
+ for (my $i=0;$i<=$scanlines->{'count'};$i++) {
+ my $line=&scantron_get_line($scanlines,$scan_data,$i);
+ if ($line=~/^[\s\cz]*$/) { next; }
+ my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
+ $scan_data);
+ if (!defined($$scan_record{'scantron.missingerror'})) { next; }
+ my @to_correct;
+ foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {
+ if ($missing > $max_bubble) { next; }
+ push(@to_correct,$missing);
+ }
+ if (@to_correct) {
+ &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
+ $line,'missingbubble',\@to_correct);
+ return (1,$currentphase);
+ }
+
+ }
+ return (0,$currentphase+1);
}
sub scantron_process_students {
my ($r) = @_;
- my (undef,undef,$sequence)=split(/___/,$ENV{'form.selectpage'});
- my ($symb,$url)=&get_symb_and_url($r);
+ my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
+ my ($symb)=&get_symb($r);
if (!$symb) {return '';}
- my $default_form_data=&defaultFormData($symb,$url);
+ my $default_form_data=&defaultFormData($symb);
- my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'});
- my $scanlines=Apache::File->new($Apache::lonnet::perlvar{'lonScansDir'}."/$ENV{'form.scantron_selectfile'}");
- my @scanlines=<$scanlines>;
+ my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+ my ($scanlines,$scan_data)=&scantron_getfile();
my $classlist=&Apache::loncoursedata::get_classlist();
my %idmap=&username_to_idmap($classlist);
- my $navmap=Apache::lonnavmaps::navmap->new($ENV{'request.course.fn'}.'.db',$ENV{'request.course.fn'}.'_parms.db',1, 1);
+ my $navmap=Apache::lonnavmaps::navmap->new();
my $map=$navmap->getResourceByUrl($sequence);
my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
# $r->print("geto ".scalar(@resources)." ");
@@ -3683,111 +5715,232 @@ SCANTRONFORM
my @delayqueue;
my %completedstudents;
- my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,
- 'Scantron Status','Scantron Progress',scalar(@scanlines));
+ my $count=&get_todo_count($scanlines,$scan_data);
+ my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron Status',
+ 'Scantron Progress',$count,
+ 'inline',undef,'scantronupload');
&Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
'Processing first student');
my $start=&Time::HiRes::time();
- foreach my $line (@scanlines) {
- $r->print('line is'.$line.' ');
+ my $i=-1;
+ my ($uname,$udom,$started);
+ while ($i<$scanlines->{'count'}) {
+ ($uname,$udom)=('','');
+ $i++;
+ my $line=&scantron_get_line($scanlines,$scan_data,$i);
+ if ($line=~/^[\s\cz]*$/) { next; }
+ if ($started) {
+ &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
+ 'last student');
+ }
+ $started=1;
+ my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
+ $scan_data);
+ unless ($uname=&scantron_find_student($scan_record,$scan_data,
+ \%idmap,$i)) {
+ &scantron_add_delay(\@delayqueue,$line,
+ 'Unable to find a student that matches',1);
+ next;
+ }
+ if (exists $completedstudents{$uname}) {
+ &scantron_add_delay(\@delayqueue,$line,
+ 'Student '.$uname.' has multiple sheets',2);
+ next;
+ }
+ ($uname,$udom)=split(/:/,$uname);
- chomp($line);
- my $scan_record=&scantron_parse_scanline($line,\%scantron_config);
- my ($uname,$udom);
- unless ($uname=&scantron_find_student($scan_record,\%idmap)) {
- &scantron_add_delay(\@delayqueue,$line,
- 'Unable to find a student that matches',1);
- next;
- }
- if (exists $completedstudents{$uname}) {
- &scantron_add_delay(\@delayqueue,$line,
- 'Student '.$uname.' has multiple sheets',2);
- next;
+ &Apache::lonxml::clear_problem_counter();
+ &Apache::lonnet::appenv(%$scan_record);
+
+ if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
+ &scantron_putfile($scanlines,$scan_data);
}
- $r->print('doing studnet'.$uname.' ');
- ($uname,$udom)=split(/:/,$uname);
- &Apache::lonnet::delenv('form.counter');
- &Apache::lonnet::appenv(%$scan_record);
-# &Apache::lonhomework::showhash(%ENV);
-# $Apache::lonxml::debug=1;
-# &Apache::lonxml::debug("line is $line");
- my $i=0;
+ my $i=0;
foreach my $resource (@resources) {
$i++;
- my $result=&Apache::lonnet::ssi($resource->src(),
- ('submitted' =>'scantron',
- 'grade_target' =>'grade',
- 'grade_username'=>$uname,
- 'grade_domain' =>$udom,
- 'grade_courseid'=>$ENV{'request.course.id'},
- 'grade_symb' =>$resource->symb()));
-# my %score=&Apache::lonnet::restore($resource->symb(),
-# $ENV{'request.course.id'},
-# $udom,$uname);
-# foreach my $part ($resource->{PARTS}) {
-# if ($score{'resource.'.$part.'.solved'} =~ /^correct/) {
-# $studentcorrect++;
-# $totalcorrect++;
-# } else {
-# $studentincorrect++;
-# $totalincorrect++;
-# }
-# }
-# $r->print(''.
-# $resource->symb().'-'.
-# $resource->src().'-'.' result is'.$result);
-# &Apache::lonhomework::showhash(%score);
- # if ($i eq 3) {last;}
+ my %form=('submitted' =>'scantron',
+ 'grade_target' =>'grade',
+ 'grade_username'=>$uname,
+ 'grade_domain' =>$udom,
+ 'grade_courseid'=>$env{'request.course.id'},
+ 'grade_symb' =>$resource->symb());
+ if (exists($scan_record->{'scantron.CODE'})
+ &&
+ &Apache::lonnet::validCODE($scan_record->{'scantron.CODE'})) {
+ $form{'CODE'}=$scan_record->{'scantron.CODE'};
+ } else {
+ $form{'CODE'}='';
+ }
+ my $result=&Apache::lonnet::ssi($resource->src(),%form);
+ if ($result ne '') {
+ &Apache::lonnet::logthis("scantron grading error -> $result");
+ &Apache::lonnet::logthis("scantron grading error info name $uname domain $udom course $env{'request.course.id'} url ".$resource->src());
+ }
+ if (&Apache::loncommon::connection_aborted($r)) { last; }
}
$completedstudents{$uname}={'line'=>$line};
+ if (&Apache::loncommon::connection_aborted($r)) { last; }
} continue {
- &Apache::lonnet::delenv('form.counter');
+ &Apache::lonxml::clear_problem_counter();
&Apache::lonnet::delenv('scantron\.');
- &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
- 'last student');
- #last;
- #FIXME
- #get iterator for $sequence
- #foreach question 'submit' the students answer to the server
- # through grade target {
- # generate data to pass back that includes grade recevied
- #}
}
&Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
- my $lasttime = &Time::HiRes::time()-$start;
- $r->print("took $lasttime
");
+# my $lasttime = &Time::HiRes::time()-$start;
+# $r->print("took $lasttime
");
- #$Apache::lonxml::debug=0;
- foreach my $delay (@delayqueue) {
- #FIXME
- #print out each delayed student with interface to select how
- # to repair student provided info
- #Expected errors include
- # 1 bad/no stuid/username
- # 2 invalid bubblings
-
+ $r->print("");
+ $r->print(&show_grading_menu_form($symb));
+ return '';
+}
+
+sub scantron_upload_scantron_data {
+ my ($r)=@_;
+ $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}));
+ my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
+ 'domainid',
+ 'coursename');
+ my $domsel=&Apache::loncommon::select_dom_form($env{'request.role.domain'},
+ 'domainid');
+ my $default_form_data=&defaultFormData(&get_symb($r,1));
+ $r->print(<
+ function checkUpload(formname) {
+ if (formname.upfile.value == "") {
+ alert("Please use the browse button to select a file from your local directory.");
+ return false;
+ }
+ formname.submit();
+ }
+
+
+
+$default_form_data
+
+
+
+
+UPLOAD
+ return '';
+}
+
+sub scantron_upload_scantron_data_save {
+ my($r)=@_;
+ my ($symb)=&get_symb($r,1);
+ my $doanotherupload=
+ ''."\n".
+ ' '."\n".
+ ' '."\n".
+ ' '."\n";
+ if (!&Apache::lonnet::allowed('usc',$env{'form.domainid'}) &&
+ !&Apache::lonnet::allowed('usc',
+ $env{'form.domainid'}.'_'.$env{'form.courseid'})) {
+ $r->print("You are not allowed to upload Scantron data to the requested course. ");
+ if ($symb) {
+ $r->print(&show_grading_menu_form($symb));
+ } else {
+ $r->print($doanotherupload);
+ }
+ return '';
}
+ my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
+ $r->print("Doing upload to ".$coursedata{'description'}." ");
+ my $fname=$env{'form.upfile.filename'};
#FIXME
- # if delay queue exists 2 submits one to process delayed students one
- # to ignore delayed students, possibly saving the delay queue for later
-
- $navmap->untieHashes();
+ #copied from lonnet::userfileupload()
+ #make that function able to target a specified course
+ # Replace Windows backslashes by forward slashes
+ $fname=~s/\\/\//g;
+ # Get rid of everything but the actual filename
+ $fname=~s/^.*\/([^\/]+)$/$1/;
+ # Replace spaces by underscores
+ $fname=~s/\s+/\_/g;
+ # Replace all other weird characters by nothing
+ $fname=~s/[^\w\.\-]//g;
+ # See if there is anything left
+ unless ($fname) { return 'error: no uploaded file'; }
+ my $uploadedfile=$fname;
+ $fname='scantron_orig_'.$fname;
+ if (length($env{'form.upfile'}) < 2) {
+ $r->print("Error: The file you attempted to upload, ".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')." , contained no information. Please check that you entered the correct filename.");
+ } else {
+ my $result=&Apache::lonnet::finishuserfileupload($env{'form.courseid'},$env{'form.domainid'},'upfile',$fname);
+ if ($result =~ m|^/uploaded/|) {
+ $r->print("Success: Successfully uploaded ".(length($env{'form.upfile'})-1)." bytes of data into location ".$result." ");
+ } else {
+ $r->print("Error: An error (".$result.") occurred when attempting to upload the file, ".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')." ");
+ }
+ }
+ if ($symb) {
+ $r->print(&scantron_selectphase($r,$uploadedfile));
+ } else {
+ $r->print($doanotherupload);
+ }
+ return '';
+}
+
+sub valid_file {
+ my ($requested_file)=@_;
+ foreach my $filename (sort(&scantron_filenames())) {
+ if ($requested_file eq $filename) { return 1; }
+ }
+ return 0;
}
+
+sub scantron_download_scantron_data {
+ my ($r)=@_;
+ my $default_form_data=&defaultFormData(&get_symb($r,1));
+ my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
+ my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
+ my $file=$env{'form.scantron_selectfile'};
+ if (! &valid_file($file)) {
+ $r->print(<
+ The requested file name was invalid.
+
+ERROR
+ $r->print(&show_grading_menu_form(&get_symb($r,1)));
+ return;
+ }
+ my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file;
+ my $corrected='/uploaded/'.$cdom.'/'.$cname.'/scantron_corrected_'.$file;
+ my $skipped='/uploaded/'.$cdom.'/'.$cname.'/scantron_skipped_'.$file;
+ &Apache::lonnet::allowuploaded('/adm/grades',$orig);
+ &Apache::lonnet::allowuploaded('/adm/grades',$corrected);
+ &Apache::lonnet::allowuploaded('/adm/grades',$skipped);
+ $r->print(<
+ Original file as uploaded by the scantron office.
+
+
+ Corrections , a file of corrected records that were used in grading.
+
+
+ Skipped , a file of records that were skipped.
+
+DOWNLOAD
+ $r->print(&show_grading_menu_form(&get_symb($r,1)));
+ return '';
+}
+
#-------- end of section for handling grading scantron forms -------
#
#-------------------------------------------------------------------
-
#-------------------------- Menu interface -------------------------
#
#--- Show a Grading Menu button - Calls the next routine ---
sub show_grading_menu_form {
- my ($symb,$url)=@_;
+ my ($symb)=@_;
my $result.=''."\n".
' '."\n".
- ' '."\n".
- ' '."\n".
+ ' '."\n".
' '."\n".
' '."\n".
' '."\n";
@@ -3797,8 +5950,8 @@ sub show_grading_menu_form {
# -- Retrieve choices for grading form
sub savedState {
my %savedState = ();
- if ($ENV{'form.saveState'}) {
- foreach (split(/:/,$ENV{'form.saveState'})) {
+ if ($env{'form.saveState'}) {
+ foreach (split(/:/,$env{'form.saveState'})) {
my ($key,$value) = split(/=/,$_,2);
$savedState{$key} = $value;
}
@@ -3809,7 +5962,7 @@ sub savedState {
#--- Displays the main menu page -------
sub gradingmenu {
my ($request) = @_;
- my ($symb,$url)=&get_symb_and_url($request);
+ my ($symb)=&get_symb($request);
if (!$symb) {return '';}
my $probTitle = &Apache::lonnet::gettitle($symb);
@@ -3825,12 +5978,13 @@ sub gradingmenu {
}
formname.command.value = cmd;
formname.saveState.value = "saveCmd="+cmdsave+":saveSec="+pullDownSelection(formname.section)+
- ":saveSub="+radioSelection(formname.submitonly)+":saveStatus="+pullDownSelection(formname.Status);
+ ":saveSub="+pullDownSelection(formname.submitonly)+":saveStatus="+pullDownSelection(formname.Status);
if (val < 5) formname.submit();
if (val == 5) {
if (!checkReceiptNo(formname,'notOK')) { return false;}
formname.submit();
}
+ if (val < 7) formname.submit();
}
function checkReceiptNo(formname,nospace) {
@@ -3849,8 +6003,8 @@ sub gradingmenu {
GRADINGMENUJS
&commonJSfunctions($request);
- my $result=' Manual Grading/View Submission ';
- my ($table,undef,$hdgrade) = &showResourceInfo($url,$probTitle);
+ my $result=' Manual Grading/View Submission ';
+ my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
$result.=$table;
my (undef,$sections) = &getclasslist('all','0');
my $savedState = &savedState();
@@ -3861,7 +6015,6 @@ GRADINGMENUJS
$result.=''."\n".
' '."\n".
- ' '."\n".
' '."\n".
' '."\n".
' '."\n".
@@ -3869,46 +6022,50 @@ GRADINGMENUJS
' '."\n".
' '."\n";
- $result.=''."\n".
- ''."\n".
+ $result.=''."\n".
+ ''."\n".
' Select a Grading/Viewing Option '."\n".
''."\n";
- $result.='';
+ $result.=''."\n".
- '
'."\n";
+ '
'."\n";
+ return $result;
+}
+
+sub reset_perm {
+ undef(%perm);
+}
+
+sub init_perm {
+ &reset_perm();
+ foreach my $test_perm ('vgr','mgr','opa') {
+
+ my $scope = $env{'request.course.id'};
+ if (!($perm{$test_perm}=&Apache::lonnet::allowed($test_perm,$scope))) {
+
+ $scope .= '/'.$env{'request.course.sec'};
+ if ( $perm{$test_perm}=
+ &Apache::lonnet::allowed($test_perm,$scope)) {
+ $perm{$test_perm.'_section'}=$env{'request.course.sec'};
+ } else {
+ delete($perm{$test_perm});
+ }
+ }
+ }
+}
+
+sub gather_clicker_ids {
+ my %clickerids=();
+
+ my $classlist = &Apache::loncoursedata::get_classlist();
+
+ # Set up a couple variables.
+ my $usernameidx = &Apache::loncoursedata::CL_SNAME();
+ my $domainidx = &Apache::loncoursedata::CL_SDOM();
+
+ foreach my $student (keys %$classlist) {
+
+ my $username = $classlist->{$student}->[$usernameidx];
+ my $domain = $classlist->{$student}->[$domainidx];
+ my $clickers =
+ (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1];
+ foreach my $id (split(/\,/,$clickers)) {
+ if (exists($clickerids{$id})) {
+ $clickerids{$id}.=','.$username.':'.$domain;
+ } else {
+ $clickerids{$id}=$username.':'.$domain;
+ }
+ }
+ }
+ return %clickerids;
+}
+
+sub gather_adv_clicker_ids {
+ my %clickerids=();
+ my $cnum=$env{'course.'.$env{'request.course.id'}.'.num'};
+ my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
+ my %coursepersonnel=&Apache::lonnet::get_course_adv_roles($cdom.'/'.$cnum);
+ foreach my $element (sort keys %coursepersonnel) {
+ foreach my $person (split(/\,/,$coursepersonnel{$element})) {
+ my ($puname,$pudom)=split(/\:/,$person);
+ my $clickers =
+ (&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1];
+ foreach my $id (split(/\,/,$clickers)) {
+ if (exists($clickerids{$id})) {
+ $clickerids{$id}.=','.$puname.':'.$pudom;
+ } else {
+ $clickerids{$id}=$puname.':'.$pudom;
+ }
+ }
+ }
+ }
+ return %clickerids;
+}
+
+sub process_clicker {
+ my ($r)=@_;
+ my ($symb)=&get_symb($r);
+ if (!$symb) {return '';}
+ my $result=&checkforfile_js();
+ $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
+ my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});
+ $result.=$table;
+ $result.=''."\n";
+ $result.=''."\n".
+ '
'."\n";
+ $result.=&show_grading_menu_form($symb);
return $result;
}
+sub process_clicker_file {
+ my ($r)=@_;
+ my ($symb)=&get_symb($r);
+ if (!$symb) {return '';}
+ my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
+ if (($env{'form.gradingmechanism'} eq 'specific') && ($env{'form.specificid'}!~/\w/)) {
+ $result.=''.&mt('You need to specify a clicker ID for the correct answer').' ';
+ return $result.&show_grading_menu_form($symb);
+ }
+ my %clickerids=&gather_clicker_ids();
+ my %correctids=();
+ if ($env{'form.gradingmechanism'} eq 'personnel') {
+ %correctids=&gather_adv_clicker_ids();
+ }
+ if ($env{'form.gradingmechanism'} eq 'specific') {
+ my $correctid=$env{'form.specificid'};
+ $correctid=~tr/a-z/A-Z/;
+ $correctid=~s/\s//gs;
+ $correctids{$correctid}='specified';
+ }
+ if ($env{'form.gradingmechanism'} eq 'attendance') {
+ $result.=&mt('Score based on attendance only');
+ } else {
+ my $number=0;
+ $result.=''.&mt('Correctness determined by the following IDs').' ';
+ foreach my $id (sort(keys(%correctids))) {
+ $result.=''.$id.' - ';
+ if ($correctids{$id} eq 'specified') {
+ $result.=&mt('specified');
+ } else {
+ my ($uname,$udom)=split(/\:/,$correctids{$id});
+ $result.=&Apache::loncommon::plainname($uname,$udom);
+ }
+ $result.=' ';
+ $number++;
+ }
+ if ($number==0) {
+ $result.=''.&mt('No IDs found to determine correct answer').' ';
+ return $result.&show_grading_menu_form($symb);
+ }
+ }
+ return $result.&show_grading_menu_form($symb);
+}
+
sub handler {
my $request=$_[0];
- undef(%perm);
- if ($ENV{'browser.mathml'}) {
- $request->content_type('text/xml');
+ &reset_perm();
+ if ($env{'browser.mathml'}) {
+ &Apache::loncommon::content_type($request,'text/xml');
} else {
- $request->content_type('text/html');
+ &Apache::loncommon::content_type($request,'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 $symb=&get_symb($request,1);
+ my @commands=&Apache::loncommon::get_env_multiple('form.command');
+ my $command=$commands[0];
+ if ($#commands > 0) {
+ &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
+ }
+ $request->print(&Apache::loncommon::start_page('Grading'));
+ if ($symb eq '' && $command 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);
+ my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb);
if (&Apache::lonnet::allowed('mgr',$tcrsid)) {
$request->print(&Apache::lonnet::ssi_body('/res/'.$url,
('grade_username' => $tuname,
@@ -3987,23 +6353,9 @@ sub handler {
}
}
} else {
- if (!($perm{'vgr'}=&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'}))) {
- if ($perm{'vgr'}=&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'}.'/'.$ENV{'request.course.sec'})) {
- $perm{'vgr_section'}=$ENV{'request.course.sec'};
- } else {
- delete($perm{'vgr'});
- }
- }
- if (!($perm{'mgr'}=&Apache::lonnet::allowed('mgr',$ENV{'request.course.id'}))) {
- if ($perm{'mgr'}=&Apache::lonnet::allowed('mgr',$ENV{'request.course.id'}.'/'.$ENV{'request.course.sec'})) {
- $perm{'mgr_section'}=$ENV{'request.course.sec'};
- } else {
- delete($perm{'mgr'});
- }
- }
-
+ &init_perm();
if ($command eq 'submission' && $perm{'vgr'}) {
- ($ENV{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0));
+ ($env{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0));
} elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) {
&pickStudentPage($request);
} elsif ($command eq 'displayPage' && $perm{'vgr'}) {
@@ -4022,54 +6374,56 @@ sub handler {
$request->print(&editgrades($request));
} elsif ($command eq 'verify' && $perm{'vgr'}) {
$request->print(&verifyreceipt($request));
+ } elsif ($command eq 'processclicker' && $perm{'mgr'}) {
+ $request->print(&process_clicker($request));
+ } elsif ($command eq 'processclickerfile' && $perm{'mgr'}) {
+ $request->print(&process_clicker_file($request));
} elsif ($command eq 'csvform' && $perm{'mgr'}) {
$request->print(&upcsvScores_form($request));
} elsif ($command eq 'csvupload' && $perm{'mgr'}) {
$request->print(&csvupload($request));
} elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) {
$request->print(&csvuploadmap($request));
- } elsif ($command eq 'csvuploadassign' && $perm{'mgr'}) {
- if ($ENV{'form.associate'} ne 'Reverse Association') {
- $request->print(&csvuploadassign($request));
+ } elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) {
+ if ($env{'form.associate'} ne 'Reverse Association') {
+ $request->print(&csvuploadoptions($request));
} else {
- if ( $ENV{'form.upfile_associate'} ne 'reverse' ) {
- $ENV{'form.upfile_associate'} = 'reverse';
+ if ( $env{'form.upfile_associate'} ne 'reverse' ) {
+ $env{'form.upfile_associate'} = 'reverse';
} else {
- $ENV{'form.upfile_associate'} = 'forward';
+ $env{'form.upfile_associate'} = 'forward';
}
$request->print(&csvuploadmap($request));
}
+ } elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {
+ $request->print(&csvuploadassign($request));
} elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
$request->print(&scantron_selectphase($request));
+ } elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {
+ $request->print(&scantron_do_warning($request));
} elsif ($command eq 'scantron_validate' && $perm{'mgr'}) {
$request->print(&scantron_validate_file($request));
} elsif ($command eq 'scantron_process' && $perm{'mgr'}) {
$request->print(&scantron_process_students($request));
+ } elsif ($command eq 'scantronupload' &&
+ (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
+ &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
+ $request->print(&scantron_upload_scantron_data($request));
+ } elsif ($command eq 'scantronupload_save' &&
+ (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
+ &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
+ $request->print(&scantron_upload_scantron_data_save($request));
+ } elsif ($command eq 'scantron_download' &&
+ &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
+ $request->print(&scantron_download_scantron_data($request));
} elsif ($command) {
- $request->print("Access Denied");
+ $request->print("Access Denied ($command)");
}
}
- &send_footer($request);
+ $request->print(&Apache::loncommon::end_page());
return '';
}
-sub send_header {
- my ($request)= @_;
- $request->print(&Apache::lontexconvert::header());
-# $request->print("
-#");
- $request->print(&Apache::loncommon::bodytag('Grading'));
-}
-
-sub send_footer {
- my ($request)= @_;
- $request->print('