");
- pDoc.write("");
- pDoc.write("Type<\\/b><\\/td> Include<\\/b><\\/td> Message<\\/td><\\/tr>");
+ pDoc.write('');
+ pDoc.write('
-
+
ENDPICK
}
sub checkforfile_js {
- my $result =<
+ my $alertmsg = &mt('Please use the browse button to select a file from your local directory.');
+ my $result = &Apache::lonhtmlcommon::scripttag(<
CSVFORMJS
return $result;
}
sub upcsvScores_form {
- my ($request) = shift;
- my ($symb)=&get_symb($request);
+ my ($request,$symb) = @_;
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";
- $result.='
'."\n";
- $result.=&show_grading_menu_form($symb);
+ &mt("How do I create a CSV file from a spreadsheet")).
+ ' '.
+ &Apache::loncommon::end_data_table_row().
+ &Apache::loncommon::end_data_table();
return $result;
}
sub csvuploadmap {
- my ($request)= @_;
- my ($symb)=&get_symb($request);
+ my ($request,$symb)= @_;
if (!$symb) {return '';}
my $datatoken;
@@ -3769,12 +4192,15 @@ sub csvuploadmap {
&Apache::loncommon::load_tmp_file($request);
}
my @records=&Apache::loncommon::upfile_record_sep();
- if ($env{'form.noFirstLine'}) { shift(@records); }
&csvuploadmap_header($request,$symb,$datatoken,$#records+1);
my ($i,$keyfields);
if (@records) {
- my @fields=&csvupload_fields($symb);
-
+ my $fieldserror;
+ my @fields=&csvupload_fields($symb,\$fieldserror);
+ if ($fieldserror) {
+ $request->print(&navmap_errormsg());
+ return;
+ }
if ($env{'form.upfile_associate'} eq 'reverse') {
&Apache::loncommon::csv_print_samples($request,\@records);
$i=&Apache::loncommon::csv_print_select_table($request,\@records,
@@ -3795,39 +4221,27 @@ sub csvuploadmap {
}
}
&csvuploadmap_footer($request,$i,$keyfields);
- $request->print(&show_grading_menu_form($symb));
return '';
}
sub csvuploadoptions {
- my ($request)= @_;
- my ($symb)=&get_symb($request);
- my $checked=(($env{'form.noFirstLine'})?'1':'0');
- my $ignore=&mt('Ignore First Line');
+ my ($request,$symb)= @_;
+ my $overwrite=&mt('Overwrite any existing score');
$request->print(<
-Uploading Class Grade Options
-
- Overwrite any existing score
+ $overwrite
ENDPICK
my %fields=&get_fields();
if (!defined($fields{'domain'})) {
my $domform = &Apache::loncommon::select_dom_form($env{'request.role.domain'},'default_domain');
- $request->print("\n Users are in domain: ".$domform."
\n");
+ $request->print("\n".&mt('Users are in domain: [_1]',$domform)."
\n");
}
foreach my $key (sort(keys(%env))) {
if ($key !~ /^form\.(.*)$/) { next; }
@@ -3840,7 +4254,6 @@ ENDPICK
# FIXME do a check for any invalid user ids?...
$request->print('
'."\n");
- $request->print(&show_grading_menu_form($symb));
return '';
}
@@ -3862,19 +4275,17 @@ sub get_fields {
}
sub csvuploadassign {
- my ($request)= @_;
- my ($symb)=&get_symb($request);
+ my ($request,$symb)= @_;
if (!$symb) {return '';}
my $error_msg = '';
&Apache::loncommon::load_tmp_file($request);
my @gradedata = &Apache::loncommon::upfile_record_sep();
- if ($env{'form.noFirstLine'}) { shift(@gradedata); }
my %fields=&get_fields();
- $request->print('Assigning Grades ');
my $courseid=$env{'request.course.id'};
my ($classlist) = &getclasslist('all',0);
my @notallowed;
my @skipped;
+ my @warnings;
my $countdone=0;
foreach my $grade (@gradedata) {
my %entries=&Apache::loncommon::record_sep($grade);
@@ -3923,6 +4334,9 @@ sub csvuploadassign {
my $pcr=$entries{$fields{$dest}} / $wgt;
my $award=($pcr == 0) ? 'incorrect_by_override'
: 'correct_by_override';
+ if ($pcr>1) {
+ push(@warnings,&mt("[_1]: point value larger than weight","$username:$domain"));
+ }
$grades{"resource.$part.awarded"}=$pcr;
$grades{"resource.$part.solved"}=$award;
$points{$part}=1;
@@ -3942,35 +4356,44 @@ sub csvuploadassign {
$grades{$store_key}=$entries{$fields{$dest}};
}
}
- if (! %grades) { push(@skipped,"$username:$domain no data to save"); }
- $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
- my $result=&Apache::lonnet::cstore(\%grades,$symb,
+ if (! %grades) {
+ push(@skipped,&mt("[_1]: no data to save","$username:$domain"));
+ } else {
+ $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
+ my $result=&Apache::lonnet::cstore(\%grades,$symb,
$env{'request.course.id'},
$domain,$username);
- if ($result eq 'ok') {
- $request->print('.');
- } else {
- $request->print("
-
- Failed to save student $username:$domain.
- Message when trying to save was ($result)
-
-
" );
- }
- $request->rflush();
- $countdone++;
+ if ($result eq 'ok') {
+# Successfully stored
+ $request->print('.');
+# Remove from grading queue
+ &Apache::bridgetask::remove_from_queue('gradingqueue',$symb,
+ $env{'course.'.$env{'request.course.id'}.'.domain'},
+ $env{'course.'.$env{'request.course.id'}.'.num'},
+ $domain,$username);
+ $countdone++;
+ } else {
+ $request->print("".
+ &mt("Failed to save data for student [_1]. Message when trying to save was: [_2]",
+ "$username:$domain",$result)."
");
+ }
+ $request->rflush();
+ }
+ }
+ $request->print(' '.&Apache::lonhtmlcommon::confirm_success(&mt("Saved scores for [quant,_1,student]",$countdone),$countdone==0));
+ if (@warnings) {
+ $request->print(' '.&Apache::lonhtmlcommon::confirm_success(&mt('Warnings generated for the following saved scores:'),1).' ');
+ $request->print(join(', ',@warnings));
}
- $request->print(" Saved $countdone students\n");
if (@skipped) {
- $request->print('
Skipped Students ');
- foreach my $student (@skipped) { $request->print("$student \n"); }
+ $request->print(' '.&Apache::lonhtmlcommon::confirm_success(&mt('No scores stored for the following username(s):'),1).' ');
+ $request->print(join(', ',@skipped));
}
if (@notallowed) {
- $request->print('Students Not Allowed to Modify
');
- foreach my $student (@notallowed) { $request->print("$student \n"); }
+ $request->print(' '.&Apache::lonhtmlcommon::confirm_success(&mt('Modification of scores not allowed for the following username(s):'),1).' ');
+ $request->print(join(', ',@notallowed));
}
$request->print(" \n");
- $request->print(&show_grading_menu_form($symb));
return $error_msg;
}
#------------- end of section for handling csv file upload ---------
@@ -3981,14 +4404,14 @@ sub csvuploadassign {
#
#--- Select a page/sequence and a student to grade
sub pickStudentPage {
- my ($request) = shift;
+ my ($request,$symb) = @_;
- $request->print(<
+ my $alertmsg = &mt('Please select the student you wish to grade.');
+ $request->print(&Apache::lonhtmlcommon::scripttag(<
LISTJAVASCRIPT
&commonJSfunctions($request);
- 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 $result=' '.
- 'Manual Grading by Page or Sequence ';
+ &mt('Manual Grading by Page or Sequence').'';
$result.=''."\n";
- $result.=' Problems from: '."\n";
- my ($titles,$symbx) = &getSymbMap();
+ my $map_error;
+ my ($titles,$symbx) = &getSymbMap($map_error);
+ if ($map_error) {
+ $request->print(&navmap_errormsg());
+ return;
+ }
my ($curpage) =&Apache::lonnet::decode_symb($symb);
# my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb);
# my $type=($curpage =~ /\.(page|sequence)/);
+ my $select = ''."\n";
my $ctr=0;
foreach (@$titles) {
my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
- $result.=''.$showtitle.' '."\n";
$ctr++;
}
- $result.= ' '." \n";
+ $select.= ' ';
+ $result.=' '.&mt('Problems from').': '.$select." \n";
+
$ctr=0;
foreach (@$titles) {
my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
@@ -4033,36 +4462,38 @@ LISTJAVASCRIPT
$result.=' '."\n".
' '."\n";
- $result.=' View Problems Text: no '."\n".
- ' yes '." \n";
-
- $result.=' Submission Details: '.
- ' none '."\n".
- ' by dates and submissions '."\n".
- ' all details '."\n";
+ my $options =
+ ' '.&mt('no').' '."\n".
+ ' '.&mt('yes').' '." \n";
+ $result.=' '.&mt('View Problem Text').': '.$options;
+
+ $options =
+ ' '.&mt('none').' '."\n".
+ ' '.&mt('by dates and submissions').' '."\n".
+ ' '.&mt('all details').' '."\n";
+ $result.=' '.&mt('Submissions').': '.$options;
$result.=&build_section_inputs();
my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
$result.=' '."\n".
' '."\n".
- ' '."\n".
- ' '." \n";
+ ' '." \n";
- $result.=' '.&mt('Use CODE:').' '.
- ' '."\n";
+ $result.=' '.&mt('Use CODE').': '."\n";
$result.=' " /> '."\n";
+ 'onclick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' →" /> '."\n";
$request->print($result);
- my $studentTable.=' Select a student you wish to grade and then click on the Next button. '.
- ''.
- ''.
- ' No. '.
- ''.&nameUserString('header').' '.
- ' No. '.
- ''.&nameUserString('header').' ';
+ my $studentTable.=' '.&mt('Select a student you wish to grade and then click on the Next button.').' '.
+ &Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_header_row().
+ ' '.&mt('No.').' '.
+ ''.&nameUserString('header').' '.
+ ' '.&mt('No.').' '.
+ ''.&nameUserString('header').' '.
+ &Apache::loncommon::end_data_table_header_row();
my (undef,undef,$fullname) = &getclasslist($getsec,'1');
my $ptr = 1;
@@ -4074,27 +4505,38 @@ LISTJAVASCRIPT
return $a cmp $b;
} (keys(%$fullname))) {
my ($uname,$udom) = split(/:/,$student);
- $studentTable.=($ptr%2 == 1 ? '' : '');
+ $studentTable.=($ptr%2==1 ? &Apache::loncommon::start_data_table_row()
+ : '');
$studentTable.=''.$ptr.' ';
$studentTable.=' '
.&nameUserString(undef,$$fullname{$student},$uname,$udom)." \n";
- $studentTable.=($ptr%2 == 0 ? ' ' : '');
+ $studentTable.=
+ ($ptr%2 == 0 ? ''.&Apache::loncommon::end_data_table_row()
+ : '');
$ptr++;
}
- $studentTable.=' ' if ($ptr%2 == 0);
- $studentTable.='
'."\n";
+ if ($ptr%2 == 0) {
+ $studentTable.=' '.
+ &Apache::loncommon::end_data_table_row();
+ }
+ $studentTable.=&Apache::loncommon::end_data_table()."\n";
$studentTable.=' " /> '."\n";
+ 'onclick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' →" />'."\n";
- $studentTable.=&show_grading_menu_form($symb);
$request->print($studentTable);
return '';
}
sub getSymbMap {
+ my ($map_error) = @_;
my $navmap = Apache::lonnavmaps::navmap->new();
-
+ unless (ref($navmap)) {
+ if (ref($map_error)) {
+ $$map_error = 'navmap';
+ }
+ return;
+ }
my %symbx = ();
my @titles = ();
my $minder = 0;
@@ -4117,9 +4559,7 @@ sub getSymbMap {
#
#--- Displays a page/sequence w/wo problems, w/wo submissions
sub displayPage {
- my ($request) = shift;
-
- my ($symb) = &get_symb($request);
+ my ($request,$symb) = @_;
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'};
@@ -4136,15 +4576,15 @@ sub displayPage {
&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));
+ $request->print(''.&mt('Unable to view requested student. ([_1])',$env{'form.student'}).' ');
return;
}
my $result=' '.$env{'form.title'}.' ';
- $result.=' Student: '.&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom).
+ $result.=' '.&mt('Student: [_1]',&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom)).
' '."\n";
- if (&Apache::lonnet::validCODE($env{'form.CODE'})) {
- $result.=' CODE: '.$env{'form.CODE'}.' '."\n";
+ $env{'form.CODE'} = uc($env{'form.CODE'});
+ if (&Apache::lonnet::validCODE(uc($env{'form.CODE'}))) {
+ $result.=' '.&mt('CODE: [_1]',$env{'form.CODE'}).' '."\n";
} else {
delete($env{'form.CODE'});
}
@@ -4152,11 +4592,14 @@ sub displayPage {
$request->print($result);
my $navmap = Apache::lonnavmaps::navmap->new();
+ unless (ref($navmap)) {
+ $request->print(&navmap_errormsg());
+ return;
+ }
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));
+ $request->print(''.&mt('Unable to view requested sequence. ([_1])',$resUrl).' ');
return;
}
my $iterator = $navmap->getIterator($map->map_start(),
@@ -4169,23 +4612,23 @@ sub displayPage {
' '."\n".
' '."\n".
' '."\n".
- ' '."\n".
- ' '."\n";
+ ' '."\n";
if (defined($env{'form.CODE'})) {
$studentTable.=
' '."\n";
}
my $checkIcon = ' ';
+ '" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
- $studentTable.=' Note: Problems graded correct by the computer are marked with a '.$checkIcon.
- ' symbol.'."\n".
- ' '.
- ''.
- ' Prob. '.
- ' '.($env{'form.vProb'} eq 'no' ? 'Title' : 'Problem Text').'/Grade ';
+ $studentTable.=' '.
+ &mt('Problems graded correct by the computer are marked with a [_1] symbol.',$checkIcon).
+ ' '."\n".
+ &Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_header_row().
+ ' Prob. '.
+ ' '.($env{'form.vProb'} eq 'no' ? &mt('Title') : &mt('Problem Text')).'/'.&mt('Grade').' '.
+ &Apache::loncommon::end_data_table_header_row();
&Apache::lonxml::clear_problem_counter();
my ($depth,$question,$prob) = (1,1,1);
@@ -4199,8 +4642,14 @@ sub displayPage {
my $parts = $curRes->parts();
my $title = $curRes->compTitle();
my $symbx = $curRes->symb();
- $studentTable.=''.$prob.
- (scalar(@{$parts}) == 1 ? '' : ' ('.scalar(@{$parts}).' parts)').' ';
+ $studentTable.=
+ &Apache::loncommon::start_data_table_row().
+ ''.$prob.
+ (scalar(@{$parts}) == 1 ? ''
+ : ' ('.&mt('[_1]parts)',
+ scalar(@{$parts}).' ')
+ ).
+ ' ';
$studentTable.='';
my %form = ('CODE' => $env{'form.CODE'},);
if ($env{'form.vProb'} eq 'yes' ) {
@@ -4215,14 +4664,14 @@ sub displayPage {
# $request->print('match='.$1." \n");
# }
# $companswer =~ s|||g;
- $studentTable.=' '.$title.' Correct answer: '.$companswer;
+ $studentTable.=' '.$title.' '.&mt('Correct answer').': '.$companswer;
}
my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
if ($env{'form.lastSub'} eq 'datesub') {
if ($record{'version'} eq '') {
- $studentTable.=' No recorded submission for this problem ';
+ $studentTable.=' '.&mt('No recorded submission for this problem.').' ';
} else {
my %responseType = ();
foreach my $partid (@{$parts}) {
@@ -4245,11 +4694,13 @@ sub displayPage {
}
if (&canmodify($usec)) {
+ $studentTable.=&gradeBox_start();
foreach my $partid (@{$parts}) {
$studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record);
$studentTable.=' '."\n";
$question++;
}
+ $studentTable.=&gradeBox_end();
$prob++;
}
$studentTable.='';
@@ -4258,11 +4709,11 @@ sub displayPage {
$curRes = $iterator->next();
}
- $studentTable.='
'."\n".
- ' '.
- ''."\n";
- $studentTable.=&show_grading_menu_form($symb);
+ $studentTable.=
+ '
'."\n".
+ ' '.
+ ''."\n";
$request->print($studentTable);
return '';
@@ -4273,76 +4724,111 @@ sub displaySubByDates {
my $isCODE=0;
my $isTask = ($symb =~/\.task$/);
if (exists($record->{'resource.CODE'})) { $isCODE=1; }
- my $studentTable=''.
- ''.
- 'Date/Time '.
- ($isCODE?'CODE ':'').
- 'Submission '.
- 'Status ';
+ my $studentTable=&Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_header_row().
+ ''.&mt('Date/Time').' '.
+ ($isCODE?''.&mt('CODE').' ':'').
+ ($isTask?''.&mt('Version').' ':'').
+ ''.&mt('Submission').' '.
+ ''.&mt('Status').' '.
+ &Apache::loncommon::end_data_table_header_row();
my ($version);
my %mark;
my %orders;
$mark{'correct_by_student'} = $checkIcon;
if (!exists($$record{'1:timestamp'})) {
- return ' Nothing submitted - no attempts ';
+ return ' '.&mt('Nothing submitted - no attempts.').' ';
}
my $interaction;
+ my $no_increment = 1;
+ my %lastrndseed;
for ($version=1;$version<=$$record{'version'};$version++) {
- my $timestamp = scalar(localtime($$record{$version.':timestamp'}));
+ my $timestamp =
+ &Apache::lonlocal::locallocaltime($$record{$version.':timestamp'});
if (exists($$record{$version.':resource.0.version'})) {
$interaction = $$record{$version.':resource.0.version'};
}
-
+ if ($isTask && $env{'form.previousversion'}) {
+ next unless ($interaction == $env{'form.previousversion'});
+ }
my $where = ($isTask ? "$version:resource.$interaction"
: "$version:resource");
- $studentTable.=''.$timestamp.' ';
+ $studentTable.=&Apache::loncommon::start_data_table_row().
+ ''.$timestamp.' ';
if ($isCODE) {
$studentTable.=''.$record->{$version.':resource.CODE'}.' ';
}
+ if ($isTask) {
+ $studentTable.=''.$interaction.' ';
+ }
my @versionKeys = split(/\:/,$$record{$version.':keys'});
my @displaySub = ();
foreach my $partid (@{$parts}) {
+ my ($hidden,$type);
+ $type = $$record{$version.':resource.'.$partid.'.type'};
+ if (($type eq 'anonsurvey') || ($type eq 'anonsurveycred')) {
+ $hidden = 1;
+ }
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 '');
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$/));
- $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'
+ $displaySub[0].='';
+ $displaySub[0].=''.&mt('Part: [_1]',$display_part).' '
+ .' '
+ .'('.&mt('Response ID: [_1]',$responseId).')'
+ .' '
+ .' ';
+ if ($hidden) {
+ $displaySub[0].= &mt('Anonymous Survey').' ';
+ } else {
+ my ($trial,$rndseed,$newvariation);
+ if ($type eq 'randomizetry') {
+ $trial = $$record{"$where.$partid.tries"};
+ $rndseed = $$record{"$where.$partid.rndseed"};
+ }
+ if ($$record{"$where.$partid.tries"} eq '') {
+ $displaySub[0].=&mt('Trial not counted');
+ } else {
+ $displaySub[0].=&mt('Trial: [_1]',
+ $$record{"$where.$partid.tries"});
+ if ($rndseed || $lastrndseed{$partid}) {
+ if ($rndseed ne $lastrndseed{$partid}) {
+ $newvariation = ' ('.&mt('New variation this try').')';
+ }
+ }
+ $lastrndseed{$partid} = $rndseed;
+ }
+ 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($orders{$partid})) { $orders{$partid}={}; }
+ if ((!exists($orders{$partid}->{$responseId})) || ($trial)) {
+ $orders{$partid}->{$responseId}=
+ &get_order($partid,$responseId,$symb,$uname,$udom,
+ $no_increment,$type,$trial,$rndseed);
+ }
+ $displaySub[0].=''.$newvariation.''; # /nobreak
+ $displaySub[0].=' '.
+ &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom,$type,$trial,$rndseed).' ';
+ }
}
}
if (exists($$record{"$where.$partid.checkedin"})) {
- $displaySub[1].='Checked in by '.
- $$record{"$where.$partid.checkedin"}.' into slot '.
- $$record{"$where.$partid.checkedin.slot"}.
- ' ';
+ $displaySub[1].=&mt('Checked in by [_1] into slot [_2]',
+ $$record{"$where.$partid.checkedin"},
+ $$record{"$where.$partid.checkedin.slot"}).
+ ' ';
}
if (exists $$record{"$where.$partid.award"}) {
- $displaySub[1].='Part: '.$display_part.' '.
+ $displaySub[1].=''.&mt('Part:').' '.$display_part.' '.
lc($$record{"$where.$partid.award"}).' '.
$mark{$$record{"$where.$partid.solved"}}.
' ';
@@ -4362,17 +4848,17 @@ sub displaySubByDates {
}
$studentTable.=''.$displaySub[0].' '.$displaySub[1];
if ($displaySub[2]) {
- $studentTable.='Manually graded by '.$displaySub[2];
+ $studentTable.=&mt('Manually graded by [_1]',$displaySub[2]);
}
- $studentTable.=' ';
-
+ $studentTable.=' '.
+ &Apache::loncommon::end_data_table_row();
}
- $studentTable.='
';
+ $studentTable.=&Apache::loncommon::end_data_table();
return $studentTable;
}
sub updateGradeByPage {
- my ($request) = shift;
+ my ($request,$symb) = @_;
my $cdom = $env{"course.$env{'request.course.id'}.domain"};
my $cnum = $env{"course.$env{'request.course.id'}.num"};
@@ -4382,34 +4868,38 @@ sub updateGradeByPage {
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'}));
+ $request->print(''.&mt('Unable to modify requested student ([_1])',$env{'form.student'}).' ');
return;
}
my $result=' '.$env{'form.title'}.' ';
- $result.=' Student: '.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
+ $result.=' '.&mt('Student: ').&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
' '."\n";
$request->print($result);
+
my $navmap = Apache::lonnavmaps::navmap->new();
+ unless (ref($navmap)) {
+ $request->print(&navmap_errormsg());
+ return;
+ }
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));
+ $request->print(''.&mt('Unable to grade requested sequence ([_1]).',$resUrl).' ');
return;
}
my $iterator = $navmap->getIterator($map->map_start(),
$map->map_finish());
- my $studentTable=' '.
- ''.
- ' Prob. '.
- ' Title '.
- ' Previous Score '.
- ' New Score ';
+ my $studentTable=
+ &Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_header_row().
+ ' '.&mt('Prob.').' '.
+ ' '.&mt('Title').' '.
+ ' '.&mt('Previous Score').' '.
+ ' '.&mt('New Score').' '.
+ &Apache::loncommon::end_data_table_header_row();
$iterator->next(); # skip the first BEGIN_MAP
my $curRes = $iterator->next(); # for "current resource"
@@ -4422,8 +4912,12 @@ sub updateGradeByPage {
my $parts = $curRes->parts();
my $title = $curRes->compTitle();
my $symbx = $curRes->symb();
- $studentTable.=''.$prob.
- (scalar(@{$parts}) == 1 ? '' : ' ('.scalar(@{$parts}).' parts)').' ';
+ $studentTable.=
+ &Apache::loncommon::start_data_table_row().
+ ''.$prob.
+ (scalar(@{$parts}) == 1 ? ''
+ : ' ('.&mt('[quant,_1,part]',scalar(@{$parts}))
+ .')').' ';
$studentTable.=' '.$title.' ';
my %newrecord=();
@@ -4467,10 +4961,10 @@ sub updateGradeByPage {
}
my $display_part=&get_display_part($partid,$curRes->symb());
my $oldstatus = $env{'form.solved'.$question.'_'.$partid};
- $displayPts[0].=' Part: '.$display_part.' = '.
+ $displayPts[0].=' '.&mt('Part').': '.$display_part.' = '.
(($oldstatus eq 'excused') ? 'excused' : $oldpts).
' ';
- $displayPts[1].=' Part: '.$display_part.' = '.
+ $displayPts[1].=' '.&mt('Part').': '.$display_part.' = '.
(($score eq 'excused') ? 'excused' : $newpts).
' ';
$question++;
@@ -4510,18 +5004,17 @@ sub updateGradeByPage {
$studentTable.=''.$displayPts[0].' '.
''.$displayPts[1].' '.
- ' ';
+ &Apache::loncommon::end_data_table_row();
$prob++;
}
$curRes = $iterator->next();
}
- $studentTable.='
';
- $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.'));
+ $studentTable.=&Apache::loncommon::end_data_table();
+ my $grademsg=($changeflag == 0 ? &mt('No score was changed or updated.') :
+ &mt('The scores were changed for [quant,_1,problem].',
+ $changeflag));
$request->print($grademsg.$studentTable);
return '';
@@ -4531,7 +5024,7 @@ sub updateGradeByPage {
#
#-------------------------------------------------------------------
-#--------------------Scantron Grading-----------------------------------
+#-------------------- Bubblesheet (Scantron) Grading -------------------
#
#------ start of section for handling grading by page/sequence ---------
@@ -4545,12 +5038,12 @@ sub updateGradeByPage {
from the file that we are parsing that represents one entire sheet
'bubble line' refers to the data
- representing the line of bubbles that are on the physical bubble sheet
+ representing the line of bubbles that are on the physical bubblesheet
-The overall process is that a scanned in bubble sheet data is uploaded
+The overall process is that a scanned in bubblesheet data is uploaded
into a course. When a user wants to grade, they select a
-sequence/folder of resources, a file of bubble sheet info, and pick
+sequence/folder of resources, a file of bubblesheet info, and pick
one of the predefined configurations for what each scanline looks
like.
@@ -4558,22 +5051,22 @@ Next each scanline is checked for any er
bubbles' (it's an error because it may have been mis-scanned
because too light bubbling), 'double bubble' (each bubble line should
have no more that one letter picked), invalid or duplicated CODE,
-invalid student ID
+invalid student/employee ID
If the CODE option is used that determines the randomization of the
-homework problems, either way the student ID is looked up into a
+homework problems, either way the student/employee ID is looked up into a
username:domain.
During the validation phase the instructor can choose to skip scanlines.
-After the validation phase, there are now 3 bubble sheet files
+After the validation phase, there are now 3 bubblesheet files
scantron_original_filename (unmodified original file)
scantron_corrected_filename (file where the corrected information has replaced the original information)
scantron_skipped_filename (contains the exact text of scanlines that where skipped)
Also there is a separate hash nohist_scantrondata that contains extra
-correction information that isn't representable in the bubble sheet
+correction information that isn't representable in the bubblesheet
file (see &scantron_getfile() for more information)
After all scanlines are either valid, marked as valid or skipped, then
@@ -4596,9 +5089,7 @@ the homework problem.
sub defaultFormData {
my ($symb)=@_;
- return ' '."\n".
- ' '."\n".
- ' '."\n";
+ return ' ';
}
@@ -4609,14 +5100,19 @@ sub defaultFormData {
Return html dropdown of possible sequences to grade
Arguments:
- $symb - $symb of the current resource
+ $symb - $symb of the current resource
+ $map_error - ref to scalar which will container error if
+ $navmap object is unavailable in &getSymbMap().
=cut
sub getSequenceDropDown {
- my ($symb)=@_;
+ my ($symb,$map_error)=@_;
my $result=''."\n";
- my ($titles,$symbx) = &getSymbMap();
+ my ($titles,$symbx) = &getSymbMap($map_error);
+ if (ref($map_error)) {
+ return if ($$map_error);
+ }
my ($curpage)=&Apache::lonnet::decode_symb($symb);
my $ctr=0;
foreach (@$titles) {
@@ -4630,6 +5126,67 @@ sub getSequenceDropDown {
return $result;
}
+my %bubble_lines_per_response; # no. bubble lines for each response.
+ # key is zero-based index - 0, 1, 2 ...
+
+my %first_bubble_line; # First bubble line no. for each bubble.
+
+my %subdivided_bubble_lines; # no. bubble lines for optionresponse,
+ # matchresponse or rankresponse, where
+ # an individual response can have multiple
+ # lines
+
+my %responsetype_per_response; # responsetype for each response
+
+# Save and restore the bubble lines array to the form env.
+
+
+sub save_bubble_lines {
+ foreach my $line (keys(%bubble_lines_per_response)) {
+ $env{"form.scantron.bubblelines.$line"} = $bubble_lines_per_response{$line};
+ $env{"form.scantron.first_bubble_line.$line"} =
+ $first_bubble_line{$line};
+ $env{"form.scantron.sub_bubblelines.$line"} =
+ $subdivided_bubble_lines{$line};
+ $env{"form.scantron.responsetype.$line"} =
+ $responsetype_per_response{$line};
+ }
+}
+
+
+sub restore_bubble_lines {
+ my $line = 0;
+ %bubble_lines_per_response = ();
+ while ($env{"form.scantron.bubblelines.$line"}) {
+ my $value = $env{"form.scantron.bubblelines.$line"};
+ $bubble_lines_per_response{$line} = $value;
+ $first_bubble_line{$line} =
+ $env{"form.scantron.first_bubble_line.$line"};
+ $subdivided_bubble_lines{$line} =
+ $env{"form.scantron.sub_bubblelines.$line"};
+ $responsetype_per_response{$line} =
+ $env{"form.scantron.responsetype.$line"};
+ $line++;
+ }
+}
+
+# Given the parsed scanline, get the response for
+# 'answer' number n:
+
+sub get_response_bubbles {
+ my ($parsed_line, $response) = @_;
+
+ my $bubble_line = $first_bubble_line{$response-1} +1;
+ my $bubble_lines= $bubble_lines_per_response{$response-1};
+
+ my $selected = "";
+
+ for (my $bline = 0; $bline < $bubble_lines; $bline++) {
+ $selected .= $$parsed_line{"scantron.$bubble_line.answer"}.":";
+ $bubble_line++;
+ }
+ return $selected;
+}
=pod
@@ -4642,14 +5199,17 @@ sub getSequenceDropDown {
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 $getpropath = 1;
+ my ($dirlist,$listerror) = &Apache::lonnet::dirlist('userfiles',$cdom,
+ $cname,$getpropath);
my @possiblenames;
- foreach my $filename (sort(@files)) {
- ($filename)=split(/&/,$filename);
- if ($filename!~/^scantron_orig_/) { next ; }
- $filename=~s/^scantron_orig_//;
- push(@possiblenames,$filename);
+ if (ref($dirlist) eq 'ARRAY') {
+ foreach my $filename (sort(@{$dirlist})) {
+ ($filename)=split(/&/,$filename);
+ if ($filename!~/^scantron_orig_/) { next ; }
+ $filename=~s/^scantron_orig_//;
+ push(@possiblenames,$filename);
+ }
}
return @possiblenames;
}
@@ -4686,19 +5246,76 @@ sub scantron_uploads {
=cut
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; }
- $result.=''.$descrip.' '."\n";
+ my @lines = &get_scantronformat_file();
+ if (@lines > 0) {
+ foreach my $line (@lines) {
+ next if (($line =~ /^\#/) || ($line eq ''));
+ my ($name,$descrip)=split(/:/,$line);
+ $result.=''.$descrip.' '."\n";
+ }
}
$result.=' '."\n";
-
return $result;
}
+=pod
+
+=item get_scantronformat_file
+
+ Returns an array containing lines from the scantron format file for
+ the domain of the course.
+
+ If a url for a custom.tab file is listed in domain's configuration.db,
+ lines are from this file.
+
+ Otherwise, if a default.tab has been published in RES space by the
+ domainconfig user, lines are from this file.
+
+ Otherwise, fall back to getting lines from the legacy file on the
+ local server: /home/httpd/lonTabs/default_scantronformat.tab
+
+=cut
+
+sub get_scantronformat_file {
+ my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
+ my %domconfig = &Apache::lonnet::get_dom('configuration',['scantron'],$cdom);
+ my $gottab = 0;
+ my @lines;
+ if (ref($domconfig{'scantron'}) eq 'HASH') {
+ if ($domconfig{'scantron'}{'scantronformat'} ne '') {
+ my $formatfile = &Apache::lonnet::getfile($Apache::lonnet::perlvar{'lonDocRoot'}.$domconfig{'scantron'}{'scantronformat'});
+ if ($formatfile ne '-1') {
+ @lines = split("\n",$formatfile,-1);
+ $gottab = 1;
+ }
+ }
+ }
+ if (!$gottab) {
+ my $confname = $cdom.'-domainconfig';
+ my $default = $Apache::lonnet::perlvar{'lonDocRoot'}.'/res/'.$cdom.'/'.$confname.'/default.tab';
+ my $formatfile = &Apache::lonnet::getfile($default);
+ if ($formatfile ne '-1') {
+ @lines = split("\n",$formatfile,-1);
+ $gottab = 1;
+ }
+ }
+ if (!$gottab) {
+ my @domains = &Apache::lonnet::current_machine_domains();
+ if (grep(/^\Q$cdom\E$/,@domains)) {
+ my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
+ @lines = <$fh>;
+ close($fh);
+ } else {
+ my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/default_scantronformat.tab');
+ @lines = <$fh>;
+ close($fh);
+ }
+ }
+ return @lines;
+}
+
=pod
=item scantron_CODElist
@@ -4731,11 +5348,11 @@ sub scantron_CODElist {
=cut
sub scantron_CODEunique {
- my $result='
+ my $result='
'.&mt('Yes').'
-
+
'.&mt('No').'
';
@@ -4746,7 +5363,7 @@ sub scantron_CODEunique {
=item scantron_selectphase
- Generates the initial screen to start the bubble sheet process.
+ Generates the initial screen to start the bubblesheet process.
Allows for - starting a grading run.
- downloading existing scan data (original, corrected
or skipped info)
@@ -4760,154 +5377,177 @@ sub scantron_CODEunique {
=cut
sub scantron_selectphase {
- my ($r,$file2grade) = @_;
- my ($symb)=&get_symb($r);
+ my ($r,$file2grade,$symb) = @_;
if (!$symb) {return '';}
- my $sequence_selector=&getSequenceDropDown($symb);
+ my $map_error;
+ my $sequence_selector=&getSequenceDropDown($symb,\$map_error);
+ if ($map_error) {
+ $r->print(' '.&navmap_errormsg().' ');
+ return;
+ }
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;
- # Chunk of form to prompt for a file to grade and how:
-
- $result.= <
-
-
-
-
- $default_form_data
-
-
-
-
-SCANTRONFORM
-
- $r->print($result);
+ $ssi_error = 0;
if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
&Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
# Chunk of form to prompt for a scantron file upload.
- $r->print(<
-
-
-
-
-SCANTRONFORM
+ '.&Apache::loncommon::end_data_table_row().'
+ '.&Apache::loncommon::end_data_table().'
+');
}
+ # Chunk of form to prompt for a file to grade and how:
+
+ $result.= '
+
+
+
+ '.$default_form_data.'
+ '.&Apache::loncommon::start_data_table('LC_scantron_action').'
+ '.&Apache::loncommon::start_data_table_header_row().'
+
+ '.&mt('Specify file and which Folder/Sequence to grade').'
+
+ '.&Apache::loncommon::end_data_table_header_row().'
+ '.&Apache::loncommon::start_data_table_row().'
+ '.&mt('Sequence to grade:').' '.$sequence_selector.'
+ '.&Apache::loncommon::end_data_table_row().'
+ '.&Apache::loncommon::start_data_table_row().'
+ '.&mt('Filename of bubblesheet data file:').' '.$file_selector.'
+ '.&Apache::loncommon::end_data_table_row().'
+ '.&Apache::loncommon::start_data_table_row().'
+ '.&mt('Format of bubblesheet data file:').' '.$format_selector.'
+ '.&Apache::loncommon::end_data_table_row().'
+ '.&Apache::loncommon::start_data_table_row().'
+ '.&mt('Saved CODEs to validate against:').' '.$CODE_selector.'
+ '.&Apache::loncommon::end_data_table_row().'
+ '.&Apache::loncommon::start_data_table_row().'
+ '.&mt('Each CODE is only to be used once:').' '.$CODE_unique.'
+ '.&Apache::loncommon::end_data_table_row().'
+ '.&Apache::loncommon::start_data_table_row().'
+ '.&mt('Options:').'
+
+ '.&mt('Do only previously skipped records').'
+ '.&mt('Remove all existing corrections').'
+ '.&mt('Skip hidden resources when grading').'
+
+ '.&Apache::loncommon::end_data_table_row().'
+ '.&Apache::loncommon::start_data_table_row().'
+
+
+
+ '.&Apache::loncommon::end_data_table_row().'
+ '.&Apache::loncommon::end_data_table().'
+
+';
+
+ $r->print($result);
+
+
+
# Chunk of the form that prompts to view a scoring office file,
# corrected file, skipped records in a file.
- $r->print(<
-
-
- $default_form_data
-
-
-
-
-
-SCANTRONFORM
+ $r->print('
+
+
+ '.$default_form_data.'
+
+ '.&Apache::loncommon::start_data_table('LC_scantron_action').'
+ '.&Apache::loncommon::start_data_table_header_row().'
+
+ '.&mt('Download a scoring office file').'
+
+ '.&Apache::loncommon::end_data_table_header_row().'
+ '.&Apache::loncommon::start_data_table_row().'
+ '.&mt('Filename of scoring office file: [_1]',$file_selector).'
+
+
+ '.&Apache::loncommon::end_data_table_row().'
+ '.&Apache::loncommon::end_data_table().'
+
+
+');
- $r->print(' ');
&Apache::lonpickcode::code_list($r,2);
- $r->print('
');
- $r->print($grading_menu_button);
- return
+
+ $r->print(''.
+ $default_form_data."\n".
+ &Apache::loncommon::start_data_table('LC_scantron_action')."\n".
+ &Apache::loncommon::start_data_table_header_row()."\n".
+ '
+ '.&mt('Review bubblesheet data and submissions for a previously graded folder/sequence')."\n".
+ ' '."\n".
+ &Apache::loncommon::end_data_table_header_row()."\n".
+ &Apache::loncommon::start_data_table_row()."\n".
+ ' '.&mt('Graded folder/sequence:').' '."\n".
+ ' '.$sequence_selector.' '.
+ &Apache::loncommon::end_data_table_row()."\n".
+ &Apache::loncommon::start_data_table_row()."\n".
+ ' '.&mt('Filename of scoring office file:').' '."\n".
+ ' '.$file_selector.' '."\n".
+ &Apache::loncommon::end_data_table_row()."\n".
+ &Apache::loncommon::start_data_table_row()."\n".
+ ' '.&mt('Format of data file:').' '."\n".
+ ' '.$format_selector.' '."\n".
+ &Apache::loncommon::end_data_table_row()."\n".
+ &Apache::loncommon::start_data_table_row()."\n".
+ ' '.&mt('Options').' '."\n".
+ ' '.&mt('Skip hidden resources').' '.
+ &Apache::loncommon::end_data_table_row()."\n".
+ &Apache::loncommon::start_data_table_row()."\n".
+ ''."\n".
+ ' '."\n".
+ ' '."\n".
+ ' '."\n".
+ &Apache::loncommon::end_data_table_row()."\n".
+ &Apache::loncommon::end_data_table()."\n".
+ ' ');
+ return;
}
=pod
@@ -4942,8 +5582,8 @@ SCANTRONFORM
CODEstart - (only matter if a CODE exists) column in the line where
the CODE starts
CODElength - length of the CODE
- IDstart - column where the student ID number starts
- IDlength - length of the student ID info
+ IDstart - column where the student/employee ID starts
+ IDlength - length of the student/employee ID info
Qstart - column where the information from the bubbled
'questions' start
Qlength - number of columns comprising a single bubble line from
@@ -4964,15 +5604,17 @@ SCANTRONFORM
LastName - column that the last name starts in
LastNameLength - number of columns that the last name spans
+ BubblesPerRow - number of bubbles available in each row used to
+ bubble an answer. (If not specified, 10 assumed).
=cut
sub get_scantron_config {
my ($which) = @_;
- my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
+ my @lines = &get_scantronformat_file();
my %config;
#FIXME probably should move to XML it has already gotten a bit much now
- foreach my $line (<$fh>) {
+ foreach my $line (@lines) {
my ($name,$descrip)=split(/:/,$line);
if ($name ne $which ) { next; }
chomp($line);
@@ -4985,7 +5627,7 @@ sub get_scantron_config {
$config{'IDstart'}=$config[5];
$config{'IDlength'}=$config[6];
$config{'Qstart'}=$config[7];
- $config{'Qlength'}=$config[8];
+ $config{'Qlength'}=$config[8];
$config{'Qoff'}=$config[9];
$config{'Qon'}=$config[10];
$config{'PaperID'}=$config[11];
@@ -4994,6 +5636,7 @@ sub get_scantron_config {
$config{'FirstNamelength'}=$config[14];
$config{'LastName'}=$config[15];
$config{'LastNamelength'}=$config[16];
+ $config{'BubblesPerRow'}=$config[17];
last;
}
return %config;
@@ -5003,7 +5646,7 @@ sub get_scantron_config {
=item username_to_idmap
- creates a hash keyed by student id with values of the corresponding
+ creates a hash keyed by student/employee ID with values of the corresponding
student username:domain.
Arguments:
@@ -5042,7 +5685,7 @@ sub username_to_idmap {
$whichline - line number of the passed in scanline
$field - type of change to process
(either
- 'ID' -> correct the student ID number
+ 'ID' -> correct the student/employee ID
'CODE' -> correct the CODE
'answer' -> fixup the submitted answers)
@@ -5059,6 +5702,8 @@ sub username_to_idmap {
- 'answer'
'response' - new answer or 'none' if blank
'question' - the bubble line to change
+ 'questionnum' - the question identifier,
+ may include subquestion.
Returns:
$line - the modified scanline
@@ -5071,7 +5716,6 @@ sub username_to_idmap {
sub scantron_fixup_scanline {
my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
-
if ($field eq 'ID') {
if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {
return ($line,1,'New value too large');
@@ -5108,7 +5752,7 @@ sub scantron_fixup_scanline {
my $answer=${off}x$length;
if ($args->{'response'} eq 'none') {
&scan_data($scan_data,
- "$whichline.no_bubble.".$args->{'question'},'1');
+ "$whichline.no_bubble.".$args->{'questionnum'},'1');
} else {
if ($on eq 'letter') {
my @alphabet=('A'..'Z');
@@ -5120,7 +5764,7 @@ sub scantron_fixup_scanline {
substr($answer,$args->{'response'},1)=$on;
}
&scan_data($scan_data,
- "$whichline.no_bubble.".$args->{'question'},undef,'1');
+ "$whichline.no_bubble.".$args->{'questionnum'},undef,'1');
}
my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'};
substr($line,$where-1,$length)=$answer;
@@ -5157,6 +5801,39 @@ sub scan_data {
return $scan_data->{$filename.'_'.$key};
}
+# ----- These first few routines are general use routines.----
+
+# Return the number of occurences of a pattern in a string.
+
+sub occurence_count {
+ my ($string, $pattern) = @_;
+
+ my @matches = ($string =~ /$pattern/g);
+
+ return scalar(@matches);
+}
+
+
+# Take a string known to have digits and convert all the
+# digits into letters in the range J,A..I.
+
+sub digits_to_letters {
+ my ($input) = @_;
+
+ my @alphabet = ('J', 'A'..'I');
+
+ my @input = split(//, $input);
+ my $output ='';
+ for (my $i = 0; $i < scalar(@input); $i++) {
+ if ($input[$i] =~ /\d/) {
+ $output .= $alphabet[$input[$i]];
+ } else {
+ $output .= $input[$i];
+ }
+ }
+ return $output;
+}
+
=pod
=item scantron_parse_scanline
@@ -5182,7 +5859,7 @@ sub scan_data {
CODE_ignore_dup - 1 if the CODE is a duplicated use when unique
CODEs were selected, but the usage has been
forced by the operator
- ID - student ID
+ ID - student/employee ID
PaperID - if used, the ID number printed on the sheet when the
paper was scanned
FirstName - first name from the sheet
@@ -5216,8 +5893,10 @@ sub scan_data {
sub scantron_parse_scanline {
my ($line,$whichline,$scantron_config,$scan_data,$just_header)=@_;
+
my %record;
- my $questions=substr($line,$$scantron_config{'Qstart'}-1); # Answers
+ my $lastpos = $env{'form.scantron_maxbubble'}*$$scantron_config{'Qlength'};
+ my $questions=substr($line,$$scantron_config{'Qstart'}-1,$lastpos); # Answers
my $data=substr($line,0,$$scantron_config{'Qstart'}-1); # earlier stuff
if (!($$scantron_config{'CODElocation'} eq 0 ||
$$scantron_config{'CODElocation'} eq 'none')) {
@@ -5254,166 +5933,222 @@ sub scantron_parse_scanline {
my $questnum=0;
my $ansnum =1; # Multiple 'answer lines'/question.
- while ($questions) {
+ chomp($questions); # Get rid of any trailing \n.
+ $questions =~ s/\r$//; # Get rid of trailing \r too (MAC or Win uploads).
+ while (length($questions)) {
my $answers_needed = $bubble_lines_per_response{$questnum};
- my $answer_length = $$scantron_config{'Qlength'} * $answers_needed;
-
-
-
- $questnum++;
- my $currentquest = substr($questions,0,$answer_length);
- $questions = substr($questions,0,$answer_length)='';
- if (length($currentquest) < $answer_length) { next; }
-
- # Qon letter implies for each slot in currentquest we have:
- # ? or * for doubles a letter in A-Z for a bubble and
- # about anything else (esp. a value of Qoff for missing
- # bubbles.
-
-
- if ($$scantron_config{'Qon'} eq 'letter') {
-
- if ($currentquest =~ /\?/
- || $currentquest =~ /\*/
- || (&occurence_count($currentquest, "[A-Z]") > 1)) {
- push(@{$record{'scantron.doubleerror'}},$questnum);
- for (my $ans = 0; $ans < $answers_needed; $ans++) {
- my $bubble = substr($currentquest, $ans, 1);
- if ($bubble =~ /[A-Z]/ ) {
- $record{"scantron.$ansnum.answer"} = $bubble;
- } else {
- $record{"scantron.$ansnum.answer"}='';
- }
- $ansnum++;
- }
-
- } elsif (!defined($currentquest)
- || (&occurence_count($currentquest, $$scantron_config{'Qoff'}) == length($currentquest))
- || (&occurence_count($currentquest, "[A-Z]") == 0)) {
- for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
- $record{"scantron.$ansnum.answer"}='';
- $ansnum++;
-
- }
- if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
- push(@{$record{"scantron.missingerror"}},$questnum);
- $ansnum += $answers_needed;
- }
-
- } else {
- for (my $ans = 0; $ans < $answers_needed; $ans++) {
- $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);
- $ansnum++;
- }
- }
-
- # Qon 'number' implies each slot gives a digit that indexes the
- # the bubbles filled or Qoff or a non number for unbubbled lines.
- # and *? for double bubbles on a line.
- # these answers are also stored as letters.
-
- } elsif ($$scantron_config{'Qon'} eq 'number') {
- if ($currentquest =~ /\?/
- || $currentquest =~ /\*/
- || (&occurence_count($currentquest, '\d') > 1)) {
- push(@{$record{'scantron.doubleerror'}},$questnum);
- for (my $ans = 0; $ans < $answers_needed; $ans++) {
- my $bubble = substr($currentquest, $ans, 1);
- if ($bubble =~ /\d/) {
- $record{"scantron.$ansnum.answer"} = $alphabet[$bubble];
- } else {
- $record{"scantron.$ansnum.answer"}=' ';
- }
- $ansnum++;
- }
-
- } elsif (!defined($currentquest)
- || (&occurence_count($currentquest,$$scantron_config{'Qoff'}) == length($currentquest))
- || (&occurence_count($currentquest, '\d') == 0)) {
- for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
- $record{"scantron.$ansnum.answer"}='';
- $ansnum++;
+ my $answer_length = ($$scantron_config{'Qlength'} * $answers_needed)
+ || 1;
+ $questnum++;
+ my $quest_id = $questnum;
+ my $currentquest = substr($questions,0,$answer_length);
+ $questions = substr($questions,$answer_length);
+ if (length($currentquest) < $answer_length) { next; }
+
+ if ($subdivided_bubble_lines{$questnum-1} =~ /,/) {
+ my $subquestnum = 1;
+ my $subquestions = $currentquest;
+ my @subanswers_needed =
+ split(/,/,$subdivided_bubble_lines{$questnum-1});
+ foreach my $subans (@subanswers_needed) {
+ my $subans_length =
+ ($$scantron_config{'Qlength'} * $subans) || 1;
+ my $currsubquest = substr($subquestions,0,$subans_length);
+ $subquestions = substr($subquestions,$subans_length);
+ $quest_id = "$questnum.$subquestnum";
+ if (($$scantron_config{'Qon'} eq 'letter') ||
+ ($$scantron_config{'Qon'} eq 'number')) {
+ $ansnum = &scantron_validator_lettnum($ansnum,
+ $questnum,$quest_id,$subans,$currsubquest,$whichline,
+ \@alphabet,\%record,$scantron_config,$scan_data);
+ } else {
+ $ansnum = &scantron_validator_positional($ansnum,
+ $questnum,$quest_id,$subans,$currsubquest,$whichline, \@alphabet,\%record,$scantron_config,$scan_data);
+ }
+ $subquestnum ++;
+ }
+ } else {
+ if (($$scantron_config{'Qon'} eq 'letter') ||
+ ($$scantron_config{'Qon'} eq 'number')) {
+ $ansnum = &scantron_validator_lettnum($ansnum,$questnum,
+ $quest_id,$answers_needed,$currentquest,$whichline,
+ \@alphabet,\%record,$scantron_config,$scan_data);
+ } else {
+ $ansnum = &scantron_validator_positional($ansnum,$questnum,
+ $quest_id,$answers_needed,$currentquest,$whichline,
+ \@alphabet,\%record,$scantron_config,$scan_data);
+ }
+ }
+ }
+ $record{'scantron.maxquest'}=$questnum;
+ return \%record;
+}
- }
- if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
- push(@{$record{"scantron.missingerror"}},$questnum);
- $ansnum += $answers_needed;
- }
+sub scantron_validator_lettnum {
+ my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,$whichline,
+ $alphabet,$record,$scantron_config,$scan_data) = @_;
+
+ # Qon 'letter' implies for each slot in currquest we have:
+ # ? or * for doubles, a letter in A-Z for a bubble, and
+ # about anything else (esp. a value of Qoff) for missing
+ # bubbles.
+ #
+ # Qon 'number' implies each slot gives a digit that indexes the
+ # bubbles filled, or Qoff, or a non-number for unbubbled lines,
+ # and * or ? for double bubbles on a single line.
+ #
- } else {
- $currentquest = &digits_to_letters($currentquest);
- for (my $ans =0; $ans < $answers_needed; $ans++) {
- $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1);
- $ansnum++;
- }
- }
- } else {
+ my $matchon;
+ if ($$scantron_config{'Qon'} eq 'letter') {
+ $matchon = '[A-Z]';
+ } elsif ($$scantron_config{'Qon'} eq 'number') {
+ $matchon = '\d';
+ }
+ my $occurrences = 0;
+ if (($responsetype_per_response{$questnum-1} eq 'essayresponse') ||
+ ($responsetype_per_response{$questnum-1} eq 'formularesponse') ||
+ ($responsetype_per_response{$questnum-1} eq 'stringresponse') ||
+ ($responsetype_per_response{$questnum-1} eq 'imageresponse') ||
+ ($responsetype_per_response{$questnum-1} eq 'reactionresponse') ||
+ ($responsetype_per_response{$questnum-1} eq 'organicresponse')) {
+ my @singlelines = split('',$currquest);
+ foreach my $entry (@singlelines) {
+ $occurrences = &occurence_count($entry,$matchon);
+ if ($occurrences > 1) {
+ last;
+ }
+ }
+ } else {
+ $occurrences = &occurence_count($currquest,$matchon);
+ }
+ if (($currquest =~ /\?/ || $currquest =~ /\*/) || ($occurrences > 1)) {
+ push(@{$record->{'scantron.doubleerror'}},$quest_id);
+ for (my $ans=0; $ans<$answers_needed; $ans++) {
+ my $bubble = substr($currquest,$ans,1);
+ if ($bubble =~ /$matchon/ ) {
+ if ($$scantron_config{'Qon'} eq 'number') {
+ if ($bubble == 0) {
+ $bubble = 10;
+ }
+ $record->{"scantron.$ansnum.answer"} =
+ $alphabet->[$bubble-1];
+ } else {
+ $record->{"scantron.$ansnum.answer"} = $bubble;
+ }
+ } else {
+ $record->{"scantron.$ansnum.answer"}='';
+ }
+ $ansnum++;
+ }
+ } elsif (!defined($currquest)
+ || (&occurence_count($currquest, $$scantron_config{'Qoff'}) == length($currquest))
+ || (&occurence_count($currquest,$matchon) == 0)) {
+ for (my $ans=0; $ans<$answers_needed; $ans++ ) {
+ $record->{"scantron.$ansnum.answer"}='';
+ $ansnum++;
+ }
+ if (!&scan_data($scan_data,"$whichline.no_bubble.$quest_id")) {
+ push(@{$record->{'scantron.missingerror'}},$quest_id);
+ }
+ } else {
+ if ($$scantron_config{'Qon'} eq 'number') {
+ $currquest = &digits_to_letters($currquest);
+ }
+ for (my $ans=0; $ans<$answers_needed; $ans++) {
+ my $bubble = substr($currquest,$ans,1);
+ $record->{"scantron.$ansnum.answer"} = $bubble;
+ $ansnum++;
+ }
+ }
+ return $ansnum;
+}
- # Otherwise there's a positional notation;
- # each bubble line requires Qlength items, and there are filled in
- # bubbles for each case where there 'Qon' characters.
- #
+sub scantron_validator_positional {
+ my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,
+ $whichline,$alphabet,$record,$scantron_config,$scan_data) = @_;
- my @array=split($$scantron_config{'Qon'},$currentquest,-1);
+ # Otherwise there's a positional notation;
+ # each bubble line requires Qlength items, and there are filled in
+ # bubbles for each case where there 'Qon' characters.
+ #
- # If the split only giveas us one element.. the full length of the
- # answser string, no bubbles are filled in:
+ my @array=split($$scantron_config{'Qon'},$currquest,-1);
- if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {
- for (my $ans = 0; $ans < $answers_needed; $ans++ ) {
- $record{"scantron.$ansnum.answer"}='';
- $ansnum++;
+ # If the split only gives us one element.. the full length of the
+ # answer string, no bubbles are filled in:
- }
- if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) {
- push(@{$record{"scantron.missingerror"}},$questnum);
- }
- } elsif (scalar(@array) lt 2) {
+ if ($answers_needed eq '') {
+ return;
+ }
- my $location = length($array[0]);
- my $line_num = $location / $$scantron_config{'Qlength'};
- my $bubble = $alphabet[$location % $$scantron_config{'Qlength'}];
+ if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {
+ for (my $ans=0; $ans<$answers_needed; $ans++ ) {
+ $record->{"scantron.$ansnum.answer"}='';
+ $ansnum++;
+ }
+ if (!&scan_data($scan_data,"$whichline.no_bubble.$quest_id")) {
+ push(@{$record->{"scantron.missingerror"}},$quest_id);
+ }
+ } elsif (scalar(@array) == 2) {
+ my $location = length($array[0]);
+ my $line_num = int($location / $$scantron_config{'Qlength'});
+ my $bubble = $alphabet->[$location % $$scantron_config{'Qlength'}];
+ for (my $ans=0; $ans<$answers_needed; $ans++) {
+ if ($ans eq $line_num) {
+ $record->{"scantron.$ansnum.answer"} = $bubble;
+ } else {
+ $record->{"scantron.$ansnum.answer"} = ' ';
+ }
+ $ansnum++;
+ }
+ } else {
+ # If there's more than one instance of a bubble character
+ # That's a double bubble; with positional notation we can
+ # record all the bubbles filled in as well as the
+ # fact this response consists of multiple bubbles.
+ #
+ if (($responsetype_per_response{$questnum-1} eq 'essayresponse') ||
+ ($responsetype_per_response{$questnum-1} eq 'formularesponse') ||
+ ($responsetype_per_response{$questnum-1} eq 'stringresponse') ||
+ ($responsetype_per_response{$questnum-1} eq 'imageresponse') ||
+ ($responsetype_per_response{$questnum-1} eq 'reactionresponse') ||
+ ($responsetype_per_response{$questnum-1} eq 'organicresponse')) {
+ my $doubleerror = 0;
+ while (($currquest >= $$scantron_config{'Qlength'}) &&
+ (!$doubleerror)) {
+ my $currline = substr($currquest,0,$$scantron_config{'Qlength'});
+ $currquest = substr($currquest,$$scantron_config{'Qlength'});
+ my @currarray = split($$scantron_config{'Qon'},$currline,-1);
+ if (length(@currarray) > 2) {
+ $doubleerror = 1;
+ }
+ }
+ if ($doubleerror) {
+ push(@{$record->{'scantron.doubleerror'}},$quest_id);
+ }
+ } else {
+ push(@{$record->{'scantron.doubleerror'}},$quest_id);
+ }
+ my $item = $ansnum;
+ for (my $ans=0; $ans<$answers_needed; $ans++) {
+ $record->{"scantron.$item.answer"} = '';
+ $item ++;
+ }
- for (my $ans = 0; $ans < $answers_needed; $ans++) {
- if ($ans eq $line_num) {
- $record{"scantron.$ansnum.answer"} = $bubble;
- } else {
- $record{"scantron.$ansnum.answer"} = ' ';
- }
- $ansnum++;
- }
- }
- # If there's more than one instance of a bubble character
- # That's a double bubble; with positional notation we can
- # record all the bubbles filled in as well as the
- # fact this response consists of multiple bubbles.
- #
- else {
- push(@{$record{'scantron.doubleerror'}},$questnum);
-
- my $first_answer = $ansnum;
- for (my $ans =0; $ans < $answers_needed; $ans++) {
- my $item = $first_answer+$ans;
- $record{"scantron.$item.answer"} = '';
- }
-
- my @ans=@array;
- my $i=0;
- my $increment = 0;
- while ($#ans) {
- $i+=length($ans[0]) + $increment;
- my $line = int($i/$$scantron_config{'Qlength'} + $first_answer);
- my $bubble = $i%$$scantron_config{'Qlength'};
- $record{"scantron.$line.answer"}.=$alphabet[$bubble];
- shift(@ans);
- $increment = 1;
- }
- $ansnum += $answers_needed;
- }
- }
+ my @ans=@array;
+ my $i=0;
+ my $increment = 0;
+ while ($#ans) {
+ $i+=length($ans[0]) + $increment;
+ my $line = int($i/$$scantron_config{'Qlength'} + $ansnum);
+ my $bubble = $i%$$scantron_config{'Qlength'};
+ $record->{"scantron.$line.answer"}.=$alphabet->[$bubble];
+ shift(@ans);
+ $increment = 1;
+ }
+ $ansnum += $answers_needed;
}
- $record{'scantron.maxquest'}=$questnum;
- return \%record;
+ return $ansnum;
}
=pod
@@ -5553,7 +6288,8 @@ sub scantron_process_corrections {
&scantron_fixup_scanline(\%scantron_config,$scan_data,$line,
$which,'answer',
{ 'question'=>$question,
- 'response'=>$env{"form.scantron_correct_Q_$question"}});
+ 'response'=>$env{"form.scantron_correct_Q_$question"},
+ 'questionnum'=>$env{"form.scantron_questionnum_Q_$question"}});
if ($err) { last; }
}
}
@@ -5650,7 +6386,7 @@ sub remember_current_skipped {
=item check_for_error
Checks if there was an error when attempting to remove a specific
- scantron_.. bubble sheet data file. Prints out an error if
+ scantron_.. bubblesheet data file. Prints out an error if
something went wrong.
=cut
@@ -5658,7 +6394,7 @@ sub remember_current_skipped {
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.");
+ $r->print(&mt("An error occurred ([_1]) when trying to remove the existing corrections.",$result));
}
}
@@ -5672,7 +6408,7 @@ sub check_for_error {
=cut
sub scantron_warning_screen {
- my ($button_text)=@_;
+ my ($button_text,$symb)=@_;
my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
my $CODElist;
@@ -5682,25 +6418,30 @@ sub scantron_warning_screen {
$CODElist=$env{'form.scantron_CODElist'};
if ($env{'form.scantron_CODElist'} eq '') { $CODElist='None '; }
$CODElist=
- 'List of CODES to validate against: '.
+ ''.&mt('List of CODES to validate against:').' '.
$env{'form.scantron_CODElist'}.' ';
}
- return (<'.&mt('Hand-graded items: points from last bubble in row').' '.
+ $env{'form.scantron_lastbubblepoints'}.' ';
+ }
+ return ('
-Please double check the information
- below before clicking on '$button_text'
+
+'.&mt('Please double check the information below before clicking on \'[_1]\'',&mt($button_text)).'
-Sequence to be Graded: $title
-Data File that will be used: $env{'form.scantron_selectfile'}
-$CODElist
+'.&mt('Sequence to be Graded:').' '.$title.'
+'.&mt('Data File that will be used:').' '.$env{'form.scantron_selectfile'}.'
+'.$CODElist.$lastbubblepoints.'
-
- If this information is correct, please click on '$button_text'.
- If something is incorrect, please click the 'Grading Menu' button to start over.
+ '.&mt('If this information is correct, please click on \'[_1]\'.',&mt($button_text)).'
+'.&mt('If something is incorrect, please return to [_1]Grade/Manage/Review Bubblesheets[_2] to start over.','',' ').'
-STUFF
+');
}
=pod
@@ -5713,33 +6454,33 @@ STUFF
=cut
sub scantron_do_warning {
- my ($r)=@_;
- my ($symb)=&get_symb($r);
+ my ($r,$symb)=@_;
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.
");
+ $r->print("".&mt('You have forgotten 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
');
+ $r->print(''.&mt('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.
');
+ $r->print(''.&mt("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.
');
+ $r->print(''.&mt("You have not selected the format of the student's response data.").'
');
}
} else {
- my $warning=&scantron_warning_screen('Grading: Validate Records');
- $r->print(<
+ my $warning=&scantron_warning_screen('Grading: Validate Records',$symb);
+ my $bubbledbyhand=&hand_bubble_option();
+ $r->print('
+'.$warning.$bubbledbyhand.'
+
-STUFF
+');
}
- $r->print(" ".&show_grading_menu_form($symb));
+ $r->print(" ");
return '';
}
@@ -5772,6 +6513,10 @@ SCANTRONFORM
' '."\n";
$chunk .=
' '."\n";
+ $chunk .=
+ ' '."\n";
+ $chunk .=
+ ' '."\n";
$result .= $chunk;
$line++;
}
@@ -5782,7 +6527,7 @@ SCANTRONFORM
=item scantron_validate_file
- Dispatch routine for doing validation of a bubble sheet data file.
+ Dispatch routine for doing validation of a bubblesheet data file.
Also processes any necessary information resets that need to
occur before validation begins (ignore previous corrections,
@@ -5791,8 +6536,7 @@ SCANTRONFORM
=cut
sub scantron_validate_file {
- my ($r) = @_;
- my ($symb)=&get_symb($r);
+ my ($r,$symb) = @_;
if (!$symb) {return '';}
my $default_form_data=&defaultFormData($symb);
@@ -5816,11 +6560,20 @@ sub scantron_validate_file {
if ($env{'form.scantron_corrections'}) {
&scantron_process_corrections($r);
}
- $r->print("Gathering necessary info.
");$r->rflush();
+ $r->print(''.&mt('Gathering necessary information.').'
');$r->rflush();
#get the student pick code ready
$r->print(&Apache::loncommon::studentbrowser_javascript());
- my $max_bubble=&scantron_get_maxbubble();
+ my $nav_error;
+ my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+ my $max_bubble=&scantron_get_maxbubble(\$nav_error,\%scantron_config);
+ if ($nav_error) {
+ $r->print(&navmap_errormsg());
+ return '';
+ }
my $result=&scantron_form_start($max_bubble).$default_form_data;
+ if ($env{'form.scantron_lastbubblepoints'} ne '') {
+ $result .= ' ';
+ }
$r->print($result);
my @validate_phases=( 'sequence',
@@ -5836,7 +6589,7 @@ sub scantron_validate_file {
my $stop=0;
while (!$stop && $currentphase < scalar(@validate_phases)) {
- $r->print(" Validating ".$validate_phases[$currentphase]."
");
+ $r->print(&mt('Validating '.$validate_phases[$currentphase]).' ');
$r->rflush();
my $which="scantron_validate_".$validate_phases[$currentphase];
{
@@ -5845,32 +6598,41 @@ sub scantron_validate_file {
}
}
if (!$stop) {
- my $warning=&scantron_warning_screen('Start Grading');
- $r->print(<
-$warning
-
-
-STUFF
-
+ my $warning=&scantron_warning_screen('Start Grading',$symb);
+ $r->print(&mt('Validation process complete.').' '.
+ $warning.
+ &mt('Perform verification for each student after storage of submissions?').
+ ' '.
+ ' '.&mt('Yes').' '.
+ (' 'x3).''.
+ ' '.&mt('No').
+ ' '.
+ &mt('Grading will take longer if you use verification.').' '.
+ &mt('Otherwise, Grade/Manage/Review Bubblesheets [_1] Review bubblesheet data can be used once grading is complete.','»').' '.
+ ' '.
+ ' '."\n");
} else {
$r->print(' ');
$r->print(" ");
}
if ($stop) {
if ($validate_phases[$currentphase] eq 'sequence') {
- $r->print(' ');
- $r->print(' this error ');
+ $r->print(' ');
+ $r->print(' '.&mt('this error').' ');
- $r->print(" Or click the 'Grading Menu' button to start over.
");
+ $r->print(''.&mt('Or return to [_1]Grade/Manage/Review Bubblesheets[_2] to start over.','',' ').'
');
} else {
- $r->print(' ');
- $r->print(' using corrected info ');
- $r->print(" ");
- $r->print(" this scanline saving it for later.");
+ if ($validate_phases[$currentphase] eq 'doublebubble' || $validate_phases[$currentphase] eq 'missingbubbles') {
+ $r->print(' ');
+ } else {
+ $r->print(' ');
+ }
+ $r->print(' '.&mt('using corrected info').' ');
+ $r->print(" ");
+ $r->print(" ".&mt("this scanline saving it for later."));
}
}
- $r->print(" ".&show_grading_menu_form($symb));
+ $r->print(" ");
return '';
}
@@ -5879,7 +6641,7 @@ STUFF
=item scantron_remove_file
- Removes the requested bubble sheet data file, makes sure that
+ Removes the requested bubblesheet data file, makes sure that
scantron_original_ is never removed
@@ -5904,7 +6666,7 @@ sub scantron_remove_file {
=item scantron_remove_scan_data
- Removes all scan_data correction for the requested bubble sheet
+ Removes all scan_data correction for the requested bubblesheet
data file. (In the case that both the are doing skipped records we need
to remember the old skipped lines for the time being so that element
persists for a while.)
@@ -5928,7 +6690,10 @@ sub scantron_remove_scan_data {
}
my $result;
if (@todelete) {
- $result=&Apache::lonnet::del('nohist_scantrondata',\@todelete,$cdom,$cname);
+ $result = &Apache::lonnet::del('nohist_scantrondata',
+ \@todelete,$cdom,$cname);
+ } else {
+ $result = 'ok';
}
return $result;
}
@@ -5938,7 +6703,7 @@ sub scantron_remove_scan_data {
=item scantron_getfile
- Fetches the requested bubble sheet data file (all 3 versions), and
+ Fetches the requested bubblesheet data file (all 3 versions), and
the scan_data hash
Arguments:
@@ -6038,7 +6803,7 @@ sub lonnet_putfile {
=item scantron_putfile
- Stores the current version of the bubble sheet data files, and the
+ Stores the current version of the bubblesheet data files, and the
scan_data hash. (Does not modify the original version only the
corrected and skipped versions.
@@ -6132,7 +6897,7 @@ sub get_todo_count {
=item scantron_put_line
- Updates the 'corrected' or 'skipped' versions of the bubble sheet
+ Updates the 'corrected' or 'skipped' versions of the bubblesheet
data file.
Arguments:
@@ -6219,6 +6984,10 @@ sub scantron_validate_sequence {
my ($r,$currentphase) = @_;
my $navmap=Apache::lonnavmaps::navmap->new();
+ unless (ref($navmap)) {
+ $r->print(&navmap_errormsg());
+ return (1,$currentphase);
+ }
my (undef,undef,$sequence)=
&Apache::lonnet::decode_symb($env{'form.selectpage'});
@@ -6238,14 +7007,7 @@ sub scantron_validate_sequence {
return (0,$currentphase+1);
}
-=pod
-=item scantron_validate_ID
-
- Validates all scanlines in the selected file to not have any
- invalid or underspecified student IDs
-
-=cut
sub scantron_validate_ID {
my ($r,$currentphase) = @_;
@@ -6257,8 +7019,13 @@ sub scantron_validate_ID {
#get scantron line setup
my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
my ($scanlines,$scan_data)=&scantron_getfile();
-
- &scantron_get_maxbubble(); # parse needs the bubble_lines.. array.
+
+ my $nav_error;
+ &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the bubble_lines.. array.
+ if ($nav_error) {
+ $r->print(&navmap_errormsg());
+ return(1,$currentphase);
+ }
my %found=('ids'=>{},'usernames'=>{});
for (my $i=0;$i<=$scanlines->{'count'};$i++) {
@@ -6311,67 +7078,52 @@ sub scantron_validate_ID {
return (0,$currentphase+1);
}
-=pod
-
-=item scantron_get_correction
-
- Builds the interface screen to interact with the operator to fix a
- specific error condition in a specific scanline
-
- Arguments:
- $r - Apache request object
- $i - number of the current scanline
- $scan_record - hash ref as returned from &scantron_parse_scanline()
- $scan_config - hash ref as returned from &get_scantron_config()
- $line - full contents of the current scanline
- $error - error condition, valid values are
- 'incorrectCODE', 'duplicateCODE',
- 'doublebubble', 'missingbubble',
- 'duplicateID', 'incorrectID'
- $arg - extra information needed
- For errors:
- - duplicateID - paper number that this studentID was seen before on
- - duplicateCODE - array ref of the paper numbers this CODE was
- seen on before
- - incorrectCODE - current incorrect CODE
- - doublebubble - array ref of the bubble lines that have double
- bubble errors
- - missingbubble - array ref of the bubble lines that have missing
- bubble errors
-
-=cut
sub scantron_get_correction {
my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;
-
#FIXME in the case of a duplicated ID the previous line, probably need
#to show both the current line and the previous one and allow skipping
#the previous one or the current one
- $r->print("An error was detected ($error) ");
if ( $$scan_record{'scantron.PaperID'} =~ /\S/) {
- $r->print(" for PaperID ".
- $$scan_record{'scantron.PaperID'}." \n");
+ $r->print(
+ '
'
+ .&mt('An error was detected ([_1]) for PaperID [_2]',
+ "$error ",
+ ''.$$scan_record{'scantron.PaperID'}.' ')
+ ."
\n");
} else {
- $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(
+ ''
+ .&mt('An error was detected ([_1]) in scanline [_2] [_3]',
+ "$error ", $i, "
$line ")
+ ." \n");
+ }
+ my $message =
+ ''
+ .&mt('The ID on the form is [_1]',
+ "$$scan_record{'scantron.ID'} ")
+ .' '
+ .&mt('The name on the paper is [_1], [_2]',
+ $$scan_record{'scantron.LastName'},
+ $$scan_record{'scantron.FirstName'})
+ .'
';
$r->print(' '."\n");
$r->print(' '."\n");
+ # Array populated for doublebubble or
+ my @lines_to_correct; # missingbubble errors to build javascript
+ # to validate radio button checking
+
if ($error =~ /ID$/) {
if ($error eq 'incorrectID') {
- $r->print("The encoded ID is not in the classlist\n");
+ $r->print(''.&mt("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(''.&mt("The encoded ID has also been used by a previous paper [_1]",$arg)."
\n");
}
$r->print($message);
- $r->print("How should I handle this? \n");
+ $r->print("
".&mt("How should I handle this?")." \n");
$r->print("\n
");
#FIXME it would be nice if this sent back the user ID and
#could do partial userID matches
@@ -6384,14 +7136,15 @@ sub scantron_get_correction {
$r->print(' ');
} elsif ($error =~ /CODE$/) {
if ($error eq 'incorrectCODE') {
- $r->print("The encoded CODE is not in the list of possible CODEs
\n");
+ $r->print(''.&mt("The encoded CODE is not in the list of possible CODEs.")."
\n");
} elsif ($error eq 'duplicateCODE') {
- $r->print("The encoded CODE has also been used by a previous paper ".join(', ',@{$arg}).", and CODEs are supposed to be unique
\n");
+ $r->print(''.&mt("The encoded CODE has also been used by a previous paper [_1], and CODEs are supposed to be unique.",join(', ',@{$arg}))."
\n");
}
- $r->print("The CODE on the form is '".
- $$scan_record{'scantron.CODE'}."' \n");
+ $r->print("
".&mt('The CODE on the form is [_1]',
+ "'$$scan_record{'scantron.CODE'}' ")
+ ."
\n");
$r->print($message);
- $r->print("How should I handle this? \n");
+ $r->print("
".&mt("How should I handle this?")."
\n");
$r->print("\n ");
my $i=0;
if ($error eq 'incorrectCODE'
@@ -6400,21 +7153,31 @@ sub scantron_get_correction {
if ($closest > 0) {
foreach my $testcode (@{$closest}) {
my $checked='';
- if (!$i) { $checked=' checked="checked" '; }
- $r->print(" Use the similar CODE ".$testcode." instead. ");
+ if (!$i) { $checked=' checked="checked"'; }
+ $r->print("
+
+
+ ".&mt("Use the similar CODE [_1] instead.",
+ "".$testcode." ")."
+
+ ");
$r->print("\n ");
$i++;
}
}
}
if ($$scan_record{'scantron.CODE'}=~/\S/ ) {
- my $checked; if (!$i) { $checked=' checked="checked" '; }
- $r->print(" Use the CODE ".$$scan_record{'scantron.CODE'}." that is was on the paper, ignoring the error. ");
+ my $checked; if (!$i) { $checked=' checked="checked"'; }
+ $r->print("
+
+
+ ".&mt("Use the CODE [_1] that was on the paper, ignoring the error.",
+ "".$$scan_record{'scantron.CODE'}." ")."
+ ");
$r->print("\n ");
}
- $r->print(<
+ $r->print(&Apache::lonhtmlcommon::scripttag(<
ENDSCRIPT
my $href="/adm/pickcode?".
"form=".&escape("scantronupload").
@@ -6431,115 +7193,284 @@ ENDSCRIPT
"&curCODE=".&escape($$scan_record{'scantron.CODE'}).
"&scantron_selectfile=".&escape($env{'form.scantron_selectfile'});
if ($env{'form.scantron_CODElist'} =~ /\S/) {
- $r->print(" Select a CODE from the list of all CODEs and use it. Selected CODE is ");
+ $r->print("
+
+
+ ".&mt("[_1]Select[_2] a CODE from the list of all CODEs and use it.",
+ ""," ")."
+
+ ".&mt("Selected CODE is [_1]",' '));
$r->print("\n ");
}
- $r->print(" Use as the CODE.");
+ $r->print("
+
+
+ ".&mt("Use [_1] as the CODE.",
+ " "));
$r->print("\n ");
} elsif ($error eq 'doublebubble') {
- $r->print("There have been multiple bubbles scanned for a some question(s)
\n");
+ $r->print(''.&mt("There have been multiple bubbles scanned for some question(s)")."
\n");
+
+ # The form field scantron_questions is acutally a list of line numbers.
+ # represented by this form so:
+
+ my $line_list = &questions_to_line_list($arg);
+
$r->print(' ');
+ $line_list.'" />');
$r->print($message);
- $r->print("Please indicate which bubble should be used for grading
");
+ $r->print("".&mt("Please indicate which bubble should be used for grading")."
");
foreach my $question (@{$arg}) {
- my $selected = &get_response_bubbles($scan_record, $question);
- my @select_array = split(/:/,$selected);
- &scantron_bubble_selector($r,$scan_config,$question,
- @select_array);
+ my @linenums = &prompt_for_corrections($r,$question,$scan_config,
+ $scan_record, $error);
+ push(@lines_to_correct,@linenums);
}
+ $r->print(&verify_bubbles_checked(@lines_to_correct));
} elsif ($error eq 'missingbubble') {
- $r->print("There have been no bubbles scanned for some question(s)
\n");
+ $r->print(''.&mt("There have been [_1]no[_2] bubbles scanned for some question(s)",'',' ')."
\n");
$r->print($message);
- $r->print("Please indicate which bubble should be used for grading
");
- $r->print("Some questions have no scanned bubbles\n");
+ $r->print("".&mt("Please indicate which bubble should be used for grading.")."
");
+ $r->print(&mt("Some questions have no scanned bubbles.")."\n");
+
+ # The form field scantron_questions is actually a list of line numbers not
+ # a list of question numbers. Therefore:
+ #
+
+ my $line_list = &questions_to_line_list($arg);
+
$r->print(' ');
+ $line_list.'" />');
foreach my $question (@{$arg}) {
- my $selected = &get_response_bubbles($scan_record, $question);
- &scantron_bubble_selector($r,$scan_config,$question);
+ my @linenums = &prompt_for_corrections($r,$question,$scan_config,
+ $scan_record, $error);
+ push(@lines_to_correct,@linenums);
}
+ $r->print(&verify_bubbles_checked(@lines_to_correct));
} else {
$r->print("\n");
+}
+sub verify_bubbles_checked {
+ my (@ansnums) = @_;
+ my $ansnumstr = join('","',@ansnums);
+ my $warning = &mt("A bubble or 'No bubble' selection has not been made for one or more lines.");
+ my $output = &Apache::lonhtmlcommon::scripttag((< 1) {
+ var bubble_picked = 0;
+ for (var j=0; jprint("$quest ");
+=cut
- for (my $l = 0; $l < $lines; $l++) {
- if ($l != 0) {
- $r->print('');
- }
- my @selected = split(//,$lines[$l]);
- for (my $i=0;$i<$max;$i++) {
- $r->print("\n".'');
- if ($selected[0] eq $alphabet[$i]) {
- $r->print('X');
- shift(@selected) ;
- } else {
- $r->print(' ');
- }
- $r->print(' ');
-
- }
+sub prompt_for_corrections {
+ my ($r, $question, $scan_config, $scan_record, $error) = @_;
+ my ($current_line,$lines);
+ my @linenums;
+ my $questionnum = $question;
+ if ($question =~ /^(\d+)\.(\d+)$/) {
+ $question = $1;
+ $current_line = $first_bubble_line{$question-1} + 1 ;
+ my $subquestion = $2;
+ my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
+ my $subcount = 1;
+ while ($subcount<$subquestion) {
+ $current_line += $subans[$subcount-1];
+ $subcount ++;
+ }
+ $lines = $subans[$subquestion-1];
+ } else {
+ $current_line = $first_bubble_line{$question-1} + 1 ;
+ $lines = $bubble_lines_per_response{$question-1};
+ }
+ if ($lines > 1) {
+ $r->print(&mt('The group of bubble lines below responds to a single question.').' ');
+ if (($responsetype_per_response{$question-1} eq 'essayresponse') ||
+ ($responsetype_per_response{$question-1} eq 'formularesponse') ||
+ ($responsetype_per_response{$question-1} eq 'stringresponse') ||
+ ($responsetype_per_response{$question-1} eq 'imageresponse') ||
+ ($responsetype_per_response{$question-1} eq 'reactionresponse') ||
+ ($responsetype_per_response{$question-1} eq 'organicresponse')) {
+ $r->print(&mt("Although this particular question type requires handgrading, the instructions for this question in the exam directed students to leave [quant,_1,line] blank on their bubblesheets.",$lines).' '.&mt('A non-zero score can be assigned to the student during bubblesheet grading by selecting a bubble in at least one line.').' '.&mt('The score for this question will be a sum of the numeric values for the selected bubbles from each line, where A=1 point, B=2 points etc.').' '.&mt("To assign a score of zero for this question, mark all lines as 'No bubble'.").' ');
+ } else {
+ $r->print(&mt("Select at most one bubble in a single line and select 'No Bubble' in all the other lines. ")." ");
+ }
+ }
+ for (my $i =0; $i < $lines; $i++) {
+ my $selected = $$scan_record{"scantron.$current_line.answer"};
+ &scantron_bubble_selector($r,$scan_config,$current_line,
+ $questionnum,$error,split('', $selected));
+ push(@linenums,$current_line);
+ $current_line++;
+ }
+ if ($lines > 1) {
+ $r->print(" ");
+ }
+ return @linenums;
+}
- if ($l == 0) {
- my $lspan = $total_lines * 2; # 2 table rows per bubble line.
+=pod
- $r->print(' No bubble ');
-
- }
+=item scantron_bubble_selector
+
+ Generates the html radiobuttons to correct a single bubble line
+ possibly showing the existing the selected bubbles if known
- $r->print(' ');
+ Arguments:
+ $r - Apache request object
+ $scan_config - hash from &get_scantron_config()
+ $line - Number of the line being displayed.
+ $questionnum - Question number (may include subquestion)
+ $error - Type of error.
+ @selected - Array of bubbles picked on this line.
- # FIXME: This may have to be a bit more clever for
- # multiline questions (different values e.g..).
+=cut
- for (my $i=0;$i<$max;$i++) {
- $r->print("\n".
- ' '.$alphabet[$i]." ");
- }
- $r->print(' ');
+sub scantron_bubble_selector {
+ my ($r,$scan_config,$line,$questionnum,$error,@selected)=@_;
+ my $max=$$scan_config{'Qlength'};
-
+ my $scmode=$$scan_config{'Qon'};
+ if ($scmode eq 'number' || $scmode eq 'letter') {
+ if (($$scan_config{'BubblesPerRow'} =~ /^\d+$/) &&
+ ($$scan_config{'BubblesPerRow'} > 0)) {
+ $max=$$scan_config{'BubblesPerRow'};
+ if (($scmode eq 'number') && ($max > 10)) {
+ $max = 10;
+ } elsif (($scmode eq 'letter') && $max > 26) {
+ $max = 26;
+ }
+ } else {
+ $max = 10;
+ }
}
- $r->print('
');
+
+ my @alphabet=('A'..'Z');
+ $r->print(&Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_row());
+ $r->print('');
+ for (my $i=0;$i<$max+1;$i++) {
+ $r->print("\n".'');
+ if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) }
+ else { $r->print(' '); }
+ $r->print(' ');
+ }
+ $r->print(&Apache::loncommon::end_data_table_row().
+ &Apache::loncommon::start_data_table_row());
+ for (my $i=0;$i<$max;$i++) {
+ $r->print("\n".
+ ' '.$alphabet[$i]." ");
+ }
+ my $nobub_checked = ' ';
+ if ($error eq 'missingbubble') {
+ $nobub_checked = ' checked = "checked" ';
+ }
+ $r->print("\n".' '.&mt('No bubble').
+ ' '."\n".' ');
+ $r->print(&Apache::loncommon::end_data_table_row().
+ &Apache::loncommon::end_data_table());
}
=pod
@@ -6665,7 +7596,12 @@ sub scantron_validate_CODE {
my %allcodes=&get_codes();
- &scantron_get_maxbubble(); # parse needs the lines per response array.
+ my $nav_error;
+ &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the lines per response array.
+ if ($nav_error) {
+ $r->print(&navmap_errormsg());
+ return(1,$currentphase);
+ }
my ($scanlines,$scan_data)=&scantron_getfile();
for (my $i=0;$i<=$scanlines->{'count'};$i++) {
@@ -6696,7 +7632,7 @@ sub scantron_validate_CODE {
$line,'duplicateCODE',$usedCODEs{$CODE});
return(1,$currentphase);
}
- push (@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});
+ push(@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});
}
return (0,$currentphase+1);
}
@@ -6719,8 +7655,12 @@ sub scantron_validate_doublebubble {
#get scantron line setup
my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
my ($scanlines,$scan_data)=&scantron_getfile();
-
- &scantron_get_maxbubble(); # parse needs the bubble line array.
+ my $nav_error;
+ &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the bubble line array.
+ if ($nav_error) {
+ $r->print(&navmap_errormsg());
+ return(1,$currentphase);
+ }
for (my $i=0;$i<=$scanlines->{'count'};$i++) {
my $line=&scantron_get_line($scanlines,$scan_data,$i);
@@ -6736,24 +7676,9 @@ sub scantron_validate_doublebubble {
return (0,$currentphase+1);
}
-=pod
-
-=item scantron_get_maxbubble
-
- Returns the maximum number of bubble lines that are expected to
- occur. Does this by walking the selected sequence rendering the
- resource and then checking &Apache::lonxml::get_problem_counter()
- for what the current value of the problem counter is.
-
- Caches the results to $env{'form.scantron_maxbubble'},
- $env{'form.scantron.bubble_lines.n'} and
- $env{'form.scantron.first_bubble_line.n'}
- which are the total number of bubble, lines, the number of bubble
- lines for reponse n and number of the first bubble line for response n.
-=cut
-
-sub scantron_get_maxbubble {
+sub scantron_get_maxbubble {
+ my ($nav_error,$scantron_config) = @_;
if (defined($env{'form.scantron_maxbubble'}) &&
$env{'form.scantron_maxbubble'}) {
&restore_bubble_lines();
@@ -6764,55 +7689,87 @@ sub scantron_get_maxbubble {
&Apache::lonnet::decode_symb($env{'form.selectpage'});
my $navmap=Apache::lonnavmaps::navmap->new();
+ unless (ref($navmap)) {
+ if (ref($nav_error)) {
+ $$nav_error = 1;
+ }
+ return;
+ }
my $map=$navmap->getResourceByUrl($sequence);
my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
+ my $bubbles_per_row = &bubblesheet_bubbles_per_row($scantron_config);
&Apache::lonxml::clear_problem_counter();
- my $uname = $env{'form.student'};
- my $udom = $env{'form.userdom'};
+ my $uname = $env{'user.name'};
+ my $udom = $env{'user.domain'};
my $cid = $env{'request.course.id'};
my $total_lines = 0;
%bubble_lines_per_response = ();
%first_bubble_line = ();
+ %subdivided_bubble_lines = ();
+ %responsetype_per_response = ();
-
my $response_number = 0;
my $bubble_line = 0;
foreach my $resource (@resources) {
- my $symb = $resource->symb();
- &Apache::lonxml::clear_bubble_lines_for_part();
- my $result=&Apache::lonnet::ssi($resource->src(),
- ('symb' => $resource->symb()),
- ('grade_target' => 'analyze'),
- ('grade_courseid' => $cid),
- ('grade_domain' => $udom),
- ('grade_username' => $uname));
- my (undef, $an) =
- split(/_HASH_REF__/,$result, 2);
-
- my %analysis = &Apache::lonnet::str2hash($an);
-
-
-
- foreach my $part_id (@{$analysis{'parts'}}) {
-
-
- my $lines = $analysis{"$part_id.bubble_lines"};;
-
- # TODO - make this a persistent hash not an array.
-
-
- $first_bubble_line{$response_number} = $bubble_line;
- $bubble_lines_per_response{$response_number} = $lines;
- $response_number++;
+ my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,$udom,undef,$bubbles_per_row);
+ if ((ref($analysis) eq 'HASH') && (ref($parts) eq 'ARRAY')) {
+ foreach my $part_id (@{$parts}) {
+ my $lines;
+
+ # TODO - make this a persistent hash not an array.
+
+ # optionresponse, matchresponse and rankresponse type items
+ # render as separate sub-questions in exam mode.
+ if (($analysis->{$part_id.'.type'} eq 'optionresponse') ||
+ ($analysis->{$part_id.'.type'} eq 'matchresponse') ||
+ ($analysis->{$part_id.'.type'} eq 'rankresponse')) {
+ my ($numbub,$numshown);
+ if ($analysis->{$part_id.'.type'} eq 'optionresponse') {
+ if (ref($analysis->{$part_id.'.options'}) eq 'ARRAY') {
+ $numbub = scalar(@{$analysis->{$part_id.'.options'}});
+ }
+ } elsif ($analysis->{$part_id.'.type'} eq 'matchresponse') {
+ if (ref($analysis->{$part_id.'.items'}) eq 'ARRAY') {
+ $numbub = scalar(@{$analysis->{$part_id.'.items'}});
+ }
+ } elsif ($analysis->{$part_id.'.type'} eq 'rankresponse') {
+ if (ref($analysis->{$part_id.'.foils'}) eq 'ARRAY') {
+ $numbub = scalar(@{$analysis->{$part_id.'.foils'}});
+ }
+ }
+ if (ref($analysis->{$part_id.'.shown'}) eq 'ARRAY') {
+ $numshown = scalar(@{$analysis->{$part_id.'.shown'}});
+ }
+ my $bubbles_per_row =
+ &bubblesheet_bubbles_per_row($scantron_config);
+ my $inner_bubble_lines = int($numbub/$bubbles_per_row);
+ if (($numbub % $bubbles_per_row) != 0) {
+ $inner_bubble_lines++;
+ }
+ for (my $i=0; $i<$numshown; $i++) {
+ $subdivided_bubble_lines{$response_number} .=
+ $inner_bubble_lines.',';
+ }
+ $subdivided_bubble_lines{$response_number} =~ s/,$//;
+ $lines = $numshown * $inner_bubble_lines;
+ } else {
+ $lines = $analysis->{"$part_id.bubble_lines"};
+ }
- $bubble_line += $lines;
- $total_lines += $lines;
- }
+ $first_bubble_line{$response_number} = $bubble_line;
+ $bubble_lines_per_response{$response_number} = $lines;
+ $responsetype_per_response{$response_number} =
+ $analysis->{$part_id.'.type'};
+ $response_number++;
+ $bubble_line += $lines;
+ $total_lines += $lines;
+ }
+ }
}
- &Apache::lonnet::delenv('scantron\.');
+ &Apache::lonnet::delenv('scantron.');
&save_bubble_lines();
$env{'form.scantron_maxbubble'} =
@@ -6820,15 +7777,17 @@ sub scantron_get_maxbubble {
return $env{'form.scantron_maxbubble'};
}
-=pod
-
-=item scantron_validate_missingbubbles
-
- Validates all scanlines in the selected file to not have any
- answers that don't have bubbles that have not been verified
- to be bubble free.
-
-=cut
+sub bubblesheet_bubbles_per_row {
+ my ($scantron_config) = @_;
+ my $bubbles_per_row;
+ if (ref($scantron_config) eq 'HASH') {
+ $bubbles_per_row = $scantron_config->{'BubblesPerRow'};
+ }
+ if ((!$bubbles_per_row) || ($bubbles_per_row < 1)) {
+ $bubbles_per_row = 10;
+ }
+ return $bubbles_per_row;
+}
sub scantron_validate_missingbubbles {
my ($r,$currentphase) = @_;
@@ -6839,7 +7798,11 @@ sub scantron_validate_missingbubbles {
#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();
+ my $nav_error;
+ my $max_bubble=&scantron_get_maxbubble(\$nav_error,\%scantron_config);
+ if ($nav_error) {
+ return(1,$currentphase);
+ }
if (!$max_bubble) { $max_bubble=2**31; }
for (my $i=0;$i<=$scanlines->{'count'};$i++) {
my $line=&scantron_get_line($scanlines,$scan_data,$i);
@@ -6848,8 +7811,29 @@ sub scantron_validate_missingbubbles {
$scan_data);
if (!defined($$scan_record{'scantron.missingerror'})) { next; }
my @to_correct;
+
+ # Probably here's where the error is...
+
foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {
- if ($missing > $max_bubble) { next; }
+ my $lastbubble;
+ if ($missing =~ /^(\d+)\.(\d+)$/) {
+ my $question = $1;
+ my $subquestion = $2;
+ if (!defined($first_bubble_line{$question -1})) { next; }
+ my $first = $first_bubble_line{$question-1};
+ my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
+ my $subcount = 1;
+ while ($subcount<$subquestion) {
+ $first += $subans[$subcount-1];
+ $subcount ++;
+ }
+ my $count = $subans[$subquestion-1];
+ $lastbubble = $first + $count;
+ } else {
+ if (!defined($first_bubble_line{$missing - 1})) { next; }
+ $lastbubble = $first_bubble_line{$missing - 1} + $bubble_lines_per_response{$missing - 1};
+ }
+ if ($lastbubble > $max_bubble) { next; }
push(@to_correct,$missing);
}
if (@to_correct) {
@@ -6862,45 +7846,93 @@ sub scantron_validate_missingbubbles {
return (0,$currentphase+1);
}
-=pod
-
-=item scantron_process_students
-
- Routine that does the actual grading of the bubble sheet information.
-
- The parsed scanline hash is added to %env
-
- Then foreach unskipped scanline it does an &Apache::lonnet::ssi()
- foreach resource , with the form data of
-
- 'submitted' =>'scantron'
- 'grade_target' =>'grade',
- 'grade_username'=> username of student
- 'grade_domain' => domain of student
- 'grade_courseid'=> of course
- 'grade_symb' => symb of resource to grade
-
- This triggers a grading pass. The problem grading code takes care
- of converting the bubbled letter information (now in %env) into a
- valid submission.
-
-=cut
+sub hand_bubble_option {
+ my (undef, undef, $sequence) =
+ &Apache::lonnet::decode_symb($env{'form.selectpage'});
+ return if ($sequence eq '');
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ unless (ref($navmap)) {
+ return;
+ }
+ my $needs_hand_bubbles;
+ my $map=$navmap->getResourceByUrl($sequence);
+ my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
+ foreach my $res (@resources) {
+ if (ref($res)) {
+ if ($res->is_problem()) {
+ my $partlist = $res->parts();
+ foreach my $part (@{ $partlist }) {
+ my @types = $res->responseType($part);
+ if (grep(/^(chem|essay|image|formula|math|string|functionplot)$/,@types)) {
+ $needs_hand_bubbles = 1;
+ last;
+ }
+ }
+ }
+ }
+ }
+ if ($needs_hand_bubbles) {
+ my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+ my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
+ return &mt('The sequence to be graded contains response types which are handgraded.').''.
+ &mt('If you have already graded these by bubbling sheets to indicate points awarded, [_1]what point value is assigned to a filled last bubble in each row?',' ').
+ ' '.&mt('[quant,_1,point]',$bubbles_per_row).' '.&mt('or').' '.
+ ' 0 points
';
+ }
+ return;
+}
sub scantron_process_students {
- my ($r) = @_;
+ my ($r,$symb) = @_;
+
my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
- my ($symb)=&get_symb($r);
- if (!$symb) {return '';}
+ if (!$symb) {
+ return '';
+ }
my $default_form_data=&defaultFormData($symb);
my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
+ my $bubbles_per_row =
+ &bubblesheet_bubbles_per_row(\%scantron_config);
my ($scanlines,$scan_data)=&scantron_getfile();
my $classlist=&Apache::loncoursedata::get_classlist();
my %idmap=&username_to_idmap($classlist);
my $navmap=Apache::lonnavmaps::navmap->new();
+ unless (ref($navmap)) {
+ $r->print(&navmap_errormsg());
+ return '';
+ }
my $map=$navmap->getResourceByUrl($sequence);
my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
-# $r->print("geto ".scalar(@resources)." ");
+ my (%grader_partids_by_symb,%grader_randomlists_by_symb);
+ &graders_resources_pass(\@resources,\%grader_partids_by_symb,
+ \%grader_randomlists_by_symb,$bubbles_per_row);
+ my $resource_error;
+ foreach my $resource (@resources) {
+ my $ressymb;
+ if (ref($resource)) {
+ $ressymb = $resource->symb();
+ } else {
+ $resource_error = 1;
+ last;
+ }
+ my ($analysis,$parts) =
+ &scantron_partids_tograde($resource,$env{'request.course.id'},
+ $env{'user.name'},$env{'user.domain'},1,$bubbles_per_row);
+ $grader_partids_by_symb{$ressymb} = $parts;
+ if (ref($analysis) eq 'HASH') {
+ if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') {
+ $grader_randomlists_by_symb{$ressymb} =
+ $analysis->{'parts_withrandomlist'};
+ }
+ }
+ }
+ if ($resource_error) {
+ $r->print(&navmap_errormsg());
+ return '';
+ }
+
+ my ($uname,$udom);
my $result= <
@@ -6909,19 +7941,36 @@ SCANTRONFORM
$r->print($result);
my @delayqueue;
- my %completedstudents;
+ my (%completedstudents,%scandata);
+ my $lock=&Apache::lonnet::set_lock(&mt('Grading bubblesheet exam'));
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 %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count);
+ &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,'Processing first student');
+ $r->print(' ');
my $start=&Time::HiRes::time();
my $i=-1;
- my ($uname,$udom,$started);
+ my $started;
- &scantron_get_maxbubble(); # Need the bubble lines array to parse.
+ my $nav_error;
+ &scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse.
+ if ($nav_error) {
+ $r->print(&navmap_errormsg());
+ return '';
+ }
+
+ # If an ssi failed in scantron_get_maxbubble, put an error message out to
+ # the user and return.
+
+ if ($ssi_error) {
+ $r->print("");
+ &ssi_print_error($r);
+ &Apache::lonnet::remove_lock($lock);
+ return ''; # Dunno why the other returns return '' rather than just returning.
+ }
+
+ my %lettdig = &letter_to_digits();
+ my $numletts = scalar(keys(%lettdig));
while ($i<$scanlines->{'count'}) {
($uname,$udom)=('','');
@@ -6929,8 +7978,7 @@ SCANTRONFORM
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');
+ &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
}
$started=1;
my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
@@ -6948,164 +7996,398 @@ SCANTRONFORM
}
($uname,$udom)=split(/:/,$uname);
+ my (%partids_by_symb,$res_error);
+ foreach my $resource (@resources) {
+ my $ressymb;
+ if (ref($resource)) {
+ $ressymb = $resource->symb();
+ } else {
+ $res_error = 1;
+ last;
+ }
+ if ((exists($grader_randomlists_by_symb{$ressymb})) ||
+ (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
+ my ($analysis,$parts) =
+ &scantron_partids_tograde($resource,$env{'request.course.id'},$uname,$udom,undef,$bubbles_per_row);
+ $partids_by_symb{$ressymb} = $parts;
+ } else {
+ $partids_by_symb{$ressymb} = $grader_partids_by_symb{$ressymb};
+ }
+ }
+
+ if ($res_error) {
+ &scantron_add_delay(\@delayqueue,$line,
+ 'An error occurred while grading student '.$uname,2);
+ next;
+ }
+
&Apache::lonxml::clear_problem_counter();
- &Apache::lonnet::appenv(%$scan_record);
+ &Apache::lonnet::appenv($scan_record);
if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
&scantron_putfile($scanlines,$scan_data);
}
- my $i=0;
- foreach my $resource (@resources) {
- $i++;
- 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 '') {
- }
- if (&Apache::loncommon::connection_aborted($r)) { last; }
- }
+ my $scancode;
+ if ((exists($scan_record->{'scantron.CODE'})) &&
+ (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
+ $scancode = $scan_record->{'scantron.CODE'};
+ } else {
+ $scancode = '';
+ }
+
+ if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
+ \@resources,\%partids_by_symb,
+ $bubbles_per_row) eq 'ssi_error') {
+ $ssi_error = 0; # So end of handler error message does not trigger.
+ $r->print("");
+ &ssi_print_error($r);
+ &Apache::lonnet::remove_lock($lock);
+ return ''; # Why return ''? Beats me.
+ }
+
$completedstudents{$uname}={'line'=>$line};
- if (&Apache::loncommon::connection_aborted($r)) { last; }
+ if ($env{'form.verifyrecord'}) {
+ my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
+ my $studentdata = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
+ chomp($studentdata);
+ $studentdata =~ s/\r$//;
+ my $studentrecord = '';
+ my $counter = -1;
+ foreach my $resource (@resources) {
+ my $ressymb = $resource->symb();
+ ($counter,my $recording) =
+ &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
+ $counter,$studentdata,$partids_by_symb{$ressymb},
+ \%scantron_config,\%lettdig,$numletts);
+ $studentrecord .= $recording;
+ }
+ if ($studentrecord ne $studentdata) {
+ &Apache::lonxml::clear_problem_counter();
+ if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
+ \@resources,\%partids_by_symb,
+ $bubbles_per_row) eq 'ssi_error') {
+ $ssi_error = 0; # So end of handler error message does not trigger.
+ $r->print("");
+ &ssi_print_error($r);
+ &Apache::lonnet::remove_lock($lock);
+ delete($completedstudents{$uname});
+ return '';
+ }
+ $counter = -1;
+ $studentrecord = '';
+ foreach my $resource (@resources) {
+ my $ressymb = $resource->symb();
+ ($counter,my $recording) =
+ &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
+ $counter,$studentdata,$partids_by_symb{$ressymb},
+ \%scantron_config,\%lettdig,$numletts);
+ $studentrecord .= $recording;
+ }
+ if ($studentrecord ne $studentdata) {
+ $r->print('');
+ if ($scancode eq '') {
+ $r->print(&mt('Mismatch grading bubblesheet for user: [_1] with ID: [_2].',
+ $uname.':'.$udom,$scan_record->{'scantron.ID'}));
+ } else {
+ $r->print(&mt('Mismatch grading bubblesheet for user: [_1] with ID: [_2] and CODE: [_3].',
+ $uname.':'.$udom,$scan_record->{'scantron.ID'},$scancode));
+ }
+ $r->print(' '.&Apache::loncommon::start_data_table()."\n".
+ &Apache::loncommon::start_data_table_header_row()."\n".
+ '
'.&mt('Source').' '.&mt('Bubbled responses').' '.
+ &Apache::loncommon::end_data_table_header_row()."\n".
+ &Apache::loncommon::start_data_table_row().
+ ''.&mt('Bubblesheet').' '.
+ ''.$studentdata.' '.
+ &Apache::loncommon::end_data_table_row().
+ &Apache::loncommon::start_data_table_row().
+ ''.&mt('Stored submissions').' '.
+ ''.$studentrecord.' '."\n".
+ &Apache::loncommon::end_data_table_row().
+ &Apache::loncommon::end_data_table().'');
+ } else {
+ $r->print(''.
+ &mt('A second grading pass was needed for user: [_1] with ID: [_2], because a mismatch was seen on the first pass.',$uname.':'.$udom,$scan_record->{'scantron.ID'}).' '.
+ &mt("As a consequence, this user's submission history records two tries.").
+ ' ');
+ }
+ }
+ }
+ if (&Apache::loncommon::connection_aborted($r)) { last; }
} continue {
&Apache::lonxml::clear_problem_counter();
- &Apache::lonnet::delenv('scantron\.');
+ &Apache::lonnet::delenv('scantron.');
}
&Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
+ &Apache::lonnet::remove_lock($lock);
# my $lasttime = &Time::HiRes::time()-$start;
# $r->print("took $lasttime
");
$r->print("");
- $r->print(&show_grading_menu_form($symb));
return '';
}
-=pod
-
-=item scantron_upload_scantron_data
-
- Creates the screen for adding a new bubble sheet data file to a course.
+sub graders_resources_pass {
+ my ($resources,$grader_partids_by_symb,$grader_randomlists_by_symb,
+ $bubbles_per_row) = @_;
+ if ((ref($resources) eq 'ARRAY') && (ref($grader_partids_by_symb)) &&
+ (ref($grader_randomlists_by_symb) eq 'HASH')) {
+ foreach my $resource (@{$resources}) {
+ my $ressymb = $resource->symb();
+ my ($analysis,$parts) =
+ &scantron_partids_tograde($resource,$env{'request.course.id'},
+ $env{'user.name'},$env{'user.domain'},1,$bubbles_per_row);
+ $grader_partids_by_symb->{$ressymb} = $parts;
+ if (ref($analysis) eq 'HASH') {
+ if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') {
+ $grader_randomlists_by_symb->{$ressymb} =
+ $analysis->{'parts_withrandomlist'};
+ }
+ }
+ }
+ }
+ return;
+}
-=cut
+sub grade_student_bubbles {
+ my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts,$bubbles_per_row) = @_;
+# Walk folder as student here to get resources in order student sees.
+ if (ref($resources) eq 'ARRAY') {
+ my $count = 0;
+ foreach my $resource (@{$resources}) {
+ my $ressymb = $resource->symb();
+ my %form = ('submitted' => 'scantron',
+ 'grade_target' => 'grade',
+ 'grade_username' => $uname,
+ 'grade_domain' => $udom,
+ 'grade_courseid' => $env{'request.course.id'},
+ 'grade_symb' => $ressymb,
+ 'CODE' => $scancode
+ );
+ if ($bubbles_per_row ne '') {
+ $form{'bubbles_per_row'} = $bubbles_per_row;
+ }
+ if ($env{'form.scantron_lastbubblepoints'} ne '') {
+ $form{'scantron_lastbubblepoints'} = $env{'form.scantron_lastbubblepoints'};
+ }
+ if (ref($parts) eq 'HASH') {
+ if (ref($parts->{$ressymb}) eq 'ARRAY') {
+ foreach my $part (@{$parts->{$ressymb}}) {
+ $form{'scantron_questnum_start.'.$part} =
+ 1+$env{'form.scantron.first_bubble_line.'.$count};
+ $count++;
+ }
+ }
+ }
+ my $result=&ssi_with_retries($resource->src(),$ssi_retries,%form);
+ return 'ssi_error' if ($ssi_error);
+ last if (&Apache::loncommon::connection_aborted($r));
+ }
+ }
+ return;
+}
sub scantron_upload_scantron_data {
- my ($r)=@_;
- $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}));
+ my ($r,$symb)=@_;
+ my $dom = $env{'request.role.domain'};
+ my $domdesc = &Apache::lonnet::domain($dom,'description');
+ $r->print(&Apache::loncommon::coursebrowser_javascript($dom));
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(<
+ 'coursename',$dom);
+ my $syllabuslink = ''.&mt('Syllabus').' '.
+ (' 'x2).&mt('(shows course personnel)');
+ my $default_form_data=&defaultFormData($symb);
+ my $nofile_alert = &mt('Please use the browse button to select a file from your local directory.');
+ my $nocourseid_alert = &mt("Please use the 'Select Course' link to open a separate window where you can search for a course to which a file can be uploaded.");
+ $r->print(&Apache::lonhtmlcommon::scripttag('
function checkUpload(formname) {
if (formname.upfile.value == "") {
- alert("Please use the browse button to select a file from your local directory.");
+ alert("'.$nofile_alert.'");
return false;
}
+ if (formname.courseid.value == "") {
+ alert("'.$nocourseid_alert.'");
+ return false;
+ }
formname.submit();
}
-
-
-$default_form_data
-
-
-
+ function ToSyllabus() {
+ var cdom = '."'$dom'".';
+ var cnum = document.rules.courseid.value;
+ if (cdom == "" || cdom == null) {
+ return;
+ }
+ if (cnum == "" || cnum == null) {
+ return;
+ }
+ syllwin=window.open("/public/"+cdom+"/"+cnum+"/syllabus","LONCAPASyllabus",
+ "height=350,width=350,scrollbars=yes,menubar=no");
+ return;
+ }
+
+'));
+ $r->print('
+'.&mt('Send bubblesheet data to a course').'
+
+
+'.$default_form_data.
+ &Apache::lonhtmlcommon::start_pick_box().
+ &Apache::lonhtmlcommon::row_title(&mt('Course ID')).
+ ' '.$select_link.
+ &Apache::lonhtmlcommon::row_closure().
+ &Apache::lonhtmlcommon::row_title(&mt('Course Name')).
+ ' '.$syllabuslink.
+ &Apache::lonhtmlcommon::row_closure().
+ &Apache::lonhtmlcommon::row_title(&mt('Domain')).
+ ' '.$domdesc.
+ &Apache::lonhtmlcommon::row_closure().
+ &Apache::lonhtmlcommon::row_title(&mt('File to upload')).
+ ' '.
+ &Apache::lonhtmlcommon::row_closure(1).
+ &Apache::lonhtmlcommon::end_pick_box().'
+
+
+
-UPLOAD
+');
return '';
}
-=pod
-
-=item scantron_upload_scantron_data_save
-
- Adds a provided bubble information data file to the course if user
- has the correct privileges to do so.
-
-=cut
sub scantron_upload_scantron_data_save {
- my($r)=@_;
- my ($symb)=&get_symb($r,1);
+ my($r,$symb)=@_;
my $doanotherupload=
''."\n".
' '."\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(&mt("You are not allowed to upload bubblesheet data to the requested course.")." ");
+ unless ($symb) {
$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
- #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;
+ my $uploadedfile;
+ $r->print(''.&mt("Uploading file to [_1]",$coursedata{'description'}).' ');
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.");
+ $r->print(&mt('[_1]Error:[_2] The file you attempted to upload, [_3] contained no information. Please check that you entered the correct filename.','',' ',''.&HTML::Entities::encode($env{'form.upfile.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." ");
+ my $result =
+ &Apache::lonnet::userfileupload('upfile','','scantron','','','',
+ $env{'form.courseid'},$env{'form.domainid'});
+ if ($result =~ m{^/uploaded/}) {
+ $r->print(&mt('[_1]Success:[_2] Successfully uploaded [_3] bytes of data into location: [_4]',
+ '',' ',(length($env{'form.upfile'})-1),
+ ''.$result.' '));
+ ($uploadedfile) = ($result =~ m{/([^/]+)$});
+ $r->print(&validate_uploaded_scantron_file($env{'form.domainid'},
+ $env{'form.courseid'},$uploadedfile));
} else {
- $r->print("Error: An error (".$result.") occurred when attempting to upload the file, ".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')." ");
+ $r->print(&mt('[_1]Error:[_2] An error ([_3]) occurred when attempting to upload the file, [_4]',
+ '',' ',$result,
+ ''.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').' '));
}
}
if ($symb) {
- $r->print(&scantron_selectphase($r,$uploadedfile));
+ $r->print(&scantron_selectphase($r,$uploadedfile,$symb));
} else {
$r->print($doanotherupload);
}
return '';
}
-=pod
-
-=item valid_file
-
- Validates that the requested bubble data file exists in the course.
-
-=cut
+sub validate_uploaded_scantron_file {
+ my ($cdom,$cname,$fname) = @_;
+ my $scanlines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.$fname);
+ my @lines;
+ if ($scanlines ne '-1') {
+ @lines=split("\n",$scanlines,-1);
+ }
+ my $output;
+ if (@lines) {
+ my (%counts,$max_match_format);
+ my ($max_match_count,$max_match_pct) = (0,0);
+ my $classlist = &Apache::loncoursedata::get_classlist($cdom,$cname);
+ my %idmap = &username_to_idmap($classlist);
+ foreach my $key (keys(%idmap)) {
+ my $lckey = lc($key);
+ $idmap{$lckey} = $idmap{$key};
+ }
+ my %unique_formats;
+ my @formatlines = &get_scantronformat_file();
+ foreach my $line (@formatlines) {
+ chomp($line);
+ my @config = split(/:/,$line);
+ my $idstart = $config[5];
+ my $idlength = $config[6];
+ if (($idstart ne '') && ($idlength > 0)) {
+ if (ref($unique_formats{$idstart.':'.$idlength}) eq 'ARRAY') {
+ push(@{$unique_formats{$idstart.':'.$idlength}},$config[0].':'.$config[1]);
+ } else {
+ $unique_formats{$idstart.':'.$idlength} = [$config[0].':'.$config[1]];
+ }
+ }
+ }
+ foreach my $key (keys(%unique_formats)) {
+ my ($idstart,$idlength) = split(':',$key);
+ %{$counts{$key}} = (
+ 'found' => 0,
+ 'total' => 0,
+ );
+ foreach my $line (@lines) {
+ next if ($line =~ /^#/);
+ next if ($line =~ /^[\s\cz]*$/);
+ my $id = substr($line,$idstart-1,$idlength);
+ $id = lc($id);
+ if (exists($idmap{$id})) {
+ $counts{$key}{'found'} ++;
+ }
+ $counts{$key}{'total'} ++;
+ }
+ if ($counts{$key}{'total'}) {
+ my $percent_match = (100*$counts{$key}{'found'})/($counts{$key}{'total'});
+ if (($max_match_format eq '') || ($percent_match > $max_match_pct)) {
+ $max_match_pct = $percent_match;
+ $max_match_format = $key;
+ $max_match_count = $counts{$key}{'total'};
+ }
+ }
+ }
+ if (ref($unique_formats{$max_match_format}) eq 'ARRAY') {
+ my $format_descs;
+ my $numwithformat = @{$unique_formats{$max_match_format}};
+ for (my $i=0; $i<$numwithformat; $i++) {
+ my ($name,$desc) = split(':',$unique_formats{$max_match_format}[$i]);
+ if ($i<$numwithformat-2) {
+ $format_descs .= '"'.$desc.' ", ';
+ } elsif ($i==$numwithformat-2) {
+ $format_descs .= '"'.$desc.' " '.&mt('and').' ';
+ } elsif ($i==$numwithformat-1) {
+ $format_descs .= '"'.$desc.' "';
+ }
+ }
+ my $showpct = sprintf("%.0f",$max_match_pct).'%';
+ $output .= ' '.&mt('Comparison of student IDs in the uploaded file with the course roster found matches for [_1] of the [_2] entries in the file (for the format defined for [_3]).',''.$showpct.' ',''.$max_match_count.' ',$format_descs).
+ ' '.&mt('A low percentage of matches results from one of the following:').''.
+ ''.&mt('The file was uploaded to the wrong course').' '.
+ ''.&mt('The data are not in the format expected for the domain: [_1]',
+ ''.$cdom.' ').' '.
+ ''.&mt('Students did not bubble their IDs, or mis-bubbled them').' '.
+ ''.&mt('The course roster is not up to date').' '.
+ ' ';
+ }
+ } else {
+ $output = ''.&mt('Uploaded file contained no data').' ';
+ }
+ return $output;
+}
sub valid_file {
my ($requested_file)=@_;
@@ -7115,29 +8397,18 @@ sub valid_file {
return 0;
}
-=pod
-
-=item scantron_download_scantron_data
-
- Shows a list of the three internal files (original, corrected,
- skipped) for a specific bubble sheet data file that exists in the
- course.
-
-=cut
-
sub scantron_download_scantron_data {
- my ($r)=@_;
- my $default_form_data=&defaultFormData(&get_symb($r,1));
+ my ($r,$symb)=@_;
+ my $default_form_data=&defaultFormData($symb);
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(<print('
- The requested file name was invalid.
+ '.&mt('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;
@@ -7146,26 +8417,318 @@ ERROR
&Apache::lonnet::allowuploaded('/adm/grades',$orig);
&Apache::lonnet::allowuploaded('/adm/grades',$corrected);
&Apache::lonnet::allowuploaded('/adm/grades',$skipped);
- $r->print(<print('
- Original file as uploaded by the scantron office.
+ '.&mt('[_1]Original[_2] file as uploaded by the scantron office.',
+ '',' ').'
- Corrections , a file of corrected records that were used in grading.
+ '.&mt('[_1]Corrections[_2], a file of corrected records that were used in grading.',
+ '',' ').'
- Skipped , a file of records that were skipped.
+ '.&mt('[_1]Skipped[_2], a file of records that were skipped.',
+ '',' ').'
-DOWNLOAD
- $r->print(&show_grading_menu_form(&get_symb($r,1)));
+');
return '';
}
-=pod
+sub checkscantron_results {
+ my ($r,$symb) = @_;
+ if (!$symb) {return '';}
+ my $cid = $env{'request.course.id'};
+ my %lettdig = &letter_to_digits();
+ my $numletts = scalar(keys(%lettdig));
+ my $cnum = $env{'course.'.$cid.'.num'};
+ my $cdom = $env{'course.'.$cid.'.domain'};
+ my (undef, undef, $sequence) = &Apache::lonnet::decode_symb($env{'form.selectpage'});
+ my %record;
+ my %scantron_config =
+ &Apache::grades::get_scantron_config($env{'form.scantron_format'});
+ my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
+ my ($scanlines,$scan_data)=&Apache::grades::scantron_getfile();
+ my $classlist=&Apache::loncoursedata::get_classlist();
+ my %idmap=&Apache::grades::username_to_idmap($classlist);
+ my $navmap=Apache::lonnavmaps::navmap->new();
+ unless (ref($navmap)) {
+ $r->print(&navmap_errormsg());
+ return '';
+ }
+ my $map=$navmap->getResourceByUrl($sequence);
+ my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
+ my (%grader_partids_by_symb,%grader_randomlists_by_symb);
+ &graders_resources_pass(\@resources,\%grader_partids_by_symb, \%grader_randomlists_by_symb);
-=back
+ my ($uname,$udom);
+ my (%scandata,%lastname,%bylast);
+ $r->print('
+'."\n");
+
+ my @delayqueue;
+ my %completedstudents;
+
+ my $count=&Apache::grades::get_todo_count($scanlines,$scan_data);
+ my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count);
+ my ($username,$domain,$started);
+ my $nav_error;
+ &scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse.
+ if ($nav_error) {
+ $r->print(&navmap_errormsg());
+ return '';
+ }
+
+ &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,'Processing first student');
+ my $start=&Time::HiRes::time();
+ my $i=-1;
+
+ while ($i<$scanlines->{'count'}) {
+ ($username,$domain,$uname)=('','','');
+ $i++;
+ my $line=&Apache::grades::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=
+ &Apache::grades::scantron_parse_scanline($line,$i,\%scantron_config,
+ $scan_data);
+ unless ($uname=&Apache::grades::scantron_find_student($scan_record,$scan_data,
+ \%idmap,$i)) {
+ &Apache::grades::scantron_add_delay(\@delayqueue,$line,
+ 'Unable to find a student that matches',1);
+ next;
+ }
+ if (exists $completedstudents{$uname}) {
+ &Apache::grades::scantron_add_delay(\@delayqueue,$line,
+ 'Student '.$uname.' has multiple sheets',2);
+ next;
+ }
+ my $pid = $scan_record->{'scantron.ID'};
+ $lastname{$pid} = $scan_record->{'scantron.LastName'};
+ push(@{$bylast{$lastname{$pid}}},$pid);
+ my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
+ $scandata{$pid} = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
+ chomp($scandata{$pid});
+ $scandata{$pid} =~ s/\r$//;
+ ($username,$domain)=split(/:/,$uname);
+ my $counter = -1;
+ foreach my $resource (@resources) {
+ my $parts;
+ my $ressymb = $resource->symb();
+ if ((exists($grader_randomlists_by_symb{$ressymb})) ||
+ (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
+ (my $analysis,$parts) =
+ &scantron_partids_tograde($resource,$env{'request.course.id'},$username,$domain,undef,$bubbles_per_row);
+ } else {
+ $parts = $grader_partids_by_symb{$ressymb};
+ }
+ ($counter,my $recording) =
+ &verify_scantron_grading($resource,$domain,$username,$cid,$counter,
+ $scandata{$pid},$parts,
+ \%scantron_config,\%lettdig,$numletts);
+ $record{$pid} .= $recording;
+ }
+ }
+ &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
+ $r->print(' ');
+ my ($okstudents,$badstudents,$numstudents,$passed,$failed);
+ $passed = 0;
+ $failed = 0;
+ $numstudents = 0;
+ foreach my $last (sort(keys(%bylast))) {
+ if (ref($bylast{$last}) eq 'ARRAY') {
+ foreach my $pid (sort(@{$bylast{$last}})) {
+ my $showscandata = $scandata{$pid};
+ my $showrecord = $record{$pid};
+ $showscandata =~ s/\s/ /g;
+ $showrecord =~ s/\s/ /g;
+ if ($scandata{$pid} eq $record{$pid}) {
+ my $css_class = ($passed % 2)?'LC_odd_row':'LC_even_row';
+ $okstudents .= ''.
+''.&mt('Bubblesheet').' '.$showscandata.' '.$last.' '.$pid.' '."\n".
+' '."\n".
+''."\n".
+'Submissions '.$showrecord.' '."\n";
+ $passed ++;
+ } else {
+ my $css_class = ($failed % 2)?'LC_odd_row':'LC_even_row';
+ $badstudents .= ''.&mt('Bubblesheet').' '.$scandata{$pid}.' '.$last.' '.$pid.' '."\n".
+' '."\n".
+''."\n".
+'Submissions '.$record{$pid}.' '."\n".
+' '."\n";
+ $failed ++;
+ }
+ $numstudents ++;
+ }
+ }
+ }
+ $r->print(
+ ''
+ .&mt('Comparison of bubblesheet data (including corrections) with corresponding submission records (most recent submission) for [_1][quant,_2,student][_3] ([quant,_4,bubblesheet line] per student).',
+ '',
+ $numstudents,
+ ' ',
+ $env{'form.scantron_maxbubble'})
+ .'
'
+ );
+ $r->print(''.&mt('Exact matches for [quant,_1,student] .',$passed).' '.&mt('Discrepancies detected for [quant,_1,student] .',$failed).'
');
+ if ($passed) {
+ $r->print(&mt('Students with exact correspondence between bubblesheet data and submissions are as follows:').' ');
+ $r->print(&Apache::loncommon::start_data_table()."\n".
+ &Apache::loncommon::start_data_table_header_row()."\n".
+ ''.&mt('Source').' '.&mt('Bubble records').' '.&mt('Name').' '.&mt('ID').' '.
+ &Apache::loncommon::end_data_table_header_row()."\n".
+ $okstudents."\n".
+ &Apache::loncommon::end_data_table().' ');
+ }
+ if ($failed) {
+ $r->print(&mt('Students with differences between bubblesheet data and submissions are as follows:').' ');
+ $r->print(&Apache::loncommon::start_data_table()."\n".
+ &Apache::loncommon::start_data_table_header_row()."\n".
+ ''.&mt('Source').' '.&mt('Bubble records').' '.&mt('Name').' '.&mt('ID').' '.
+ &Apache::loncommon::end_data_table_header_row()."\n".
+ $badstudents."\n".
+ &Apache::loncommon::end_data_table()).' '.
+ &mt('Differences can occur if submissions were modified using manual grading after a bubblesheet grading pass.').' '.&mt('If unexpected discrepancies were detected, it is recommended that you inspect the original bubblesheets.');
+ }
+ $r->print(' ');
+ return;
+}
+
+sub verify_scantron_grading {
+ my ($resource,$domain,$username,$cid,$counter,$scandata,$partids,
+ $scantron_config,$lettdig,$numletts) = @_;
+ my ($record,%expected,%startpos);
+ return ($counter,$record) if (!ref($resource));
+ return ($counter,$record) if (!$resource->is_problem());
+ my $symb = $resource->symb();
+ return ($counter,$record) if (ref($partids) ne 'ARRAY');
+ foreach my $part_id (@{$partids}) {
+ $counter ++;
+ $expected{$part_id} = 0;
+ if ($env{"form.scantron.sub_bubblelines.$counter"}) {
+ my @sub_lines = split(/,/,$env{"form.scantron.sub_bubblelines.$counter"});
+ foreach my $item (@sub_lines) {
+ $expected{$part_id} += $item;
+ }
+ } else {
+ $expected{$part_id} = $env{"form.scantron.bubblelines.$counter"};
+ }
+ $startpos{$part_id} = $env{"form.scantron.first_bubble_line.$counter"};
+ }
+ if ($symb) {
+ my %recorded;
+ my (%returnhash) = &Apache::lonnet::restore($symb,$cid,$domain,$username);
+ if ($returnhash{'version'}) {
+ my %lasthash=();
+ my $version;
+ for ($version=1;$version<=$returnhash{'version'};$version++) {
+ foreach my $key (sort(split(/\:/,$returnhash{$version.':keys'}))) {
+ $lasthash{$key}=$returnhash{$version.':'.$key};
+ }
+ }
+ foreach my $key (keys(%lasthash)) {
+ if ($key =~ /\.scantron$/) {
+ my $value = &unescape($lasthash{$key});
+ my ($part_id) = ($key =~ /^resource\.(.+)\.scantron$/);
+ if ($value eq '') {
+ for (my $i=0; $i<$expected{$part_id}; $i++) {
+ for (my $j=0; $j<$scantron_config->{'length'}; $j++) {
+ $recorded{$part_id} .= $scantron_config->{'Qoff'};
+ }
+ }
+ } else {
+ my @tocheck;
+ my @items = split(//,$value);
+ if (($scantron_config->{'Qon'} eq 'letter') ||
+ ($scantron_config->{'Qon'} eq 'number')) {
+ if (@items < $expected{$part_id}) {
+ my $fragment = substr($scandata,$startpos{$part_id},$expected{$part_id});
+ my @singles = split(//,$fragment);
+ foreach my $pos (@singles) {
+ if ($pos eq ' ') {
+ push(@tocheck,$pos);
+ } else {
+ my $next = shift(@items);
+ push(@tocheck,$next);
+ }
+ }
+ } else {
+ @tocheck = @items;
+ }
+ foreach my $letter (@tocheck) {
+ if ($scantron_config->{'Qon'} eq 'letter') {
+ if ($letter !~ /^[A-J]$/) {
+ $letter = $scantron_config->{'Qoff'};
+ }
+ $recorded{$part_id} .= $letter;
+ } elsif ($scantron_config->{'Qon'} eq 'number') {
+ my $digit;
+ if ($letter !~ /^[A-J]$/) {
+ $digit = $scantron_config->{'Qoff'};
+ } else {
+ $digit = $lettdig->{$letter};
+ }
+ $recorded{$part_id} .= $digit;
+ }
+ }
+ } else {
+ @tocheck = @items;
+ for (my $i=0; $i<$expected{$part_id}; $i++) {
+ my $curr_sub = shift(@tocheck);
+ my $digit;
+ if ($curr_sub =~ /^[A-J]$/) {
+ $digit = $lettdig->{$curr_sub}-1;
+ }
+ if ($curr_sub eq 'J') {
+ $digit += scalar($numletts);
+ }
+ for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) {
+ if ($j == $digit) {
+ $recorded{$part_id} .= $scantron_config->{'Qon'};
+ } else {
+ $recorded{$part_id} .= $scantron_config->{'Qoff'};
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ foreach my $part_id (@{$partids}) {
+ if ($recorded{$part_id} eq '') {
+ for (my $i=0; $i<$expected{$part_id}; $i++) {
+ for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) {
+ $recorded{$part_id} .= $scantron_config->{'Qoff'};
+ }
+ }
+ }
+ $record .= $recorded{$part_id};
+ }
+ }
+ return ($counter,$record);
+}
+
+sub letter_to_digits {
+ my %lettdig = (
+ A => 1,
+ B => 2,
+ C => 3,
+ D => 4,
+ E => 5,
+ F => 6,
+ G => 7,
+ H => 8,
+ I => 9,
+ J => 0,
+ );
+ return %lettdig;
+}
-=cut
#-------- end of section for handling grading scantron forms -------
#
@@ -7173,309 +8736,248 @@ DOWNLOAD
#-------------------------- Menu interface -------------------------
#
-#--- Show a Grading Menu button - Calls the next routine ---
-sub show_grading_menu_form {
- my ($symb)=@_;
- my $result.=''."\n".
- ' '."\n".
- ' '."\n".
- ' '."\n".
- ' '."\n".
- ' '."\n";
- return $result;
-}
+#--- Href with symb and command ---
-# -- Retrieve choices for grading form
-sub savedState {
- my %savedState = ();
- if ($env{'form.saveState'}) {
- foreach (split(/:/,$env{'form.saveState'})) {
- my ($key,$value) = split(/=/,$_,2);
- $savedState{$key} = $value;
- }
- }
- return \%savedState;
+sub href_symb_cmd {
+ my ($symb,$cmd)=@_;
+ return '/adm/grades?symb='.&HTML::Entities::encode(&Apache::lonenc::check_encrypt($symb),'<>&"').'&command='.$cmd;
}
sub grading_menu {
- my ($request) = @_;
- my ($symb)=&get_symb($request);
+ my ($request,$symb) = @_;
if (!$symb) {return '';}
- my $probTitle = &Apache::lonnet::gettitle($symb);
- my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
- $request->print($table);
my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb),
- 'handgrade'=>$hdgrade,
- 'probTitle'=>$probTitle,
- 'command'=>'submit_options',
- 'saveState'=>"",
- 'gradingMenu'=>1,
- 'showgrading'=>"yes");
- my $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
- my @menu = ({ url => $url,
- name => &mt('Manual Grading/View Submissions'),
- short_description =>
- &mt('Start the process of hand grading submissions.'),
- });
+ 'command'=>'individual');
+
+ my $url1a = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
+ $fields{'command'}='ungraded';
+ my $url1b=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
+ $fields{'command'}='table';
+ my $url1c=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
+ $fields{'command'}='all_for_one';
+ my $url1d=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
+ $fields{'command'}='downloadfilesselect';
+ my $url1e=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
$fields{'command'} = 'csvform';
- $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
- push (@menu, { url => $url,
- name => &mt('Upload Scores'),
- short_description =>
- &mt('Specify a file containing the class scores for current resource.')});
+ my $url2 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
$fields{'command'} = 'processclicker';
- $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
- push (@menu, { url => $url,
- name => &mt('Process Clicker'),
- short_description =>
- &mt('Specify a file containing the clicker information for this resource.')});
+ my $url3 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
$fields{'command'} = 'scantron_selectphase';
- $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
- push (@menu, { url => $url,
- name => &mt('Grade/Manage Scantron Forms'),
- short_description =>
- &mt('')});
- $fields{'command'} = 'verify';
- $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
- push (@menu, { url => "",
- name => &mt('Verify Receipt'),
- short_description =>
- &mt('')});
- #
+ my $url4 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
+ $fields{'command'} = 'initialverifyreceipt';
+ my $url5 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+
+ my @menu = ({ categorytitle=>'Hand Grading',
+ items =>[
+ { linktext => 'Select individual students to grade',
+ url => $url1a,
+ permission => 'F',
+ icon => 'grade_students.png',
+ linktitle => 'Grade current resource for a selection of students.'
+ },
+ { linktext => 'Grade ungraded submissions.',
+ url => $url1b,
+ permission => 'F',
+ icon => 'ungrade_sub.png',
+ linktitle => 'Grade all submissions that have not been graded yet.'
+ },
+
+ { linktext => 'Grading table',
+ url => $url1c,
+ permission => 'F',
+ icon => 'grading_table.png',
+ linktitle => 'Grade current resource for all students.'
+ },
+ { linktext => 'Grade page/folder for one student',
+ url => $url1d,
+ permission => 'F',
+ icon => 'grade_PageFolder.png',
+ linktitle => 'Grade all resources in current page/sequence/folder for one student.'
+ },
+ { linktext => 'Download submissions',
+ url => $url1e,
+ permission => 'F',
+ icon => 'download_sub.png',
+ linktitle => 'Download all students submissions.'
+ }]},
+ { categorytitle=>'Automated Grading',
+ items =>[
+
+ { linktext => 'Upload Scores',
+ url => $url2,
+ permission => 'F',
+ icon => 'uploadscores.png',
+ linktitle => 'Specify a file containing the class scores for current resource.'
+ },
+ { linktext => 'Process Clicker',
+ url => $url3,
+ permission => 'F',
+ icon => 'addClickerInfoFile.png',
+ linktitle => 'Specify a file containing the clicker information for this resource.'
+ },
+ { linktext => 'Grade/Manage/Review Bubblesheets',
+ url => $url4,
+ permission => 'F',
+ icon => 'bubblesheet.png',
+ linktitle => 'Grade bubblesheet exams, upload/download bubblesheet data files, and review previously graded bubblesheet exams.'
+ },
+ { linktext => 'Verify Receipt Number',
+ url => $url5,
+ permission => 'F',
+ icon => 'receipt_number.png',
+ linktitle => 'Verify a system-generated receipt number for correct problem solution.'
+ }
+
+ ]
+ });
+
# Create the menu
my $Str;
- # $Str .= ''.&mt('Please select a grading task').' ';
$Str .= '';
$Str .= ' '.
- ' '."\n".
- ' '."\n".
- ' '."\n".
- ' '."\n".
- ' '."\n".
- ' '."\n";
-
- foreach my $menudata (@menu) {
- if ($menudata->{'name'} ne &mt('Verify Receipt')) {
- $Str .=' \n";
- } else {
- $Str .=' {'jscript'}.
- ' onClick="javascript:checkChoice(document.forms.gradingMenu,\'5\',\'verify\')" '.
- ' /> ';
- $Str .= (' 'x8).
- ' receipt: '.&Apache::lonnet::recprefix($env{'request.course.id'}).
- '- ';
- }
- $Str .= ' '.(' 'x8).$menudata->{'short_description'}.
- "\n";
- }
- $Str .="\n";
- $Str .=" \n";
- $request->print(<
- function checkChoice(formname,val,cmdx) {
- if (val <= 2) {
- var cmd = radioSelection(formname.radioChoice);
- var cmdsave = cmd;
- } else {
- cmd = cmdx;
- cmdsave = 'submission';
- }
- formname.command.value = cmd;
- if (val < 5) formname.submit();
- if (val == 5) {
- if (!checkReceiptNo(formname,'notOK')) {
- return false;
- } else {
- formname.submit();
- }
- }
- }
+ ' '."\n";
- function checkReceiptNo(formname,nospace) {
- var receiptNo = formname.receipt.value;
- var checkOpt = false;
- if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
- if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
- if (checkOpt) {
- alert("Please enter a receipt number given by a student in the receipt box.");
- formname.receipt.value = "";
- formname.receipt.focus();
- return false;
- }
- return true;
- }
-
-GRADINGMENUJS
- &commonJSfunctions($request);
+ $Str .= &Apache::lonhtmlcommon::generate_menu(@menu);
return $Str;
}
+sub ungraded {
+ my ($request)=@_;
+ &submit_options($request);
+}
+
+sub submit_options_sequence {
+ my ($request,$symb) = @_;
+ if (!$symb) {return '';}
+ &commonJSfunctions($request);
+ my $result;
+
+ $result.=''."\n".
+ ' '."\n";
+ $result.=&selectfield(0).
+ '
+
+
+
+
+ ';
+ return $result;
+}
+
+sub submit_options_table {
+ my ($request,$symb) = @_;
+ if (!$symb) {return '';}
+ &commonJSfunctions($request);
+ my $result;
+
+ $result.=''."\n".
+ ' '."\n";
+
+ $result.=&selectfield(0).
+ '
+
+
+
+
+ ';
+ return $result;
+}
+
+sub submit_options_download {
+ my ($request,$symb) = @_;
+ if (!$symb) {return '';}
+
+ &commonJSfunctions($request);
+
+ my $result=''."\n".
+ ' '."\n";
+ $result.='
+
+ '.&mt('Select Students for Which to Download Submissions').'
+ '.&selectfield(1).'
+
+
+
+
+
+
+ ';
+ return $result;
+}
+
#--- Displays the submissions first page -------
sub submit_options {
- my ($request) = @_;
- my ($symb)=&get_symb($request);
+ my ($request,$symb) = @_;
if (!$symb) {return '';}
- my $probTitle = &Apache::lonnet::gettitle($symb);
- $request->print(<
- function checkChoice(formname,val,cmdx) {
- if (val <= 2) {
- var cmd = radioSelection(formname.radioChoice);
- var cmdsave = cmd;
- } else {
- cmd = cmdx;
- cmdsave = 'submission';
- }
- formname.command.value = cmd;
- formname.saveState.value = "saveCmd="+cmdsave+":saveSec="+pullDownSelection(formname.section)+
- ":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) {
- var receiptNo = formname.receipt.value;
- var checkOpt = false;
- if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
- if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
- if (checkOpt) {
- alert("Please enter a receipt number given by a student in the receipt box.");
- formname.receipt.value = "";
- formname.receipt.focus();
- return false;
- }
- return true;
- }
-
-GRADINGMENUJS
&commonJSfunctions($request);
- my $result=' Manual Grading/View Submission ';
- my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
- $result.=$table;
- my (undef,$sections) = &getclasslist('all','0');
- my $savedState = &savedState();
- my $saveCmd = ($$savedState{'saveCmd'} eq '' ? 'submission' : $$savedState{'saveCmd'});
- my $saveSec = ($$savedState{'saveSec'} eq '' ? 'all' : $$savedState{'saveSec'});
- my $saveSub = ($$savedState{'saveSub'} eq '' ? 'all' : $$savedState{'saveSub'});
- my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'});
+ my $result;
$result.=''."\n".
- ' '."\n".
- ' '."\n".
- ' '."\n".
- ' '."\n".
- ' '."\n".
- ' '."\n".
- ' '."\n";
-
- $result.=''."\n".
- ''."\n".
- ' Select a Grading/Viewing Option '."\n".
- ''."\n";
-
- $result.=''."\n";
-
- $result.=' '; #';
-
-# $result.=''."\n".' ';
- $result.= '
'."\n".
- '
'."\n";
+ ' '."\n";
+ $result.=&selectfield(1).'
+
+
+
+
+
+
+ ';
+ return $result;
+}
+
+sub selectfield {
+ my ($full)=@_;
+ my %options =
+ (&Apache::lonlocal::texthash(
+ 'yes' => 'with submissions',
+ 'queued' => 'in grading queue',
+ 'graded' => 'with ungraded submissions',
+ 'incorrect' => 'with incorrect submissions',
+ 'all' => 'with any status'),
+ 'select_form_order' => ['yes','queued','graded','incorrect','all']);
+ my $result='
+
+
+
+ '.&mt('Sections').'
+
+ '.&Apache::lonstatistics::SectionSelect('section','multiple',5).'
+
+
+
+
+ '.&mt('Groups').'
+
+ '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'
+
+
+
+
+ '.&mt('Access Status').'
+
+ '.&Apache::lonhtmlcommon::StatusOptions(undef,undef,5,undef,'mult').'
+ ';
+ if ($full) {
+ $result.='
+
+
+ '.&mt('Submission Status').'
+ '.
+ &Apache::loncommon::select_form('all','submitonly',\%options).
+ ' ';
+ }
+ $result.=' ';
return $result;
}
@@ -7563,18 +9065,14 @@ sub clicker_grading_parameters {
}
sub process_clicker {
- my ($r)=@_;
- my ($symb)=&get_symb($r);
+ my ($r,$symb)=@_;
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";
- $result.=' '.&mt('Specify a file containing the clicker information for this resource').
- '. '."\n";
- $result.=''."\n";
+ $result.=&Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_header_row().
+ ' '.&mt('Specify a file containing clicker information and set grading options.').' '.
+ &Apache::loncommon::end_data_table_header_row().
+ &Apache::loncommon::start_data_table_row()."\n";
# Attempt to restore parameters from last session, set defaults if not present
my %Saveable_Parameters=&clicker_grading_parameters();
&Apache::loncommon::restore_course_settings('grades_clicker',
@@ -7585,25 +9083,27 @@ sub process_clicker {
if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; }
my %checked;
- foreach my $gradingmechanism ('attendance','personnel','specific') {
+ foreach my $gradingmechanism ('attendance','personnel','specific','given') {
if ($env{'form.gradingmechanism'} eq $gradingmechanism) {
- $checked{$gradingmechanism}="checked='checked'";
+ $checked{$gradingmechanism}=' checked="checked"';
}
}
- my $upload=&mt("Upload File");
+ my $upload=&mt("Evaluate File");
my $type=&mt("Type");
my $attendance=&mt("Award points just for participation");
my $personnel=&mt("Correctness determined from response by course personnel");
my $specific=&mt("Correctness determined from response with clicker ID(s)");
+ my $given=&mt("Correctness determined from given list of answers").' '.
+ '('.&mt("Provide comma-separated list. Use '*' for any answer correct, '-' for skip").') ';
my $pcorrect=&mt("Percentage points for correct solution");
my $pincorrect=&mt("Percentage points for incorrect solution");
my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',
- ('iclicker' => 'i>clicker',
- 'interwrite' => 'interwrite PRS'));
+ {'iclicker' => 'i>clicker',
+ 'interwrite' => 'interwrite PRS',
+ 'turning' => 'Turning Technologies'});
$symb = &Apache::lonenc::check_encrypt($symb);
- $result.=<
+ $result.= &Apache::lonhtmlcommon::scripttag(<
+ENDUPFORM
+ $result.= <
-
-
$type: $selectform
- $attendance
- $personnel
- $specific
+ENDUPFORM
+ $result.=' '.&Apache::loncommon::end_data_table_row().
+ &Apache::loncommon::start_data_table_row().''.(< $attendance
+ $personnel
+ $specific
+ $given
+
+
-$pcorrect:
-$pincorrect:
-
-
-ENDUPFORM
- $result.='
'."\n".
- '
'."\n";
- $result.=&show_grading_menu_form($symb);
+ENDGRADINGFORM
+ $result.=''.&Apache::loncommon::end_data_table_row().
+ &Apache::loncommon::start_data_table_row().''.(<$pcorrect:
+$pincorrect:
+
+'
+ENDPERCFORM
+ $result.=' '.
+ &Apache::loncommon::end_data_table_row().
+ &Apache::loncommon::end_data_table();
return $result;
}
sub process_clicker_file {
- my ($r)=@_;
- my ($symb)=&get_symb($r);
+ my ($r,$symb)=@_;
if (!$symb) {return '';}
my %Saveable_Parameters=&clicker_grading_parameters();
&Apache::loncommon::store_course_settings('grades_clicker',
\%Saveable_Parameters);
-
- my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
+ my $result='';
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);
+ return $result;
+ }
+ if (($env{'form.gradingmechanism'} eq 'given') && ($env{'form.givenanswer'}!~/\S/)) {
+ $result.=''.&mt('You need to specify the correct answer').' ';
+ return $result;
+ }
+ my $foundgiven=0;
+ if ($env{'form.gradingmechanism'} eq 'given') {
+ $env{'form.givenanswer'}=~s/^\s*//gs;
+ $env{'form.givenanswer'}=~s/\s*$//gs;
+ $env{'form.givenanswer'}=~s/[^a-zA-Z0-9\.\*\-\+]+/\,/g;
+ $env{'form.givenanswer'}=uc($env{'form.givenanswer'});
+ my @answers=split(/\,/,$env{'form.givenanswer'});
+ $foundgiven=$#answers+1;
}
my %clicker_ids=&gather_clicker_ids();
my %correct_ids;
@@ -7697,6 +9216,8 @@ sub process_clicker_file {
}
if ($env{'form.gradingmechanism'} eq 'attendance') {
$result.=&mt('Score based on attendance only');
+ } elsif ($env{'form.gradingmechanism'} eq 'given') {
+ $result.=&mt('Score based on [_1] ([_2] answers)',''.$env{'form.givenanswer'}.' ',$foundgiven);
} else {
my $number=0;
$result.=''.&mt('Correctness determined by the following IDs').' ';
@@ -7713,7 +9234,7 @@ sub process_clicker_file {
$result.="
\n";
if ($number==0) {
$result.=''.&mt('No IDs found to determine correct answer').' ';
- return $result.&show_grading_menu_form($symb);
+ return $result;
}
}
if (length($env{'form.upfile'}) < 2) {
@@ -7721,27 +9242,29 @@ sub process_clicker_file {
'',
' ',
''.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').' ');
- return $result.&show_grading_menu_form($symb);
+ return $result;
}
# Were able to get all the info needed, now analyze the file
$result.=&Apache::loncommon::studentbrowser_javascript();
$symb = &Apache::lonenc::check_encrypt($symb);
- my $heading=&mt('Scanning clicker file');
- $result.=(<
-
-$heading
+ $result.=&Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_header_row().
+ ' '.&mt('Evaluate clicker file').' '.
+ &Apache::loncommon::end_data_table_header_row().
+ &Apache::loncommon::start_data_table_row().(<
-
-
ENDHEADER
+ if ($env{'form.gradingmechanism'} eq 'given') {
+ $result.=' ';
+ }
my %responses;
my @questiontitles;
my $errormsg='';
@@ -7752,13 +9275,18 @@ ENDHEADER
if ($env{'form.upfiletype'} eq 'interwrite') {
($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses);
}
+ if ($env{'form.upfiletype'} eq 'turning') {
+ ($errormsg,$number)=&turning_eval(\@questiontitles,\%responses);
+ }
$result.=' '.&mt('Found [_1] question(s)',$number).' '.
' '.
- &mt('Awarding [_1] percent for corrion(s)',$number).' '.
- ' '.
&mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',
$env{'form.pcorrect'},$env{'form.pincorrect'}).
' ';
+ if (($env{'form.gradingmechanism'} eq 'given') && ($number!=$foundgiven)) {
+ $result.=''.&mt('Number of given answers does not agree with number of questions in file.').' ';
+ return $result;
+ }
# Remember Question Titles
# FIXME: Possibly need delimiter other than ":"
for (my $i=0;$i<$number;$i++) {
@@ -7777,7 +9305,9 @@ ENDHEADER
} elsif ($clicker_ids{$id}) {
if ($clicker_ids{$id}=~/\,/) {
# More than one user with the same clicker!
- $result.="\n ".&mt('Clicker registered more than once').": ".$id." ";
+ $result.="".&Apache::loncommon::end_data_table_row().
+ &Apache::loncommon::start_data_table_row()."".
+ &mt('Clicker registered more than once').": ".$id." ";
$result.="\n".' '.
"";
foreach my $reguser (sort(split(/\,/,$clicker_ids{$id}))) {
@@ -7791,18 +9321,20 @@ ENDHEADER
$student_count++;
}
} else {
- $result.="\n ".&mt('Unregistered Clicker')." ".$id." ";
+ $result.=" ".&Apache::loncommon::end_data_table_row().
+ &Apache::loncommon::start_data_table_row()."".
+ &mt('Unregistered Clicker')." ".$id." ";
$result.="\n".' '.
"\n".&mt("Username").": ".
"\n".&mt("Domain").": ".
&Apache::loncommon::select_dom_form($env{'course.'.$env{'request.course.id'}.'.domain'},'udom'.$id).' '.
- &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id);
+ &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id,0,$id);
$unknown_count++;
}
}
$result.=' '.
&mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);
- if ($env{'form.gradingmechanism'} ne 'attendance') {
+ if (($env{'form.gradingmechanism'} ne 'attendance') && ($env{'form.gradingmechanism'} ne 'given')) {
if ($correct_count==0) {
$errormsg.="Found no correct answers answers for grading!";
} elsif ($correct_count>1) {
@@ -7817,9 +9349,10 @@ ENDHEADER
} else {
$result.=' ';
}
- $result.='
'."\n".
- '
'."\n";
- return $result.&show_grading_menu_form($symb);
+ $result.=''.
+ &Apache::loncommon::end_data_table_row().
+ &Apache::loncommon::end_data_table();
+ return $result;
}
sub iclicker_eval {
@@ -7841,6 +9374,7 @@ sub iclicker_eval {
$id=~s/^[\#0]+//;
for (my $i=0;$i<$number;$i++) {
my $idx=3+$i*6;
+ $entries[$idx]=~s/[^a-zA-Z0-9\.\*\-\+]+//g;
push(@idresponses,$entries[$idx]);
}
$$responses{$id}=join(',',@idresponses);
@@ -7873,30 +9407,56 @@ sub interwrite_eval {
$id=~s/[\-\:]//g;
$idresponses{$id}[$number]=$entries[6];
}
- foreach my $id (keys %idresponses) {
+ foreach my $id (keys(%idresponses)) {
$$responses{$id}=join(',',@{$idresponses{$id}});
$$responses{$id}=~s/^\s*\,//;
}
return ($errormsg,$number);
}
+sub turning_eval {
+ my ($questiontitles,$responses)=@_;
+ my $number=0;
+ my $errormsg='';
+ foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
+ my %components=&Apache::loncommon::record_sep($line);
+ my @entries=map {$components{$_}} (sort(keys(%components)));
+ if ($#entries>$number) { $number=$#entries; }
+ my $id=$entries[0];
+ my @idresponses;
+ $id=~s/^[\#0]+//;
+ unless ($id) { next; }
+ for (my $idx=1;$idx<=$#entries;$idx++) {
+ $entries[$idx]=~s/\,/\;/g;
+ $entries[$idx]=~s/[^a-zA-Z0-9\.\*\-\+\;]+//g;
+ push(@idresponses,$entries[$idx]);
+ }
+ $$responses{$id}=join(',',@idresponses);
+ }
+ for (my $i=1; $i<=$number; $i++) {
+ $$questiontitles[$i]=&mt('Question [_1]',$i);
+ }
+ return ($errormsg,$number);
+}
+
+
sub assign_clicker_grades {
- my ($r)=@_;
- my ($symb)=&get_symb($r);
+ my ($r,$symb)=@_;
if (!$symb) {return '';}
# See which part we are saving to
- my ($partlist,$handgrade,$responseType) = &response_type($symb);
+ my $res_error;
+ my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
+ if ($res_error) {
+ return &navmap_errormsg();
+ }
# FIXME: This should probably look for the first handgradeable part
my $part=$$partlist[0];
# Start screen output
- my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
-
- my $heading=&mt('Assigning grades based on clicker file');
- $result.=(<
-
-$heading
-ENDHEADER
+ my $result=&Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_header_row().
+ ' '.&mt('Assigning grades based on clicker file').' '.
+ &Apache::loncommon::end_data_table_header_row().
+ &Apache::loncommon::start_data_table_row().'';
# Get correct result
# FIXME: Possibly need delimiter other than ":"
my @correct=();
@@ -7912,25 +9472,26 @@ ENDHEADER
$result.=''.
&mt('More than one correct result given for question "[_1]": [_2] versus [_3].',
$env{'form.question:'.$i},$correct[$i],$input[$i]).' ';
- } elsif ($input[$i]) {
+ } elsif (($input[$i]) || ($input[$i] eq '0')) {
$correct[$i]=$input[$i];
}
}
}
}
for (my $i=0;$i<$number;$i++) {
- if (!$correct[$i]) {
+ if ((!$correct[$i]) && ($correct[$i] ne '0')) {
$result.=''.
&mt('No correct result given for question "[_1]"!',
$env{'form.question:'.$i}).' ';
}
}
- $result.=' '.&mt("Correct answer: [_1]",join(', ',map { ($_?$_:'-') } @correct));
+ $result.=' '.&mt("Correct answer: [_1]",join(', ',map { ((($_) || ($_ eq '0'))?$_:'-') } @correct));
}
# Start grading
my $pcorrect=$env{'form.pcorrect'};
my $pincorrect=$env{'form.pincorrect'};
my $storecount=0;
+ my %users=();
foreach my $key (keys(%env)) {
my $user='';
if ($key=~/^form\.student\:(.*)$/) {
@@ -7944,23 +9505,46 @@ ENDHEADER
$user=$env{'form.multi'.$id};
}
}
- if ($user) {
+ if ($user) {
+ if ($users{$user}) {
+ $result.=''.
+ &mt("More than one entry found for [_1] !",$user).
+ ' ';
+ }
+ $users{$user}=1;
my @answer=split(/\,/,$env{$key});
my $sum=0;
+ my $realnumber=$number;
for (my $i=0;$i<$number;$i++) {
- if ($answer[$i]) {
+ if ($correct[$i] eq '-') {
+ $realnumber--;
+ } elsif (($answer[$i]) || ($answer[$i]=~/^[0\.]+$/)) {
if ($gradingmechanism eq 'attendance') {
$sum+=$pcorrect;
+ } elsif ($correct[$i] eq '*') {
+ $sum+=$pcorrect;
} else {
- if ($answer[$i] eq $correct[$i]) {
- $sum+=$pcorrect;
- } else {
- $sum+=$pincorrect;
+# We actually grade if correct or not
+ my $increment=$pincorrect;
+# Special case: numerical answer "0"
+ if ($correct[$i] eq '0') {
+ if ($answer[$i]=~/^[0\.]+$/) {
+ $increment=$pcorrect;
+ }
+# General numerical answer, both evaluate to something non-zero
+ } elsif ((1.0*$correct[$i]!=0) && (1.0*$answer[$i]!=0)) {
+ if (1.0*$correct[$i]==1.0*$answer[$i]) {
+ $increment=$pcorrect;
+ }
+# Must be just alphanumeric
+ } elsif ($answer[$i] eq $correct[$i]) {
+ $increment=$pcorrect;
}
+ $sum+=$increment;
}
}
}
- my $ave=$sum/(100*$number);
+ my $ave=$sum/(100*$realnumber);
# Store
my ($username,$domain)=split(/\:/,$user);
my %grades=();
@@ -7978,136 +9562,507 @@ ENDHEADER
}
}
# We are done
- $result.=' '.&mt('Successfully stored grades for [_1] student(s).',$storecount).
- '
'."\n".
- '
'."\n";
- return $result.&show_grading_menu_form($symb);
+ $result.=' '.&mt('Successfully stored grades for [quant,_1,student].',$storecount).
+ ''.
+ &Apache::loncommon::end_data_table_row().
+ &Apache::loncommon::end_data_table();
+ return $result;
+}
+
+sub navmap_errormsg {
+ return ''.
+ &mt('An error occurred retrieving information about resources in the course.').'
'.
+ &mt('It is recommended that you [_1]re-initialize the course[_2] and then return to this grading page.','
',' ').
+ '
';
+}
+
+sub startpage {
+ my ($r,$symb,$crumbs,$onlyfolderflag,$nodisplayflag,$stuvcurrent,$stuvdisp,$nomenu,$js) = @_;
+ if ($nomenu) {
+ $r->print(&Apache::loncommon::start_page("Student's Version",$js,{'only_body' => '1'}));
+ } else {
+ unshift(@$crumbs,{href=>&href_symb_cmd($symb,'gradingmenu'),text=>"Grading"});
+ $r->print(&Apache::loncommon::start_page('Grading',$js,
+ {'bread_crumbs' => $crumbs}));
+ &Apache::lonquickgrades::startGradeScreen($r,($env{'form.symb'}?'probgrading':'grading'));
+ }
+ unless ($nodisplayflag) {
+ $r->print(&Apache::lonhtmlcommon::resource_info_box($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp));
+ }
+}
+
+sub select_problem {
+ my ($r)=@_;
+ $r->print(''.&mt('Select the problem or one of the problems you want to grade').' ');
+ $r->print(&Apache::lonstathelpers::problem_selector('.',undef,1));
+ $r->print(' ');
+ $r->print(' ');
}
sub handler {
my $request=$_[0];
&reset_caches();
- if ($env{'browser.mathml'}) {
- &Apache::loncommon::content_type($request,'text/xml');
- } else {
- &Apache::loncommon::content_type($request,'text/html');
+ if ($request->header_only) {
+ &Apache::loncommon::content_type($request,'text/html');
+ $request->send_http_header;
+ return OK;
}
- $request->send_http_header;
- return '' if $request->header_only;
&Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});
- my $symb=&get_symb($request,1);
+
+# see what command we need to execute
+
my @commands=&Apache::loncommon::get_env_multiple('form.command');
my $command=$commands[0];
+ &init_perm();
+ if (!$env{'request.course.id'}) {
+ unless ((&Apache::lonnet::allowed('usc',$env{'request.role.domain'})) &&
+ ($command =~ /^scantronupload/)) {
+ # Not in a course.
+ $env{'user.error.msg'}="/adm/grades::vgr:0:0:Cannot display grades page outside course context";
+ return HTTP_NOT_ACCEPTABLE;
+ }
+ } elsif (!%perm) {
+ $request->internal_redirect('/adm/quickgrades');
+ }
+ &Apache::loncommon::content_type($request,'text/html');
+ $request->send_http_header;
+
if ($#commands > 0) {
&Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
}
+# see what the symb is
- $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)=&Apache::lonnet::decode_symb($tsymb);
- if (&Apache::lonnet::allowed('mgr',$tcrsid)) {
- $request->print(&Apache::lonnet::ssi_body('/res/'.$url,
- ('grade_username' => $tuname,
- 'grade_domain' => $tudom,
- 'grade_courseid' => $tcrsid,
- 'grade_symb' => $tsymb)));
- } else {
- $request->print('Not authorized: '.$token.' ');
- }
- } else {
- $request->print('Not a valid DocID: '.$token.' ');
- }
- } else {
- $request->print(&Apache::lonxml::tokeninputfield());
- }
- }
+ my $symb=$env{'form.symb'};
+ unless ($symb) {
+ (my $url=$env{'form.url'}) =~ s-^https*://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
+ $symb=&Apache::lonnet::symbread($url);
+ }
+ &Apache::lonenc::check_decrypt(\$symb);
+
+ $ssi_error = 0;
+ if (($symb eq '' || $command eq '') && ($env{'request.course.id'})) {
+#
+# Not called from a resource, but inside a course
+#
+ &startpage($request,undef,[],1,1);
+ &select_problem($request);
} else {
- &init_perm();
if ($command eq 'submission' && $perm{'vgr'}) {
- ($env{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0));
+ my ($stuvcurrent,$stuvdisp,$versionform,$js);
+ if (($env{'form.student'} ne '') && ($env{'form.userdom'} ne '')) {
+ ($stuvcurrent,$stuvdisp,$versionform,$js) =
+ &choose_task_version_form($symb,$env{'form.student'},
+ $env{'form.userdom'});
+ }
+ &startpage($request,$symb,[{href=>"", text=>"Student Submissions"}],undef,undef,$stuvcurrent,$stuvdisp,undef,$js);
+ if ($versionform) {
+ $request->print($versionform);
+ }
+ $request->print(' ');
+ ($env{'form.student'} eq '' ? &listStudents($request,$symb) : &submission($request,0,0,$symb));
+ } elsif ($command eq 'versionsub' && $perm{'vgr'}) {
+ my ($stuvcurrent,$stuvdisp,$versionform,$js) =
+ &choose_task_version_form($symb,$env{'form.student'},
+ $env{'form.userdom'},
+ $env{'form.inhibitmenu'});
+ &startpage($request,$symb,[{href=>"", text=>"Previous Student Version"}],undef,undef,$stuvcurrent,$stuvdisp,$env{'form.inhibitmenu'},$js);
+ if ($versionform) {
+ $request->print($versionform);
+ }
+ $request->print(' ');
+ $request->print(&show_previous_task_version($request,$symb));
} elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) {
- &pickStudentPage($request);
+ &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
+ {href=>'',text=>'Select student'}],1,1);
+ &pickStudentPage($request,$symb);
} elsif ($command eq 'displayPage' && $perm{'vgr'}) {
- &displayPage($request);
+ &startpage($request,$symb,
+ [{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
+ {href=>'',text=>'Select student'},
+ {href=>'',text=>'Grade student'}],1,1);
+ &displayPage($request,$symb);
} elsif ($command eq 'gradeByPage' && $perm{'mgr'}) {
- &updateGradeByPage($request);
+ &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
+ {href=>'',text=>'Select student'},
+ {href=>'',text=>'Grade student'},
+ {href=>'',text=>'Store grades'}],1,1);
+ &updateGradeByPage($request,$symb);
} elsif ($command eq 'processGroup' && $perm{'vgr'}) {
- &processGroup($request);
+ &startpage($request,$symb,[{href=>'',text=>'...'},
+ {href=>'',text=>'Modify grades'}]);
+ &processGroup($request,$symb);
} elsif ($command eq 'gradingmenu' && $perm{'vgr'}) {
- $request->print(&grading_menu($request));
- } elsif ($command eq 'submit_options' && $perm{'vgr'}) {
- $request->print(&submit_options($request));
+ &startpage($request,$symb);
+ $request->print(&grading_menu($request,$symb));
+ } elsif ($command eq 'individual' && $perm{'vgr'}) {
+ &startpage($request,$symb,[{href=>'',text=>'Select individual students to grade'}]);
+ $request->print(&submit_options($request,$symb));
+ } elsif ($command eq 'ungraded' && $perm{'vgr'}) {
+ &startpage($request,$symb,[{href=>'',text=>'Grade ungraded submissions'}]);
+ $request->print(&listStudents($request,$symb,'graded'));
+ } elsif ($command eq 'table' && $perm{'vgr'}) {
+ &startpage($request,$symb,[{href=>"", text=>"Grading table"}]);
+ $request->print(&submit_options_table($request,$symb));
+ } elsif ($command eq 'all_for_one' && $perm{'vgr'}) {
+ &startpage($request,$symb,[{href=>'',text=>'Grade page/folder for one student'}],1,1);
+ $request->print(&submit_options_sequence($request,$symb));
} elsif ($command eq 'viewgrades' && $perm{'vgr'}) {
- $request->print(&viewgrades($request));
+ &startpage($request,$symb,[{href=>&href_symb_cmd($symb,"table"), text=>"Grading table"},{href=>'', text=>"Modify grades"}]);
+ $request->print(&viewgrades($request,$symb));
} elsif ($command eq 'handgrade' && $perm{'mgr'}) {
- $request->print(&processHandGrade($request));
+ &startpage($request,$symb,[{href=>'',text=>'...'},
+ {href=>'',text=>'Store grades'}]);
+ $request->print(&processHandGrade($request,$symb));
} elsif ($command eq 'editgrades' && $perm{'mgr'}) {
- $request->print(&editgrades($request));
+ &startpage($request,$symb,[{href=>&href_symb_cmd($symb,"table"), text=>"Grading table"},
+ {href=>&href_symb_cmd($symb,'viewgrades').'&group=all§ion=all&Status=Active',
+ text=>"Modify grades"},
+ {href=>'', text=>"Store grades"}]);
+ $request->print(&editgrades($request,$symb));
+ } elsif ($command eq 'initialverifyreceipt' && $perm{'vgr'}) {
+ &startpage($request,$symb,[{href=>'',text=>'Verify Receipt Number'}]);
+ $request->print(&initialverifyreceipt($request,$symb));
} elsif ($command eq 'verify' && $perm{'vgr'}) {
- $request->print(&verifyreceipt($request));
+ &startpage($request,$symb,[{href=>&href_symb_cmd($symb,"initialverifyreceipt"),text=>'Verify Receipt Number'},
+ {href=>'',text=>'Verification Result'}]);
+ $request->print(&verifyreceipt($request,$symb));
} elsif ($command eq 'processclicker' && $perm{'mgr'}) {
- $request->print(&process_clicker($request));
+ &startpage($request,$symb,[{href=>'', text=>'Process clicker'}]);
+ $request->print(&process_clicker($request,$symb));
} elsif ($command eq 'processclickerfile' && $perm{'mgr'}) {
- $request->print(&process_clicker_file($request));
+ &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'processclicker'), text=>'Process clicker'},
+ {href=>'', text=>'Process clicker file'}]);
+ $request->print(&process_clicker_file($request,$symb));
} elsif ($command eq 'assignclickergrades' && $perm{'mgr'}) {
- $request->print(&assign_clicker_grades($request));
+ &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'processclicker'), text=>'Process clicker'},
+ {href=>'', text=>'Process clicker file'},
+ {href=>'', text=>'Store grades'}]);
+ $request->print(&assign_clicker_grades($request,$symb));
} elsif ($command eq 'csvform' && $perm{'mgr'}) {
- $request->print(&upcsvScores_form($request));
+ &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
+ $request->print(&upcsvScores_form($request,$symb));
} elsif ($command eq 'csvupload' && $perm{'mgr'}) {
- $request->print(&csvupload($request));
+ &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
+ $request->print(&csvupload($request,$symb));
} elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) {
- $request->print(&csvuploadmap($request));
+ &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
+ $request->print(&csvuploadmap($request,$symb));
} elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) {
if ($env{'form.associate'} ne 'Reverse Association') {
- $request->print(&csvuploadoptions($request));
+ &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
+ $request->print(&csvuploadoptions($request,$symb));
} else {
if ( $env{'form.upfile_associate'} ne 'reverse' ) {
$env{'form.upfile_associate'} = 'reverse';
} else {
$env{'form.upfile_associate'} = 'forward';
}
- $request->print(&csvuploadmap($request));
+ &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
+ $request->print(&csvuploadmap($request,$symb));
}
} elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {
- $request->print(&csvuploadassign($request));
+ &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
+ $request->print(&csvuploadassign($request,$symb));
} elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
- $request->print(&scantron_selectphase($request));
+ &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+ $request->print(&scantron_selectphase($request,undef,$symb));
} elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {
- $request->print(&scantron_do_warning($request));
+ &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+ $request->print(&scantron_do_warning($request,$symb));
} elsif ($command eq 'scantron_validate' && $perm{'mgr'}) {
- $request->print(&scantron_validate_file($request));
+ &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+ $request->print(&scantron_validate_file($request,$symb));
} elsif ($command eq 'scantron_process' && $perm{'mgr'}) {
- $request->print(&scantron_process_students($request));
+ &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+ $request->print(&scantron_process_students($request,$symb));
} 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));
+ &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+ $request->print(&scantron_upload_scantron_data($request,$symb));
} 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));
+ &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+ $request->print(&scantron_upload_scantron_data_save($request,$symb));
} elsif ($command eq 'scantron_download' &&
&Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
- $request->print(&scantron_download_scantron_data($request));
+ &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+ $request->print(&scantron_download_scantron_data($request,$symb));
+ } elsif ($command eq 'checksubmissions' && $perm{'vgr'}) {
+ &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
+ $request->print(&checkscantron_results($request,$symb));
+ } elsif ($command eq 'downloadfilesselect' && $perm{'vgr'}) {
+ &startpage($request,$symb,[{href=>'', text=>'Select which submissions to download'}]);
+ $request->print(&submit_options_download($request,$symb));
+ } elsif ($command eq 'downloadfileslink' && $perm{'vgr'}) {
+ &startpage($request,$symb,
+ [{href=>&href_symb_cmd($symb,'downloadfilesselect'), text=>'Select which submissions to download'},
+ {href=>'', text=>'Download submissions'}]);
+ &submit_download_link($request,$symb);
} elsif ($command) {
- $request->print("Access Denied ($command)");
+ &startpage($request,$symb,[{href=>'', text=>'Access denied'}]);
+ $request->print(''.&mt('Access Denied ([_1])',$command).'
');
}
}
- $request->print(&Apache::loncommon::end_page());
+ if ($ssi_error) {
+ &ssi_print_error($request);
+ }
+ if ($env{'form.inhibitmenu'}) {
+ $request->print(&Apache::loncommon::end_page());
+ } else {
+ &Apache::lonquickgrades::endGradeScreen($request);
+ }
&reset_caches();
- return '';
+ return OK;
}
1;
__END__;
+
+
+=head1 NAME
+
+Apache::grades
+
+=head1 SYNOPSIS
+
+Handles the viewing of grades.
+
+This is part of the LearningOnline Network with CAPA project
+described at http://www.lon-capa.org.
+
+=head1 OVERVIEW
+
+Do an ssi with retries:
+While I'd love to factor out this with the vesrion in lonprintout,
+that would either require a data coupling between modules, which I refuse to perpetuate (there's quite enough of that already), or would require the invention of another infrastructure
+I'm not quite ready to invent (e.g. an ssi_with_retry object).
+
+At least the logic that drives this has been pulled out into loncommon.
+
+
+
+ssi_with_retries - Does the server side include of a resource.
+ if the ssi call returns an error we'll retry it up to
+ the number of times requested by the caller.
+ If we still have a proble, no text is appended to the
+ output and we set some global variables.
+ to indicate to the caller an SSI error occurred.
+ All of this is supposed to deal with the issues described
+ in LonCAPA BZ 5631 see:
+ http://bugs.lon-capa.org/show_bug.cgi?id=5631
+ by informing the user that this happened.
+
+Parameters:
+ resource - The resource to include. This is passed directly, without
+ interpretation to lonnet::ssi.
+ form - The form hash parameters that guide the interpretation of the resource
+
+ retries - Number of retries allowed before giving up completely.
+Returns:
+ On success, returns the rendered resource identified by the resource parameter.
+Side Effects:
+ The following global variables can be set:
+ ssi_error - If an unrecoverable error occurred this becomes true.
+ It is up to the caller to initialize this to false
+ if desired.
+ ssi_error_resource - If an unrecoverable error occurred, this is the value
+ of the resource that could not be rendered by the ssi
+ call.
+ ssi_error_message - The error string fetched from the ssi response
+ in the event of an error.
+
+
+=head1 HANDLER SUBROUTINE
+
+ssi_with_retries()
+
+=head1 SUBROUTINES
+
+=over
+
+=head1 Routines to display previous version of a Task for a specific student
+
+Tasks are graded pass/fail. Students who have yet to pass a particular Task
+can receive another opportunity. Access to tasks is slot-based. If a slot
+requires a proctor to check-in the student, a new version of the Task will
+be created when the student is checked in to the new opportunity.
+
+If a particular student has tried two or more versions of a particular task,
+the submission screen provides a user with vgr privileges (e.g., a Course
+Coordinator) the ability to display a previous version worked on by the
+student. By default, the current version is displayed. If a previous version
+has been selected for display, submission data are only shown that pertain
+to that particular version, and the interface to submit grades is not shown.
+
+=over 4
+
+=item show_previous_task_version()
+
+Displays a specified version of a student's Task, as the student sees it.
+
+Inputs: 2
+ request - request object
+ symb - unique symb for current instance of resource
+
+Output: None.
+
+Side Effects: calls &show_problem() to print version of Task, with
+ version contained in form item: $env{'form.previousversion'}
+
+=item choose_task_version_form()
+
+Displays a web form used to select which version of a student's view of a
+Task should be displayed. Either launches a pop-up window, or replaces
+content in existing pop-up, or replaces page in main window.
+
+Inputs: 4
+ symb - unique symb for current instance of resource
+ uname - username of student
+ udom - domain of student
+ nomenu - 1 if display is in a pop-up window, and hence no menu
+ breadcrumbs etc., are displayed
+
+Output: 4
+ current - student's current version
+ displayed - student's version being displayed
+ result - scalar containing HTML for web form used to switch to
+ a different version (or a link to close window, if pop-up).
+ js - javascript for processing selection in versions web form
+
+Side Effects: None.
+
+=item previous_display_javascript()
+
+Inputs: 2
+ nomenu - 1 if display is in a pop-up window, and hence no menu
+ breadcrumbs etc., are displayed.
+ current - student's current version number.
+
+Output: 1
+ js - javascript for processing selection in versions web form.
+
+Side Effects: None.
+
+=back
+
+=head1 Routines to process bubblesheet data.
+
+=over 4
+
+=item scantron_get_correction() :
+
+ Builds the interface screen to interact with the operator to fix a
+ specific error condition in a specific scanline
+
+ Arguments:
+ $r - Apache request object
+ $i - number of the current scanline
+ $scan_record - hash ref as returned from &scantron_parse_scanline()
+ $scan_config - hash ref as returned from &get_scantron_config()
+ $line - full contents of the current scanline
+ $error - error condition, valid values are
+ 'incorrectCODE', 'duplicateCODE',
+ 'doublebubble', 'missingbubble',
+ 'duplicateID', 'incorrectID'
+ $arg - extra information needed
+ For errors:
+ - duplicateID - paper number that this studentID was seen before on
+ - duplicateCODE - array ref of the paper numbers this CODE was
+ seen on before
+ - incorrectCODE - current incorrect CODE
+ - doublebubble - array ref of the bubble lines that have double
+ bubble errors
+ - missingbubble - array ref of the bubble lines that have missing
+ bubble errors
+
+=item scantron_get_maxbubble() :
+
+ Arguments:
+ $nav_error - Reference to scalar which is a flag to indicate a
+ failure to retrieve a navmap object.
+ if $nav_error is set to 1 by scantron_get_maxbubble(), the
+ calling routine should trap the error condition and display the warning
+ found in &navmap_errormsg().
+
+ $scantron_config - Reference to bubblesheet format configuration hash.
+
+ Returns the maximum number of bubble lines that are expected to
+ occur. Does this by walking the selected sequence rendering the
+ resource and then checking &Apache::lonxml::get_problem_counter()
+ for what the current value of the problem counter is.
+
+ Caches the results to $env{'form.scantron_maxbubble'},
+ $env{'form.scantron.bubble_lines.n'},
+ $env{'form.scantron.first_bubble_line.n'} and
+ $env{"form.scantron.sub_bubblelines.n"}
+ which are the total number of bubble, lines, the number of bubble
+ lines for response n and number of the first bubble line for response n,
+ and a comma separated list of numbers of bubble lines for sub-questions
+ (for optionresponse, matchresponse, and rankresponse items), for response n.
+
+
+=item scantron_validate_missingbubbles() :
+
+ Validates all scanlines in the selected file to not have any
+ answers that don't have bubbles that have not been verified
+ to be bubble free.
+
+=item scantron_process_students() :
+
+ Routine that does the actual grading of the bubblesheet information.
+
+ The parsed scanline hash is added to %env
+
+ Then foreach unskipped scanline it does an &Apache::lonnet::ssi()
+ foreach resource , with the form data of
+
+ 'submitted' =>'scantron'
+ 'grade_target' =>'grade',
+ 'grade_username'=> username of student
+ 'grade_domain' => domain of student
+ 'grade_courseid'=> of course
+ 'grade_symb' => symb of resource to grade
+
+ This triggers a grading pass. The problem grading code takes care
+ of converting the bubbled letter information (now in %env) into a
+ valid submission.
+
+=item scantron_upload_scantron_data() :
+
+ Creates the screen for adding a new bubblesheet data file to a course.
+
+=item scantron_upload_scantron_data_save() :
+
+ Adds a provided bubble information data file to the course if user
+ has the correct privileges to do so.
+
+=item valid_file() :
+
+ Validates that the requested bubble data file exists in the course.
+
+=item scantron_download_scantron_data() :
+
+ Shows a list of the three internal files (original, corrected,
+ skipped) for a specific bubblesheet data file that exists in the
+ course.
+
+=item scantron_validate_ID() :
+
+ Validates all scanlines in the selected file to not have any
+ invalid or underspecified student/employee IDs
+
+=item navmap_errormsg() :
+
+ Returns HTML mark-up inside a
with a link to re-initialize the course.
+ Should be called whenever the request to instantiate a navmap object fails.
+
+=back
+
+=back
+
+=cut