--- loncom/homework/grades.pm 2009/05/01 01:07:49 1.565
+++ loncom/homework/grades.pm 2009/12/27 01:25:26 1.586
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# The LON-CAPA Grading handler
#
-# $Id: grades.pm,v 1.565 2009/05/01 01:07:49 raeburn Exp $
+# $Id: grades.pm,v 1.586 2009/12/27 01:25:26 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -97,9 +97,15 @@ sub ssi_print_error {
#
# --- Retrieve the parts from the metadata file.---
sub getpartlist {
- my ($symb) = @_;
+ my ($symb,$errorref) = @_;
my $navmap = Apache::lonnavmaps::navmap->new();
+ unless (ref($navmap)) {
+ if (ref($errorref)) {
+ $$errorref = 'navmap';
+ return;
+ }
+ }
my $res = $navmap->getBySymb($symb);
my $partlist = $res->parts();
my $url = $res->src();
@@ -144,9 +150,15 @@ sub nameUserString {
#--- Get the partlist and the response type for a given problem. ---
#--- Indicate if a response type is coded handgraded or not. ---
sub response_type {
- my ($symb) = shift;
+ my ($symb,$response_error) = @_;
my $navmap = Apache::lonnavmaps::navmap->new();
+ unless (ref($navmap)) {
+ if (ref($response_error)) {
+ $$response_error = 1;
+ }
+ return;
+ }
my $res = $navmap->getBySymb($symb);
my $partlist = $res->parts();
my %vPart =
@@ -183,7 +195,8 @@ sub get_display_part {
my ($partID,$symb)=@_;
my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);
if (defined($display) and $display ne '') {
- $display.= " (id $partID )";
+ $display.= ' ('
+ .&mt('Part ID: [_1]',$partID).' )';
} else {
$display=$partID;
}
@@ -193,37 +206,49 @@ sub get_display_part {
#--- Show resource title
#--- and parts and response type
sub showResourceInfo {
- my ($symb,$probTitle,$checkboxes) = @_;
- my $col=3;
- if ($checkboxes) { $col=4; }
+ my ($symb,$probTitle,$checkboxes,$res_error) = @_;
my $result = '
'.&mt('Current Resource').': '.$probTitle.' '."\n";
- $result .=''."\n";
+ $result.=&Apache::loncommon::end_data_table();
return $result,$responseType,$hdgrade,$partlist,$handgrade;
}
@@ -754,7 +779,7 @@ sub verifyreceipt {
my $title.=
''.
- &mt('Verifying Receipt No. [_1]',$receipt).
+ &mt('Verifying Receipt No. [_1]',$receipt).
' '."\n".
''.&mt('Resource: [_1]',$env{'form.probTitle'}).
' '."\n";
@@ -766,7 +791,13 @@ sub verifyreceipt {
if ($env{"course.$courseid.receiptalg"} eq 'receipt2' ||
$env{"course.$courseid.receiptalg"} eq 'receipt3') { $receiptparts=1; }
my $parts=['0'];
- if ($receiptparts) { ($parts)=&response_type($symb); }
+ if ($receiptparts) {
+ my $res_error;
+ ($parts)=&response_type($symb,\$res_error);
+ if ($res_error) {
+ return &navmap_errormsg();
+ }
+ }
my $header =
&Apache::loncommon::start_data_table().
@@ -808,11 +839,14 @@ sub verifyreceipt {
}
}
if ($matches == 0) {
- $string = $title.&mt('No match found for the above receipt.');
+ $string = $title
+ .''
+ .&mt('No match found for the above receipt number.')
+ .'
';
} else {
$string = &jscriptNform($symb).$title.
''.
- &mt('The above receipt matches the following [numerate,_1,student].',$matches).
+ &mt('The above receipt number matches the following [quant,_1,student].',$matches).
'
'.
$header.
$contents.
@@ -1677,6 +1711,25 @@ sub get_increment {
return $increment;
}
+sub gradeBox_start {
+ return (
+ &Apache::loncommon::start_data_table()
+ .&Apache::loncommon::start_data_table_header_row()
+ .''.&mt('Part').' '
+ .''.&mt('Points').' '
+ .' '
+ .''.&mt('Assign Grade').' '
+ .''.&mt('Weight').' '
+ .''.&mt('Grade Status').' '
+ .&Apache::loncommon::end_data_table_header_row()
+ );
+}
+
+sub gradeBox_end {
+ return (
+ &Apache::loncommon::end_data_table()
+ );
+}
#--- displays the grading box, used in essay type problem and grading by page/sequence
sub gradeBox {
my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;
@@ -1696,7 +1749,7 @@ sub gradeBox {
if ($last_resets{$partid}) {
$aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
}
- $result.='';
+ $result.=&Apache::loncommon::start_data_table_row();
my $ctr = 0;
my $thisweight = 0;
my $increment = &get_increment();
@@ -1719,8 +1772,8 @@ sub gradeBox {
$wgt.')" />'."\n";
$line.='/'.$wgt.' '.$wgtmsg.
($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? ' '.$checkIcon : '').
- ' '.&mt('Grade Status').': '."\n";
- $line.=''."\n";
+ $line.=''."\n";
if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
$line.=' '.
@@ -1734,9 +1787,8 @@ sub gradeBox {
#&mt('Part: [_1] Points: [_2] or [_3] ',$display_part,$radio,$line);
$result .=
- ''.&mt('Part').': '.$display_part.' '.&mt('Points').': '.$radio.' '.&mt('or').' '.$line.' '.
-
- $result.='
'."\n";
+ ''.$display_part.' '.$radio.' '.&mt('or').' '.$line.' ';
+ $result.=&Apache::loncommon::end_data_table_row();
$result.=' '."\n".
' '."\n".
' '."\n".
' '."\n";
- $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record);
+ my $res_error;
+ $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record,\$res_error);
+ if ($res_error) {
+ return &navmap_errormsg();
+ }
return $result;
}
sub handback_box {
- my ($symb,$uname,$udom,$counter,$partid,$record) = @_;
- my ($partlist,$handgrade,$responseType) = &response_type($symb);
+ my ($symb,$uname,$udom,$counter,$partid,$record,$res_error) = @_;
+ my ($partlist,$handgrade,$responseType) = &response_type($symb,$res_error);
my (@respids);
my @part_response_id = &flatten_responseType($responseType);
foreach my $part_response_id (@part_response_id) {
@@ -2046,7 +2102,12 @@ KEYWORDS
}
my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
- my ($partlist,$handgrade,$responseType) = &response_type($symb);
+ my $res_error;
+ my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
+ if ($res_error) {
+ $request->print(&navmap_errormsg());
+ return;
+ }
# Display student info
$request->print(($counter == 0 ? '' : ' '));
@@ -2113,9 +2174,11 @@ KEYWORDS
}
my $responsetype = $responseType->{$partid}->{$respid};
if (!exists($record{"resource.$partid.$respid.submission"})) {
- $lastsubonly.="\n".'Part: '.
- $display_part.'
( ID '.$respid.
- ' ) '.
+ $lastsubonly.="\n".'
'.
+ ''.&mt('Part: [_1]',$display_part).' '.
+ ' '.
+ '('.&mt('Part ID: [_1]',$respid).')'.
+ ' '.
''.&mt('Nothing submitted - no attempts.').'
';
next;
}
@@ -2135,10 +2198,9 @@ KEYWORDS
{'one_time' => 1});
$similar="
".
- &mt('Essay is [_1]% similar to an essay by [_2] ([_3]:[_4]) in course [_5] (course id [_6]:[_7])',
+ &mt('Essay is [_1]% similar to an essay by [_2] in course [_3] (course id [_4]:[_5])',
$osim,
- &Apache::loncommon::plainname($oname,$odom),
- $oname,$odom,
+ &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')',
$old_course_desc{'description'},
$old_course_desc{'num'},
$old_course_desc{'domain'}).
@@ -2152,9 +2214,11 @@ KEYWORDS
($env{'form.lastSub'} eq 'hdgrade' &&
$$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) {
my $display_part=&get_display_part($partid,$symb);
- $lastsubonly.='Part: '.
- $display_part.'
( ID '.$respid.
- ' ) ';
+ $lastsubonly.='
'.
+ '
'.&mt('Part: [_1]',$display_part).' '.
+ '
'.
+ '('.&mt('Part ID: [_1]',$respid).')'.
+ ' ';
my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
if (@$files) {
$lastsubonly.='
'.&mt('Like all files provided by users, this file may contain viruses').' ';
@@ -2236,11 +2300,12 @@ KEYWORDS
my @partlist;
my @gradePartRespid;
my @part_response_id = &flatten_responseType($responseType);
- $request->print('
'.
-
- ''.
- '
');
+ $request->print(
+ '
'
+ .''
+ );
+ $request->print(&gradeBox_start()); #
foreach my $part_response_id (@part_response_id) {
my ($partid,$respid) = @{ $part_response_id };
my $part_resp = join('_',@{ $part_response_id });
@@ -2252,7 +2317,8 @@ KEYWORDS
push(@gradePartRespid,$partid.'.'.$respid);
$request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
}
- $request->print('
');
+ $request->print(&gradeBox_end()); #
+ $request->print('
');
$request->print('
');
if ($perm{'vgr'}) {
@@ -2297,7 +2363,7 @@ KEYWORDS
'
7 10 '."\n";
my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1');
$ntstu =~ s/
$nsel $nsel;
- $endform.=&mt('[quant,_1,student]',$ntstu);
+ $endform.=&mt('[_1]student(s)',$ntstu);
$endform.=' '."\n".
' print(' '.&mt('Sending message to [_1]:[_2]',$uname,$udom).': '.
+ $request->print(' '.&mt('Sending message to [_1]',$uname.':'.$udom).': '.
$msgstatus);
}
if ($env{'form.collaborator'.$ctr}) {
@@ -2594,7 +2660,12 @@ sub processHandGrade {
}
$ctr = 0;
@parsedlist = reverse @parsedlist if ($button eq 'Previous');
- my ($partlist) = &response_type($symb);
+ my $res_error;
+ my ($partlist) = &response_type($symb,\$res_error);
+ if ($res_error) {
+ $request->print(&navmap_errormsg());
+ return;
+ }
foreach my $student (@parsedlist) {
my $submitonly=$env{'form.submitonly'};
my ($uname,$udom) = split(/:/,$student);
@@ -2792,8 +2863,12 @@ sub check_and_remove_from_queue {
sub handback_files {
my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_;
my $portfolio_root = '/userfiles/portfolio';
- my ($partlist,$handgrade,$responseType) = &response_type($symb);
-
+ my $res_error;
+ my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
+ if ($res_error) {
+ $request->print(' '.&navmap_errormsg().' ');
+ return;
+ }
my @part_response_id = &flatten_responseType($responseType);
foreach my $part_response_id (@part_response_id) {
my ($part_id,$resp_id) = @{ $part_response_id };
@@ -3251,7 +3326,11 @@ sub viewgrades {
$result.= ''.$common_header.' '.&Apache::loncommon::start_data_table();
#radio buttons/text box for assigning points for a section or class.
#handles different parts of a problem
- 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();
+ }
my %weight = ();
my $ctsparts = 0;
my %seen = ();
@@ -3295,7 +3374,7 @@ sub viewgrades {
$result.=
&Apache::loncommon::start_data_table_row()."\n".
- ''.&mt('Part').': '.$display_part.' '.&mt('Points').': '.$radio.' '.&mt('or').' '.$line.' '.
+ ''.&mt('Part:').' '.$display_part.' '.&mt('Points:').' '.$radio.' '.&mt('or').' '.$line.' '.
&Apache::loncommon::end_data_table_row()."\n";
$ctsparts++;
}
@@ -3311,7 +3390,11 @@ sub viewgrades {
&Apache::loncommon::start_data_table_header_row().
''.&mt('No.').' '.
''.&nameUserString('header')." \n";
- my (@parts) = sort(&getpartlist($symb));
+ my $partserror;
+ my (@parts) = sort(&getpartlist($symb,\$partserror));
+ if ($partserror) {
+ return &navmap_errormsg();
+ }
my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
my @partids = ();
foreach my $part (@parts) {
@@ -3470,7 +3553,11 @@ sub editgrades {
my %columns = ();
my ($i,$ctr,$count,$rec_update) = (0,0,0,0);
- my (@parts) = sort(&getpartlist($symb));
+ my $partserror;
+ my (@parts) = sort(&getpartlist($symb,\$partserror));
+ if ($partserror) {
+ return &navmap_errormsg();
+ }
my $header;
while ($ctr < $env{'form.totalparts'}) {
my $partid = $env{'form.partid_'.$ctr};
@@ -3681,7 +3768,7 @@ sub split_part_type {
#
#--- Javascript to handle csv upload
sub csvupload_javascript_reverse_associate {
- my $error1=&mt('You need to specify the username or ID');
+ my $error1=&mt('You need to specify the username or the student/employee ID');
my $error2=&mt('You need to specify at least one grading field');
return(<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,
@@ -4090,14 +4187,14 @@ sub csvuploadassign {
$countdone++;
}
}
- $request->print(''.&mt("Saved [_1] students",$countdone)." \n");
+ $request->print(' '.&Apache::lonhtmlcommon::confirm_success(&mt("Saved scores for [quant,_1,student]",$countdone),$countdone==0));
if (@skipped) {
- $request->print(''.&mt('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(''.&mt('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));
@@ -4140,7 +4237,12 @@ LISTJAVASCRIPT
&mt('Manual Grading by Page or Sequence').'';
$result.='
');
@@ -5178,7 +5312,7 @@ sub scantron_selectphase {
&Apache::loncommon::start_data_table('LC_scantron_action')."\n".
&Apache::loncommon::start_data_table_header_row()."\n".
'
- '.&mt('Review scantron data and submissions for a previously graded folder/sequence')."\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".
@@ -5200,7 +5334,7 @@ sub scantron_selectphase {
&Apache::loncommon::start_data_table_row()."\n".
''."\n".
' '."\n".
- ' '."\n".
+ ' '."\n".
' '."\n".
&Apache::loncommon::end_data_table_row()."\n".
&Apache::loncommon::end_data_table()."\n".
@@ -5241,7 +5375,7 @@ sub scantron_selectphase {
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/employee ID number starts
+ 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
@@ -5341,7 +5475,7 @@ sub username_to_idmap {
$whichline - line number of the passed in scanline
$field - type of change to process
(either
- 'ID' -> correct the student/employee ID number
+ 'ID' -> correct the student/employee ID
'CODE' -> correct the CODE
'answer' -> fixup the submitted answers)
@@ -6215,7 +6349,12 @@ sub scantron_validate_file {
$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 $max_bubble=&scantron_get_maxbubble(\$nav_error);
+ if ($nav_error) {
+ $r->print(&navmap_errormsg());
+ return '';
+ }
my $result=&scantron_form_start($max_bubble).$default_form_data;
$r->print($result);
@@ -6251,7 +6390,7 @@ sub scantron_validate_file {
' '.&mt('No').
' '.
&mt('Grading will take longer if you use verification.').' '.
- &mt("Alternatively, the 'Review scantron data' utility (see grading menu) can be used for all students after grading is complete.").' '.
+ &mt("Alternatively, the 'Review bubblesheet data' utility (see grading menu) can be used for all students after grading is complete.").' '.
' '.
' '."\n");
} else {
@@ -6627,6 +6766,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'});
@@ -6658,8 +6801,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); # 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++) {
@@ -6777,10 +6925,10 @@ sub scantron_get_correction {
if ($closest > 0) {
foreach my $testcode (@{$closest}) {
my $checked='';
- if (!$i) { $checked=' checked="checked" '; }
+ if (!$i) { $checked=' checked="checked"'; }
$r->print("
-
+
".&mt("Use the similar CODE [_1] instead.",
"".$testcode." ")."
@@ -6791,10 +6939,10 @@ sub scantron_get_correction {
}
}
if ($$scan_record{'scantron.CODE'}=~/\S/ ) {
- my $checked; if (!$i) { $checked=' checked="checked" '; }
+ my $checked; if (!$i) { $checked=' checked="checked"'; }
$r->print("
-
+
".&mt("Use the CODE [_1] that is was on the paper, ignoring the error.",
"".$$scan_record{'scantron.CODE'}." ")."
");
@@ -7018,7 +7166,7 @@ sub prompt_for_corrections {
($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 scantron sheets.",$lines).' '.&mt('A non-zero score can be assigned to the student during scantron 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'.").' ');
+ $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. ")." ");
}
@@ -7212,7 +7360,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); # 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++) {
@@ -7266,7 +7419,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); # 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);
@@ -7284,6 +7442,7 @@ sub scantron_validate_doublebubble {
sub scantron_get_maxbubble {
+ my ($nav_error) = @_;
if (defined($env{'form.scantron_maxbubble'}) &&
$env{'form.scantron_maxbubble'}) {
&restore_bubble_lines();
@@ -7294,6 +7453,11 @@ 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;
+ }
+ }
my $map=$navmap->getResourceByUrl($sequence);
my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
@@ -7383,7 +7547,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);
+ 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);
@@ -7443,13 +7611,24 @@ sub scantron_process_students {
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);
my (%grader_partids_by_symb,%grader_randomlists_by_symb);
&graders_resources_pass(\@resources,\%grader_partids_by_symb,
\%grader_randomlists_by_symb);
+ my $resource_error;
foreach my $resource (@resources) {
- my $ressymb = $resource->symb();
+ 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);
@@ -7461,6 +7640,10 @@ sub scantron_process_students {
}
}
}
+ if ($resource_error) {
+ $r->print(&navmap_errormsg());
+ return '';
+ }
my ($uname,$udom);
my $result= <print(&navmap_errormsg());
+ return '';
+ }
# If an ssi failed in scantron_get_maxbubble, put an error message out to
# the user and return.
@@ -7526,9 +7714,15 @@ SCANTRONFORM
}
($uname,$udom)=split(/:/,$uname);
- my %partids_by_symb;
+ my (%partids_by_symb,$res_error);
foreach my $resource (@resources) {
- my $ressymb = $resource->symb();
+ 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) =
@@ -7539,6 +7733,12 @@ SCANTRONFORM
}
}
+ 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);
@@ -7710,15 +7910,17 @@ sub scantron_upload_scantron_data {
my $syllabuslink = ''.&mt('Syllabus').' '.
(' 'x2).&mt('(shows course personnel)');
my $default_form_data=&defaultFormData(&get_symb($r,1));
+ 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('
+'.&mt('Send scanned bubblesheet data to a course').'
+
');
return '';
@@ -7776,7 +7980,7 @@ sub scantron_upload_scantron_data_save {
if (!&Apache::lonnet::allowed('usc',$env{'form.domainid'}) &&
!&Apache::lonnet::allowed('usc',
$env{'form.domainid'}.'_'.$env{'form.courseid'})) {
- $r->print(&mt("You are not allowed to upload Scantron data to the requested course.")." ");
+ $r->print(&mt("You are not allowed to upload bubblesheet data to the requested course.")." ");
if ($symb) {
$r->print(&show_grading_menu_form($symb));
} else {
@@ -7785,36 +7989,25 @@ sub scantron_upload_scantron_data_save {
return '';
}
my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
- $r->print(&mt("Doing upload to [_1]",$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(&mt("Error: The file you attempted to upload, [_1] contained no information. Please check that you entered the correct filename.",''.&HTML::Entities::encode($env{'form.upfile.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(&mt("Success: Successfully uploaded [_1] bytes of data into location [_2]",
- (length($env{'form.upfile'})-1),
- ''.$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(&mt("Error: An error ([_1]) occurred when attempting to upload the file, [_2]",
- $result,
- ''.&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) {
@@ -7825,6 +8018,92 @@ sub scantron_upload_scantron_data_save {
return '';
}
+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)=@_;
foreach my $filename (sort(&scantron_filenames())) {
@@ -7890,6 +8169,10 @@ sub checkscantron_results {
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);
@@ -7904,12 +8187,16 @@ sub checkscantron_results {
my %completedstudents;
my $count=&Apache::grades::get_todo_count($scanlines,$scan_data);
- my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron/Submissions Comparison Status',
- 'Progress of Scantron Data/Submission Records Comparison',$count,
+ my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Bubblesheet/Submissions Comparison Status',
+ 'Progress of Bubblesheet Data/Submission Records Comparison',$count,
'inline',undef,'checkscantron');
my ($username,$domain,$started);
-
- &scantron_get_maxbubble(); # Need the bubble lines array to parse.
+ my $nav_error;
+ &scantron_get_maxbubble(\$nav_error); # 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');
@@ -7982,14 +8269,14 @@ sub checkscantron_results {
if ($scandata{$pid} eq $record{$pid}) {
my $css_class = ($passed % 2)?'LC_odd_row':'LC_even_row';
$okstudents .= ''.
-''.&mt('Scantron').' '.$showscandata.' '.$last.' '.$pid.' '."\n".
+''.&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('Scantron').' '.$scandata{$pid}.' '.$last.' '.$pid.' '."\n".
+ $badstudents .= ''.&mt('Bubblesheet').' '.$scandata{$pid}.' '.$last.' '.$pid.' '."\n".
' '."\n".
''."\n".
'Submissions '.$record{$pid}.' '."\n".
@@ -8000,10 +8287,10 @@ sub checkscantron_results {
}
}
}
- $r->print(''.&mt('Comparison of scantron data (including corrections) with corresponding submission records (most recent submission) for [quant,_1,student] ([_2] scantron lines/student).',$numstudents,$env{'form.scantron_maxbubble'}).'
');
+ $r->print(''.&mt('Comparison of bubblesheet data (including corrections) with corresponding submission records (most recent submission) for [quant,_1,student] ([_2] scantron lines/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 scantron data and submissions are as follows:').' ');
+ $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').' '.
@@ -8012,14 +8299,14 @@ sub checkscantron_results {
&Apache::loncommon::end_data_table().' ');
}
if ($failed) {
- $r->print(&mt('Students with differences between scantron data and submissions are as follows:').' ');
+ $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 scantron grading pass.').' '.&mt('If unexpected discrepancies were detected, it is recommended that you inspect the original scantron sheets.');
+ &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(' '.$grading_menu_button);
return;
@@ -8234,7 +8521,7 @@ sub grading_menu {
icon => 'addClickerInfoFile.png',
linktitle => 'Specify a file containing the clicker information for this resource.'
},
- { linktext => 'Grade/Manage/Review Scantron Forms',
+ { linktext => 'Grade/Manage/Review Bubblesheet Forms',
url => $url4,
permission => 'F',
icon => 'stat.png',
@@ -8260,7 +8547,7 @@ sub grading_menu {
$Str .= Apache::lonhtmlcommon::generate_menu(@menu);
#$menudata->{'jscript'}
- $Str .=' '.
&Apache::lonnet::recprefix($env{'request.course.id'}).
@@ -8580,7 +8867,7 @@ sub process_clicker {
my %checked;
foreach my $gradingmechanism ('attendance','personnel','specific','given') {
if ($env{'form.gradingmechanism'} eq $gradingmechanism) {
- $checked{$gradingmechanism}="checked='checked'";
+ $checked{$gradingmechanism}=' checked="checked"';
}
}
@@ -8644,11 +8931,11 @@ function sanitycheck() {
$type: $selectform
- $attendance
- $personnel
- $specific
+ $attendance
+ $personnel
+ $specific
- $given
+ $given
@@ -8903,7 +9190,11 @@ sub assign_clicker_grades {
my ($symb)=&get_symb($r);
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
@@ -8967,13 +9258,13 @@ ENDHEADER
my $sum=0;
my $realnumber=$number;
for (my $i=0;$i<$number;$i++) {
- if ($answer[$i]) {
+ if ($correct[$i] eq '-') {
+ $realnumber--;
+ } elsif ($answer[$i]) {
if ($gradingmechanism eq 'attendance') {
$sum+=$pcorrect;
- } elsif ($answer[$i] eq '*') {
+ } elsif ($correct[$i] eq '*') {
$sum+=$pcorrect;
- } elsif ($answer[$i] eq '-') {
- $realnumber--;
} else {
if ($answer[$i] eq $correct[$i]) {
$sum+=$pcorrect;
@@ -9007,6 +9298,13 @@ ENDHEADER
return $result.&show_grading_menu_form($symb);
}
+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 handler {
my $request=$_[0];
&reset_caches();
@@ -9232,6 +9530,13 @@ ssi_with_retries()
=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().
+
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()
@@ -9297,6 +9602,11 @@ ssi_with_retries()
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
=cut