--- loncom/homework/grades.pm 2002/10/16 19:13:57 1.55
+++ loncom/homework/grades.pm 2003/09/27 01:59:10 1.130.2.1.2.4
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# The LON-CAPA Grading handler
#
-# $Id: grades.pm,v 1.55 2002/10/16 19:13:57 matthew Exp $
+# $Id: grades.pm,v 1.130.2.1.2.4 2003/09/27 01:59:10 albertel Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -31,6 +31,9 @@
# 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;
@@ -39,12 +42,18 @@ use Apache::style;
use Apache::lonxml;
use Apache::lonnet;
use Apache::loncommon;
+use Apache::lonhtmlcommon;
+use Apache::lonnavmaps;
use Apache::lonhomework;
use Apache::loncoursedata;
use Apache::lonmsg qw(:user_normal_msg);
use Apache::Constants qw(:common);
+use String::Similarity;
-# ----- These first few routines are general use routines.-----
+my %oldessays=();
+my %perm=();
+
+# ----- These first few routines are general use routines.----
#
# --- Retrieve the parts that matches stores_\d+ from the metadata file.---
sub getpartlist {
@@ -87,10 +96,23 @@ sub get_fullname {
return $fullname;
}
+#--- Format fullname, username:domain if different for display
+#--- Use anywhere where the student names are listed
+sub nameUserString {
+ my ($type,$fullname,$uname,$udom) = @_;
+ if ($type eq 'header') {
+ return ' Fullname (Username) ';
+ } else {
+ 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) = shift;
+ 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);
@@ -98,7 +120,9 @@ sub response_type {
if (/^\w+response_\w+.*/) {
my ($responsetype,$part) = split(/_/,$_,2);
my ($partid,$respid) = split(/_/,$part);
- $handgrade{$part} = $responsetype.':'.($allkeys =~ /parameter_$part\_handgrade/ ? 'yes' : 'no');
+ $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;
@@ -107,103 +131,177 @@ sub response_type {
return \@partlist,\%handgrade;
}
+#--- 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 %resptype = ();
+ my $hdgrade='no';
+ for (sort keys(%$handgrade)) {
+ my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_});
+ my $partID = (split(/_/))[0];
+ $resptype{$partID} = $responsetype;
+ $hdgrade = $handgrade if ($handgrade eq 'yes');
+ $result.='
Part '.$partID.'
'.
+ '
Type: '.$responsetype.'
';
+# '
Handgrade: '.$handgrade.'
';
+ }
+ $result.='
'."\n";
+ return $result,\%resptype,$hdgrade,$partlist,$handgrade;
+}
+
+#--- Clean response type for display
+#--- Currently filters option response type 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 $grayFont = '';
+ return '
'.
+ '
Answer
'.
+ (join '
',@ans).'
'.
+ '
'.$grayFont.'Option ID
'.$grayFont.
+ (join '
'.$grayFont,@IDs).'
'.
+ '
';
+ }
+ if ($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'});
+
+ 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.
+ }
+ return '
'.&keywords_highlight($answer).'
';
+ }
+ return $answer;
+}
+
+#-- A couple of common js functions
+sub commonJSfunctions {
+ my $request = shift;
+ $request->print(<
+ function radioSelection(radioButton) {
+ var selection=null;
+ if (radioButton.length > 1) {
+ for (var i=0; i 1) {
+ for (var i=0; i
+COMMONJSFUNCTIONS
+}
+
#--- Dumps the class list with usernames,list of sections,
#--- section, ids and fullnames for each user.
sub getclasslist {
- my ($getsec,$hideexpired) = @_;
- my $now = time;
- my %classlist=&Apache::lonnet::dump('classlist',
- $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
- $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
- my ($tmp) = keys(%classlist);
+ my ($getsec,$filterlist) = @_;
+ $getsec = $getsec eq '' ? 'all' : $getsec;
+ my $classlist=&Apache::loncoursedata::get_classlist();
# Bail out if we were unable to get the classlist
- return if ($tmp =~ /^(con_lost|error|no_such_host)/i);
-
- # codes to check for fields in the classlist
- # should contain end:start:id:section:fullname
- for (keys %classlist) {
- my (@fields) = split(/:/,$classlist{$_});
- %classlist = &reformat_classlist(\%classlist) if (scalar(@fields) <= 2);
- last;
- }
-
- my (@holdsec,@sections,%allids,%stusec,%fullname);
- foreach (keys(%classlist)) {
- my ($end,$start,$id,$section,$fullname)=split(/:/,$classlist{$_});
- # still a student?
- if (($hideexpired) && ($end) && ($end < $now)) {
- next;
+ 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->{$_}};
+ # filter students according to status selected
+ if ($filterlist && $ENV{'form.Status'} ne 'Any') {
+ if ($ENV{'form.Status'} ne $status) {
+ delete ($classlist->{$_});
+ next;
+ }
}
$section = ($section ne '' ? $section : 'no');
- push @holdsec,$section;
- if ($getsec eq 'all' || $getsec eq $section) {
- push (@{ $classlist{$getsec} }, $_);
- $allids{$_} =$id;
- $stusec{$_} =$section;
- $fullname{$_}=$fullname;
+ if (&canview($section)) {
+ if ($getsec eq 'all' || $getsec eq $section) {
+ $sections{$section}++;
+ $fullnames{$_}=$fullname;
+ } else {
+ delete($classlist->{$_});
+ }
+ } else {
+ delete($classlist->{$_});
}
}
my %seen = ();
- foreach my $item (@holdsec) {
- push (@sections, $item) unless $seen{$item}++;
- }
- return (\%classlist,\@sections,\%allids,\%stusec,\%fullname);
+ my @sections = sort(keys(%sections));
+ return ($classlist,\@sections,\%fullnames);
}
-# add id, section and fullname to the classlist.db
-# done to maintain backward compatibility with older versions
-sub reformat_classlist {
- my ($classlist) = shift;
- foreach (sort keys(%$classlist)) {
- my ($unam,$udom) = split(/:/);
- my $section = &Apache::lonnet::usection($udom,$unam,$ENV{'request.course.id'});
- my $fullname = &get_fullname ($unam,$udom);
- my %userid = &Apache::lonnet::idrget($udom,($unam));
- $$classlist{$_} = $$classlist{$_}.':'.$userid{$unam}.':'.$section.':'.$fullname;
- }
- my $putresult = &Apache::lonnet::put
- ('classlist',\%$classlist,
- $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
- $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
-
- return %$classlist;
-}
-
-#find user domain
-sub finduser {
- my ($name) = @_;
- my $domain = '';
- if ( $Apache::grades::viewgrades eq 'F' ) {
- my %classlist=&Apache::lonnet::dump('classlist',
- $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
- $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
- my (@fields) = grep /^$name:/, keys %classlist;
- ($name, $domain) = split(/:/,$fields[0]);
- return ($name,$domain);
- } else {
- return ($ENV{'user.name'},$ENV{'user.domain'});
+sub canmodify {
+ my ($sec)=@_;
+ if ($perm{'mgr'}) {
+ if (!defined($perm{'mgr_section'})) {
+ # can modify whole class
+ return 1;
+ } else {
+ if ($sec eq $perm{'mgr_section'}) {
+ #can modify the requested section
+ return 1;
+ } else {
+ # can't modify the request section
+ return 0;
+ }
+ }
}
+ #can't modify
+ return 0;
}
-#--- Prompts a user to enter a username.
-sub moreinfo {
- my ($request,$reason) = @_;
- $request->print("Unable to process request: $reason");
- if ( $Apache::grades::viewgrades eq 'F' ) {
- $request->print('');
+sub canview {
+ my ($sec)=@_;
+ if ($perm{'vgr'}) {
+ if (!defined($perm{'vgr_section'})) {
+ # can modify whole class
+ return 1;
+ } else {
+ if ($sec eq $perm{'vgr_section'}) {
+ #can modify the requested section
+ return 1;
+ } else {
+ # can't modify the request section
+ return 0;
+ }
+ }
}
- return '';
+ #can't modify
+ return 0;
}
#--- Retrieve the grade status of a student for all the parts
@@ -212,7 +310,7 @@ sub student_gradeStatus {
my %record = &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname);
my %partstatus = ();
foreach (@$partlist) {
- my ($status,$foo) = split(/_/,$record{"resource.$_.solved"},2);
+ my ($status,undef) = split(/_/,$record{"resource.$_.solved"},2);
$status = 'nothing' if ($status eq '');
$partstatus{$_} = $status;
my $subkey = "resource.$_.submitted_by";
@@ -236,6 +334,9 @@ sub jscriptNform {
$jscript.= ''."\n".
- 'Resource: '.$ENV{'form.url'}.'
'."\n";
+ 'Resource: '.$ENV{'form.probTitle'}.'
'."\n";
my ($string,$contents,$matches) = ('','',0);
- my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist('all','0');
-
+ my (undef,undef,$fullname) = &getclasslist('all','0');
+
foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {
my ($uname,$udom)=split(/\:/);
if ($receipt eq
@@ -306,144 +451,178 @@ sub verifyreceipt {
sub listStudents {
my ($request) = shift;
- my ($symb,$url) = &get_symb_and_url();
+ 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 $submitonly= $ENV{'form.submitonly'} eq '' ? 'all' : $ENV{'form.submitonly'};
- my $result;
- my ($partlist,$handgrade) = &response_type($url);
- for (sort keys(%$handgrade)) {
- my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_});
- $ENV{'form.handgrade'} = 'yes' if ($handgrade eq 'yes');
- $result.='
'."\n";
- $request->print($result);
+ $request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
}
$result=''."\n";
+ $result.=''."\n" if ($counter == 0);
my $ctr = 0;
while ($ctr < scalar(@partlist)) {
$result.='
'.
- ''."\n";
- if ($ENV{'form.handgrade'} eq 'yes') {
- $endform.=' '."\n";
- my $ntstu =''."\n";
- my $nsel = ($ENV{'form.NTSTU'} ne '' ? $ENV{'form.NTSTU'} : '1');
- $ntstu =~ s/
'."\n".
+ &show_grading_menu_form ($symb,$url);
+ my $msg = ' Number of records updated = '.$rec_update.
' for '.$count.' student'.($count <= 1 ? '' : 's').'. '.
'Total number of students = '.$ENV{'form.total'}.' ';
return $title.$msg.$result;
@@ -1976,19 +2481,8 @@ sub csvuploadmap_header {
$javascript=&csvupload_javascript_forward_associate();
}
- my $result='
';
- $result.='
Resource: '.$url.'
';
- my ($partlist,$handgrade) = &response_type($url);
- my ($resptype,$hdgrade)=('','no');
- for (sort keys(%$handgrade)) {
- my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_});
- $resptype = $responsetype;
- $hdgrade = $handgrade if ($handgrade eq 'yes');
- $result.='
Part '.(split(/_/))[0].'
'.
- '
Type: '.$responsetype.'
'.
- '
Handgrade: '.$handgrade.'
';
- }
- $result.='
';
+ my ($result) = &showResourceInfo($url,$ENV{'form.probTitle'});
+
$request->print(<
Uploading Class Grades
@@ -2008,13 +2502,15 @@ to this page if the data selected is ins
value="$ENV{'form.upfile_associate'}" />
+
+
ENDPICK
-return '';
+ return '';
}
@@ -2044,10 +2540,54 @@ sub csvuploadmap_footer {
ENDPICK
}
+sub upcsvScores_form {
+ my ($request) = shift;
+ my ($symb,$url)=&get_symb_and_url($request);
+ if (!$symb) {return '';}
+ my $result =<
+ 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();
+ }
+
+CSVFORMJS
+ $ENV{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
+ my ($table) = &showResourceInfo($url,$ENV{'form.probTitle'});
+ $result.=$table;
+ $result.='
'."\n";
+ $result.='
'."\n";
+ $result.=' Specify a file containing the class scores for current resource'.
+ '.
';
+ $studentTable.=&show_grading_menu_form($ENV{'form.symb'},$ENV{'form.url'});
+ my $grademsg=($changeflag == 0 ? 'No score was changed or updated.' :
+ 'The scores were changed for '.
+ $changeflag.' problem'.($changeflag == 1 ? '.' : 's.'));
+ $request->print($grademsg.$studentTable);
+
+ return '';
+}
+
+#-------- end of section for handling grading by page/sequence ---------
+#
+#-------------------------------------------------------------------
+
+#--------------------Scantron Grading-----------------------------------
+#
+#------ start of section for handling grading by page/sequence ---------
+
+sub defaultFormData {
+ my ($symb,$url)=@_;
+ return '
+ '."\n".
+ ''."\n".
+ ''."\n".
+ ''."\n";
+}
+
+sub getSequenceDropDown {
+ my ($request,$symb)=@_;
+ my $result='';
return $result;
}
-#--- Displays the main menu page -------
-sub gradingmenu {
- my ($request) = @_;
- my ($symb,$url)=&get_symb_and_url($request);
- if (!$symb) {return '';}
- my $result='
Select a Grading Method
';
- $result.='
';
- $result.='
Resource: '.$url.'
';
- my ($partlist,$handgrade) = &response_type($url);
- my ($resptype,$hdgrade)=('','no');
- for (sort keys(%$handgrade)) {
- my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_});
- $resptype = $responsetype;
- $hdgrade = $handgrade if ($handgrade eq 'yes');
- $result.='
Part '.(split(/_/))[0].'
'.
- '
Type: '.$responsetype.'
'.
- '
Handgrade: '.$handgrade.'
';
- }
- $result.='
';
- $result.=&view_edit_entire_class_form($symb,$url).' ';
- $result.=&upcsvScores_form($symb,$url).' ';
- $result.=&viewGradeaStu_form($symb,$url,$resptype,$hdgrade).' ';
- $result.=&verifyReceipt_form($symb,$url)
- if ((&Apache::lonnet::allowed('mgr',$ENV{'request.course.id'})) && ($symb));
-
+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 $result= '";
return $result;
}
-#--- Menu for grading a section or the whole class ---
-sub view_edit_entire_class_form {
- my ($symb,$url)=@_;
- my ($classlist,$sections) = &getclasslist('all','0');
- my $result.='
'."\n";
- $result.='
'."\n";
- $result.=' Grade Entire Section or Class
'."\n";
+ $result.=''."\n";
+
return $result;
}
-#--- Menu to upload a csv scores ---
-sub upcsvScores_form {
- my ($symb,$url) = @_;
+sub scantron_selectphase {
+ my ($r) = @_;
+ my ($symb,$url)=&get_symb_and_url($r);
if (!$symb) {return '';}
- my $result = ''."\n";
-
- $result.='
'."\n";
- $result.='
'."\n";
- $result.=' Specify a file containing the class scores for above resource
'."\n";
- $result.='
'."\n";
- my $upfile_select=&Apache::loncommon::upfile_select_html();
- $result.=<
-
-
-
-$upfile_select
-
+ 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 $format_selector=&scantron_scantab();
+ my $result;
+ #FIXME allow instructor to be able to download the scantron file
+ # and to upload it,
+ $result.= <
+
+ $default_form_data
+
+
+
+
+
+
+ Specify file location and which Folder/Sequence to grade
+
+
+
+
+ Sequence to grade: $sequence_selector
+
+
+
+
+ Filename of scoring office file: $file_selector
+
+
+
+
+ Format of data file: $format_selector
+
+
+
+
+
+
+
-ENDUPFORM
- $result.='
'."\n";
- $result.='
'."\n";
+$grading_menu_button
+SCANTRONFORM
+
return $result;
}
-#--- Handgrading problems ---
-sub viewGradeaStu_form {
- my ($symb,$url,$response,$handgrade) = @_;
- my ($classlist,$sections) = &getclasslist('all','0');
- my $result.='
'."\n";
- $result.='
'."\n";
- $result.=' ';
- if ($handgrade eq 'yes') {
- $result.="View/Grade ";
- } else {
- $result.="View ";
+sub get_scantron_config {
+ my ($which) = @_;
+ my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
+ my %config;
+ #FIXME probably should move to XML it has already gotten a bit much now
+ foreach my $line (<$fh>) {
+ my ($name,$descrip)=split(/:/,$line);
+ if ($name ne $which ) { next; }
+ chomp($line);
+ my @config=split(/:/,$line);
+ $config{'name'}=$config[0];
+ $config{'description'}=$config[1];
+ $config{'CODElocation'}=$config[2];
+ $config{'CODEstart'}=$config[3];
+ $config{'CODElength'}=$config[4];
+ $config{'IDstart'}=$config[5];
+ $config{'IDlength'}=$config[6];
+ $config{'Qstart'}=$config[7];
+ $config{'Qlength'}=$config[8];
+ $config{'Qoff'}=$config[9];
+ $config{'Qon'}=$config[10];
+ $config{'PaperID'}=$config[11];
+ $config{'PaperIDlength'}=$config[12];
+ $config{'FirstName'}=$config[13];
+ $config{'FirstNamelength'}=$config[14];
+ $config{'LastName'}=$config[15];
+ $config{'LastNamelength'}=$config[16];
+ last;
}
- $result.='an Individual Student\'s Submission
');
+ if (lc($id) eq lc($scanID)) {
+ #Apache->request->print('success');
+ return $$idmap{$id};
+ }
+ }
+ return undef;
+}
+
+sub scantron_filter {
+ my ($curres)=@_;
+ if (ref($curres) && $curres->is_problem() && !$curres->randomout) {
+ 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];
+ ($line,$err,$errmsg)=
+ &scantron_fixup_scanline(\%scantron_config,$line,'ID',$newid);
+ }
+ if ($err) {
+ $r->print("Unable to accept last correction, an error occurred :$errmsg:");
+ } else {
+ &scantron_put_line($scanlines,$which,$line,$skip);
+ &scantron_putfile($scanlines);
+ }
+ }
+}
+
+sub scantron_validate_file {
+ my ($r) = @_;
+ my ($symb,$url)=&get_symb_and_url($r);
+ if (!$symb) {return '';}
+ my $default_form_data=&defaultFormData($symb,$url);
+
+ if ($ENV{'form.scantron_corrections'}) {
+ &scantron_process_corrections($r);
+ }
+ #get the student pick code ready
+ $r->print(&Apache::loncommon::studentbrowser_javascript());
+ my $result= <
+
+
+
+
+ $default_form_data
+SCANTRONFORM
+ $r->print($result);
+
+ my @validate_phases=( 'ID',
+ 'CODE',
+ 'doublebubble',
+ 'missingbubbles');
+ if (!$ENV{'form.validatepass'}) {
+ $ENV{'form.valiadatepass'} = 0;
+ }
+ my $currentphase=$ENV{'form.valiadatepass'};
+
+ if ($ENV{'form.scantron_selectfile'}=~m-^/-) {
+ #first pass copy file to classdir
+
+ }
+ my $stop=0;
+ while (!$stop && $currentphase < scalar(@validate_phases)) {
+ my $which="scantron_validate_".$validate_phases[$currentphase];
+ {
+ no strict 'refs';
+ ($stop,$currentphase)=&$which($r,$currentphase);
+ }
+ }
+ $r->print("");
+ return '';
+}
- $result.=' 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
+ 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
+ }
+ my %scanlines;
+ $scanlines{'orig'}=[split("\n",$lines)];
+ 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'});
+ if ($lines eq '-1') {
+ $scanlines{'corrected'}=[];
} else {
- $result.="View";
+ $scanlines{'corrected'}=[split("\n",$lines)];
}
- $result.='" />'."\n".'
'."\n";
- $result.='
'."\n";
- $result.='
'."\n";
+ $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'});
+ if ($lines eq '-1') {
+ $scanlines{'skipped'}=[];
+ } else {
+ $scanlines{'skipped'}=[split("\n",$lines)];
+ }
+ return \%scanlines;
+}
+
+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);
+
+}
+
+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_';
+# 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'});
+}
+
+sub scantron_get_line {
+ my ($scanlines,$i)=@_;
+ if ($scanlines->{'skipped'}[$i]) {return undef;}
+ if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$i];}
+ return $scanlines->{'orig'}[$i];
+}
+
+sub scantron_put_line {
+ my ($scanlines,$i,$newline,$skip)=@_;
+ if ($skip) { $scanlines->{'skipped'}[$i]=$newline;return; }
+ $scanlines->{'corrected'}[$i]=$newline;
+}
+
+sub scantron_validate_ID {
+ 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=&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 $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 ($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);
+ } else {
+ $found{'ids'}{$found}++;
+ }
+ } else {
+ &scantron_get_correction($r,$i,$scan_record,$line,
+ 'incorrectID');
+ return(1);
+ }
+ }
+
+ return (0,$currentphase+1);
+}
+
+sub scantron_get_correction {
+ my ($r,$i,$scan_record,$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 ".
+ $$scan_record{'scantron.PaperID'}." \n");
+ } else {
+ $r->print("The current scanline is
".
+ $line."
\n");
+ }
+ $r->print(''."\n");
+ $r->print(''."\n");
+ if ($error =~ /ID$/) {
+ if ($error eq 'unknownID') {
+ $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
Pick a specific user -- username:");
+ $r->print("\ndomain:".
+ &Apache::loncommon::select_dom_form(undef,'scantron_domain'));
+ #FIXME it would be nice if this sent back the user ID and
+ #could do partial userID matches
+ $r->print(&Apache::loncommon::selectstudent_link('scantronupload',
+ 'scantron_username','scantron_domain'));
+ } elsif ($error eq 'doublebubble') {
+ $r->print("There have been muttiple bubbles scanned for a single question\n");
+ foreach my $question (@{$arg}) {
+ my $selected=$$scan_record{"scantron.$question.answer"};
+ $r->print("
For question $question, selected bubbles were".
+ join(" ",split('',$selected)).
+ " Please pick which one should be used for grading");
+ #FIXMENEXT need to have radio buttons to chose which one to use
+
+ }
+ }
+ $r->print("
Skip this scanline saving it for later ");
+ $r->print("\n
");
+ &scantron_end_validate_form($r);
+}
+
+sub scantron_validate_CODE {
+ my ($r,$currentphase) = @_;
+ #FIXME doesn't do anything yet
+ return (0,$currentphase+1);
+}
+
+sub scantron_validate_doublebubble {
+ 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=&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);
+ if (!defined($$scan_record{'scantron.doubleerror'})) { next; }
+ &scantron_get_correction($r,$i,$scan_record,$line,'double',
+ $$scan_record{'scantron.doubleerror'});
+ return (1,$currentphase);
+ }
+ return (0,$currentphase+1);
+}
+
+sub scantron_end_validate_form {
+ my ($r) = @_;
+ $r->print('');
+}
+
+sub scantron_process_students {
+ my ($r) = @_;
+ my (undef,undef,$sequence)=split(/___/,$ENV{'form.selectpage'});
+ my ($symb,$url)=&get_symb_and_url($r);
+ if (!$symb) {return '';}
+ my $default_form_data=&defaultFormData($symb,$url);
+
+ 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 $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 $map=$navmap->getResourceByUrl($sequence);
+ my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
+# $r->print("geto ".scalar(@resources)." ");
+ my $result= <
+
+ $default_form_data
+SCANTRONFORM
+ $r->print($result);
+
+ my @delayqueue;
+ my %completedstudents;
+
+ my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,
+ 'Scantron Status','Scantron Progress',scalar(@scanlines));
+ &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
+ 'Processing first student');
+ my $start=&Time::HiRes::time();
+ foreach my $line (@scanlines) {
+ $r->print('
line is'.$line.'
');
+
+ 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;
+ }
+ $r->print('
result is'.$result);
+# &Apache::lonhomework::showhash(%score);
+ # if ($i eq 3) {last;}
+ }
+ $completedstudents{$uname}={'line'=>$line};
+ } continue {
+ &Apache::lonnet::delenv('form.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
");
+
+ #$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
+
+ }
+ #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();
+}
+#-------- 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 $result.='