');
- hDoc.write('');
- hDoc.write("$lt{'txtc'}<\\/b><\\/td> $lt{'font'}<\\/b><\\/td> $lt{'fnst'}<\\/td><\\/tr>");
+ hDoc.write('');
+ hDoc.write("$lt{'txtc'}<\\/th> $lt{'font'}<\\/th> $lt{'fnst'}<\\/th><\\/tr>");
}
function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) {
var hDoc = hwdWin.document;
- hDoc.write(" ");
+ hDoc.write(" ");
hDoc.write("");
- hDoc.write(" "+clrtxt+"<\\/td>");
+ hDoc.write(" "+clrtxt+"<\\/td>");
hDoc.write(" ");
- hDoc.write(" "+sztxt+"<\\/td>");
+ hDoc.write(" "+sztxt+"<\\/td>");
hDoc.write(" ");
- hDoc.write(" "+sytxt+"<\\/td>");
+ hDoc.write(" "+sytxt+"<\\/td>");
hDoc.write("<\\/tr>");
}
function highlightend() {
var hDoc = hwdWin.document;
- hDoc.write("<\\/table>");
- hDoc.write("<\\/td><\\/tr><\\/table> ");
- hDoc.write(" ");
- hDoc.write(" ");
+ hDoc.write("<\\/table> ");
+ hDoc.write(" ");
+ hDoc.write(" ");
hDoc.write("<\\/form>");
hDoc.write('$end_page_highlight_central');
hDoc.close();
@@ -1786,7 +1813,7 @@ sub gradeBox {
$wgt = ($wgt > 0 ? $wgt : '1');
my $score = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
'' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));
- my $result=' '."\n";
+ my $data_WGT=' '."\n";
my $display_part= &get_display_part($partid,$symb);
my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
[$partid]);
@@ -1794,7 +1821,7 @@ sub gradeBox {
if ($last_resets{$partid}) {
$aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
}
- $result.=&Apache::loncommon::start_data_table_row();
+ my $result=&Apache::loncommon::start_data_table_row();
my $ctr = 0;
my $thisweight = 0;
my $increment = &get_increment();
@@ -1831,8 +1858,8 @@ sub gradeBox {
$result .=
- ' '.$display_part.' '.$radio.' '.&mt('or').' '.$line.' ';
- $result.=&Apache::loncommon::end_data_table_row();
+ ''.$data_WGT.$display_part.' '.$radio.' '.&mt('or').' '.$line.' ';
+ $result.=&Apache::loncommon::end_data_table_row().'';
$result.=' '."\n".
' '."\n".
' '."\n";
my $res_error;
$result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record,\$res_error);
+ $result.=' '.&Apache::loncommon::end_data_table_row();
if ($res_error) {
return &navmap_errormsg();
}
@@ -1916,14 +1944,27 @@ sub show_problem {
$companswer=~s|||g;
$companswer=~s|name="submit"|name="would_have_been_submit"|g;
}
+ my $renderheading = &mt('View of the problem');
+ my $answerheading = &mt('Correct answer');
+ if (($uname ne $env{'user.name'}) || ($udom ne $env{'user.domain'})) {
+ my $stu_fullname = $env{'form.fullname'};
+ if ($stu_fullname eq '') {
+ $stu_fullname = &Apache::loncommon::plainname($uname,$udom,'lastname');
+ }
+ my $forwhom = &nameUserString(undef,$stu_fullname,$uname,$udom);
+ if ($forwhom ne '') {
+ $renderheading = &mt('View of the problem for[_1]',$forwhom);
+ $answerheading = &mt('Correct answer for[_1]',$forwhom);
+ }
+ }
$rendered=
''
- .'
'.&mt('View of the problem').' '
+ .''.$renderheading.' '
.$rendered
.'';
$companswer=
''
- .'
'.&mt('Correct answer').' '
+ .''.$answerheading.' '
.$companswer
.'';
my $result;
@@ -1994,13 +2035,16 @@ sub submission {
$udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student?
my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
$env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq '';
- my $symb = &get_symb($request);
+ my ($symb) = &get_symb($request);
if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; }
if (!&canview($usec)) {
- $request->print('Unable to view requested student.('.
- $uname.':'.$udom.' in section '.$usec.' in course id '.
- $env{'request.course.id'}.') ');
+ $request->print(
+ ''.
+ &mt('Unable to view requested student.').
+ ' '.&mt('([_1] in section [_2] in course id [_3])',
+ $uname.':'.$udom,$usec,$env{'request.course.id'}).
+ ' ');
$request->print(&show_grading_menu_form($symb));
return;
}
@@ -2013,7 +2057,6 @@ sub submission {
'" src="'.$request->dir_config('lonIconsURL').
'/check.gif" height="16" border="0" />';
- my %old_essays;
# header info
if ($counter == 0) {
&sub_page_js($request);
@@ -2024,7 +2067,7 @@ sub submission {
&download_all_link($request, $symb);
}
$request->print(' '.&mt('Submission Record').' '."\n".
- ' '.&mt('Resource: [_1]',$env{'form.probTitle'}).' '."\n");
+ ' '.&mt('[_1]Resource: [_2]','',' '.$env{'form.probTitle'}).' '."\n");
# option to display problem, only once else it cause problems
# with the form later since the problem has a form.
@@ -2041,7 +2084,7 @@ sub submission {
$request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode));
}
- # kwclr is the only variable that is guaranteed to be non blank
+ # kwclr is the only variable that is guaranteed not to be blank
# if this subroutine has been called once.
my %keyhash = ();
if ($env{'form.kwclr'} eq '' && $env{'form.handgrade'} eq 'yes') {
@@ -2107,6 +2150,7 @@ sub submission {
if ($env{'form.handgrade'} eq 'yes' && $env{'form.showgrading'} eq 'yes') {
my %lt = &Apache::lonlocal::texthash(
+ keyh => 'Keyword Highlighting for Essays',
keyw => 'Keyword Options',
list => 'List',
past => 'Paste Selection to List',
@@ -2115,13 +2159,18 @@ sub submission {
#
# Print out the keyword options line
#
- $request->print(<$lt{'keyw'}:
-$lt{'list'}
-$lt{'past'}
-$lt{'high'}
-KEYWORDS
+ $request->print(
+ ''
+ );
+
#
# Load the other essays for similarity check
#
@@ -2129,7 +2178,7 @@ KEYWORDS
my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);
$apath=&escape($apath);
$apath=~s/\W/\_/gs;
- %old_essays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
+ &init_old_essays($symb,$apath,$adom,$aname);
}
}
@@ -2146,7 +2195,7 @@ KEYWORDS
if ($perm{'vgr'}) {
$request->print(
&Apache::loncommon::track_student_link(
- &mt('View recent activity'),
+ 'View recent activity',
$uname,$udom,'check')
.' '
);
@@ -2210,7 +2259,7 @@ KEYWORDS
# (for multi-response type part)
# (3) Last submission plus the parts info
# (4) The whole record for this student
- if ($env{'form.lastSub'} =~ /^(lastonly|hdgrade)$/) {
+
my ($string,$timestamp)= &get_last_submission(\%record);
my $lastsubonly;
@@ -2233,13 +2282,14 @@ KEYWORDS
if ($env{"form.$uname:$udom:$partid:submitted_by"}) {
if (exists($seenparts{$partid})) { next; }
$seenparts{$partid}=1;
- my $submitby='Part: '.$display_part.
- ' Collaborative submission by: '.
- ''.
- $$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.' ';
- $request->print($submitby);
+ $request->print(
+ ''.&mt('Part: [_1]',$display_part).' '.
+ ' '.&mt('Collaborative submission by: [_1]',
+ ''.
+ $$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.' ').
+ ' ');
next;
}
my $responsetype = $responseType->{$partid}->{$respid};
@@ -2255,7 +2305,7 @@ KEYWORDS
foreach my $submission (@$string) {
my ($partid,$respid) = ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }
- my ($ressub,$hide,$subval) = split(/:/,$submission,3);
+ my ($ressub,$hide,$draft,$subval) = split(/:/,$submission,4);
# Similarity check
my $similar='';
my ($type,$trial,$rndseed);
@@ -2264,9 +2314,9 @@ KEYWORDS
$trial = $record{"resource.$partid.tries"};
$rndseed = $record{"resource.$partid.rndseed"};
}
- if($env{'form.checkPlag'}){
+ if ($env{'form.checkPlag'}) {
my ($oname,$odom,$ocrsid,$oessay,$osim)=
- &most_similar($uname,$udom,$subval,\%old_essays);
+ &most_similar($uname,$udom,$symb,$subval);
if ($osim) {
$osim=int($osim*100.0);
my %old_course_desc =
@@ -2292,9 +2342,8 @@ KEYWORDS
}
my $order=&get_order($partid,$respid,$symb,$uname,$udom,
undef,$type,$trial,$rndseed);
- if ($env{'form.lastSub'} eq 'lastonly' ||
- ($env{'form.lastSub'} eq 'hdgrade' &&
- $$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) {
+ if ($env{'form.lastSub'} eq 'lastonly' || $env{'form.lastSub'} eq 'datesub' || $env{'form.lastSub'} =~ /^(last|all)$/ || ($env{'form.lastSub'} eq 'hdgrade' &&
+ $$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) {
my $display_part=&get_display_part($partid,$symb);
$lastsubonly.=''.
'
'.&mt('Part: [_1]',$display_part).' '.
@@ -2306,20 +2355,36 @@ KEYWORDS
if ($hide eq 'anon') {
$lastsubonly.='
'.&mt('[quant,_1,file] uploaded to this anonymous survey',scalar(@{$files}));
} else {
- $lastsubonly.='
'.&mt('Like all files provided by users, this file may contain viruses').' ';
+ $lastsubonly.='
'.'
'.&mt('Submitted Files:').' '
+ .'
';
+ if(@$files == 1) {
+ $lastsubonly .= &mt('Like all files provided by users, this file may contain viruses!');
+ } else {
+ $lastsubonly .= &mt('Like all files provided by users, these files may contain viruses!');
+ }
+ $lastsubonly .= ' ';
+
foreach my $file (@$files) {
&Apache::lonnet::allowuploaded('/adm/grades',$file);
- $lastsubonly.='
'.$file.'';
+ $lastsubonly.='
'.$file.'';
}
}
$lastsubonly.='
';
}
if ($hide eq 'anon') {
- $lastsubonly.='
'.&mt('Anonymous Survey').' ';
+ $lastsubonly.='
'.&mt('Anonymous Survey').' ';
} else {
- $lastsubonly.='
'.&mt('Submitted Answer:').' '.
+ $lastsubonly.='
'.&mt('Submitted Answer:').' ';
+ if ($draft) {
+ $lastsubonly.= '
'.&mt('Draft Copy').' ';
+ }
+ $subval =
&cleanRecord($subval,$responsetype,$symb,$partid,
$respid,\%record,$order,undef,$uname,$udom,$type,$trial,$rndseed);
+ if ($responsetype eq 'essay') {
+ $subval =~ s{\n}{
}g;
+ }
+ $lastsubonly.=$subval."\n";
}
if ($similar) {$lastsubonly.="
$similar\n";}
$lastsubonly.='
';
@@ -2329,14 +2394,17 @@ KEYWORDS
$lastsubonly.=''."\n"; # End: LC_grade_submissions_body
}
$request->print($lastsubonly);
- } elsif ($env{'form.lastSub'} eq 'datesub') {
+ if ($env{'form.lastSub'} eq 'datesub') {
my (undef,$responseType,undef,$parts) = &showResourceInfo($symb);
$request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom));
- } elsif ($env{'form.lastSub'} =~ /^(last|all)$/) {
+ }
+ if ($env{'form.lastSub'} =~ /^(last|all)$/) {
+ my $identifier = (&canmodify($usec)? $counter : '');
$request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,
$env{'request.course.id'},
$last,'.submission',
- 'Apache::grades::keywords_highlight'));
+ 'Apache::grades::keywords_highlight',
+ $usec,$identifier));
}
$request->print(' '."\n";
$result.=' '.
- &mt('Compose message to student').(scalar(@$col_fullnames) >= 1 ? 's' : '').' ('.
+ &mt('Compose message to student'.(scalar(@$col_fullnames) >= 1 ? 's' : '')).' ('.
&mt('incl. grades').' )'.
- ' dir_config('lonIconsURL').
'/mailbkgrd.gif" width="14" height="10" name="mailicon'.$counter.'" />'."\n".
' ('.
&mt('Message will be sent when you click on Save & Next below.').")\n";
@@ -2573,9 +2641,10 @@ sub get_last_submission {
}
}
my ($partid,$foo) = split(/submission$/,$key);
- my $draft = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ?
- 'Draft Copy ' : '';
- push(@string, join(':', $key, $hide, $draft.$lasthash{$key}));
+ my $draft = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ? 1: 0;
+ push(@string, join(':', $key, $hide, $draft, (
+ ref($lasthash{$key}) eq 'ARRAY' ?
+ join(',', @{$lasthash{$key}}) : $lasthash{$key}) ));
}
}
if (!@string) {
@@ -2598,10 +2667,191 @@ sub keywords_highlight {
return $string;
}
+# For Tasks provide a mechanism to display previous version for one specific student
+
+sub show_previous_task_version {
+ my ($request,$symb) = @_;
+ if ($symb eq '') {
+ $request->print(
+ ''.
+ &mt('Unable to handle ambiguous references.').
+ ' ');
+ return '';
+ }
+ my ($uname,$udom) = ($env{'form.student'},$env{'form.userdom'});
+ my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
+ if (!&canview($usec)) {
+ $request->print(''.
+ &mt('Unable to view previous version for requested student.').
+ ' '.&mt('([_1] in section [_2] in course id [_3])',
+ $uname.':'.$udom,$usec,$env{'request.course.id'}).
+ ' ');
+ return;
+ }
+ my $mode = 'both';
+ my $isTask = ($symb =~/\.task$/);
+ if ($isTask) {
+ if ($env{'form.previousversion'} =~ /^\d+$/) {
+ if ($env{'form.fullname'} eq '') {
+ $env{'form.fullname'} =
+ &Apache::loncommon::plainname($uname,$udom,'lastname');
+ }
+ my $probtitle=&Apache::lonnet::gettitle($symb);
+ $request->print("\n\n".
+ ''.
+ '
'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
+ ' '."\n");
+ &Apache::lonxml::clear_problem_counter();
+ $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode,
+ {'previousversion' => $env{'form.previousversion'} }));
+ $request->print("\n");
+ }
+ }
+ return;
+}
+
+sub choose_task_version_form {
+ my ($symb,$uname,$udom,$nomenu) = @_;
+ my $isTask = ($symb =~/\.task$/);
+ my ($current,$version,$result,$js,$displayed,$rowtitle);
+ if ($isTask) {
+ my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
+ $udom,$uname);
+ if (($record{'resource.0.version'} eq '') ||
+ ($record{'resource.0.version'} < 2)) {
+ return ($record{'resource.0.version'},
+ $record{'resource.0.version'},$result,$js);
+ } else {
+ $current = $record{'resource.0.version'};
+ }
+ if ($env{'form.previousversion'}) {
+ $displayed = $env{'form.previousversion'};
+ $rowtitle = &mt('Choose another version:')
+ } else {
+ $displayed = $current;
+ $rowtitle = &mt('Show earlier version:');
+ }
+ $result = '';
+ my $list;
+ my $numversions = 0;
+ for (my $i=1; $i<=$record{'resource.0.version'}; $i++) {
+ if ($i == $current) {
+ if (!$env{'form.previousversion'} || $nomenu) {
+ next;
+ } else {
+ $list .= '
'.&mt('Current').' '."\n";
+ $numversions ++;
+ }
+ } elsif (defined($record{'resource.'.$i.'.0.status'})) {
+ unless ($i == $env{'form.previousversion'}) {
+ $numversions ++;
+ }
+ $list .= '
'.$i.' '."\n";
+ }
+ }
+ if ($numversions) {
+ $symb = &HTML::Entities::encode($symb,'<>"&');
+ $result .=
+ '
';
+ $js = &previous_display_javascript($nomenu,$current);
+ } elsif ($displayed && $nomenu) {
+ $result .= '
'.&mt('Close window').' ';
+ } else {
+ $result .= &mt('No previous versions to show for this student');
+ }
+ $result .= '
';
+ }
+ return ($current,$displayed,$result,$js);
+}
+
+sub previous_display_javascript {
+ my ($nomenu,$current) = @_;
+ my $js = <<"JSONE";
+
+ENDJS
+
+}
+
#--- Called from submission routine
sub processHandGrade {
my ($request) = shift;
- my $symb = &get_symb($request);
+ my ($symb) = &get_symb($request);
my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
my $button = $env{'form.gradeOpt'};
my $ngrade = $env{'form.NCT'};
@@ -2613,16 +2863,26 @@ sub processHandGrade {
my $ctr = 0;
while ($ctr < $ngrade) {
my ($uname,$udom) = split(/:/,$env{'form.unamedom'.$ctr});
- my ($errorflag,$pts,$wgt) = &saveHandGrade($request,$symb,$uname,$udom,$ctr);
+ my ($errorflag,$pts,$wgt,$numhidden) =
+ &saveHandGrade($request,$symb,$uname,$udom,$ctr);
if ($errorflag eq 'no_score') {
$ctr++;
next;
}
if ($errorflag eq 'not_allowed') {
- $request->print("Not allowed to modify grades for $uname:$udom ");
+ $request->print(
+ ''
+ .&mt('Not allowed to modify grades for [_1]',"$uname:$udom")
+ .' ');
$ctr++;
next;
}
+ if ($numhidden) {
+ $request->print(
+ ''
+ .&mt('For [_1]: [quant,_2,transaction] hidden',"$uname:$udom",$numhidden)
+ .' ');
+ }
my $includemsg = $env{'form.includemsg'.$ctr};
my ($subject,$message,$msgstatus) = ('','','');
my $restitle = &Apache::lonnet::gettitle($symb);
@@ -2854,9 +3114,14 @@ sub saveHandGrade {
my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$stuname);
my @parts_graded;
my %newrecord = ();
- my ($pts,$wgt) = ('','');
+ my ($pts,$wgt,$totchg) = ('','',0);
my %aggregate = ();
my $aggregateflag = 0;
+ if ($env{'form.HIDE'.$newflg}) {
+ my ($version,$parts) = split(/:/,$env{'form.HIDE'.$newflg},2);
+ my $numchgs = &makehidden($version,$parts,\%record,$symb,$domain,$stuname,1);
+ $totchg += $numchgs;
+ }
my @parts = split(/:/,$env{'form.partlist'.$newflg});
foreach my $new_part (@parts) {
#collaborator ($submi may vary for different parts
@@ -2959,7 +3224,37 @@ sub saveHandGrade {
&Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
$cdom,$cnum);
}
- return ('',$pts,$wgt);
+ return ('',$pts,$wgt,$totchg);
+}
+
+sub makehidden {
+ my ($version,$parts,$record,$symb,$domain,$stuname,$tolog) = @_;
+ return unless (ref($record) eq 'HASH');
+ my %modified;
+ my $numchanged = 0;
+ if (exists($record->{$version.':keys'})) {
+ my $partsregexp = $parts;
+ $partsregexp =~ s/,/|/g;
+ foreach my $key (split(/\:/,$record->{$version.':keys'})) {
+ if ($key =~ /^resource\.(?:$partsregexp)\.([^\.]+)$/) {
+ my $item = $1;
+ unless (($item eq 'solved') || ($item =~ /^award(|msg|ed)$/)) {
+ $modified{$key} = $record->{$version.':'.$key};
+ }
+ } elsif ($key =~ m{^(resource\.(?:$partsregexp)\.[^\.]+\.)(.+)$}) {
+ $modified{$1.'hidden'.$2} = $record->{$version.':'.$key};
+ } elsif ($key =~ /^(ip|timestamp|host)$/) {
+ $modified{$key} = $record->{$version.':'.$key};
+ }
+ }
+ if (keys(%modified)) {
+ if (&Apache::lonnet::putstore($env{'request.course.id'},$symb,$version,\%modified,
+ $domain,$stuname,$tolog) eq 'ok') {
+ $numchanged ++;
+ }
+ }
+ }
+ return $numchanged;
}
sub check_and_remove_from_queue {
@@ -3010,7 +3305,7 @@ sub handback_files {
&Apache::lonnet::dirlist($portfolio_root.$portfolio_path,
$domain,$stuname,$getpropath);
my $version = &get_next_version($answer_name,$answer_ext,$dir_list);
- # fix file name
+ # fix filename
my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/);
my $result=&Apache::lonnet::finishuserfileupload($stuname,$domain,
$newflg.'_'.$part_resp.'_returndoc'.$counter,
@@ -3030,7 +3325,7 @@ sub handback_files {
$file_msg.=''.$save_file_name." ";
}
- $request->print(' '.&mt('[_1] will be the uploaded file name [_2]',''.$fname.' ',''.$env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$counter}.' '));
+ $request->print(' '.&mt('[_1] will be the uploaded filename [_2]',''.$fname.' ',''.$env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$counter}.' '));
}
}
}
@@ -3436,7 +3731,7 @@ sub viewgrades {
&Apache::lonnet::clear_EXT_cache_status();
my $result=''.&mt('Manual Grading').' ';
- $result.=''.&mt('Current Resource: [_1]',$env{'form.probTitle'}).' '."\n";
+ $result.=''.&mt('Current Resource').': '.$env{'form.probTitle'}.' '."\n";
#view individual student submission form - called using Javascript viewOneStudent
$result.=&jscriptNform($symb);
@@ -3499,8 +3794,9 @@ sub viewgrades {
$partid.'" size="4" '.'onchange="javascript:writePoint(\''.
$partid.'\','.$weight{$partid}.',\'textval\')" /> /'.
$weight{$partid}.' '.&mt('(problem weight)').''."\n";
- $line.= ''.&mt('Grade Status').': '.&mt('Grade Status').': '.
+ ' '.
' '.
''.&mt('excused').' '.
@@ -3547,8 +3843,8 @@ sub viewgrades {
my $display_part=&get_display_part($partid,$symb);
if ($display =~ /^Partial Credit Factor/) {
$result.=''.
- &mt('Score Part: [_1] (weight = [_2])',
- $display_part,$weight{$partid}).' '."\n";
+ &mt('Score Part: [_1][_2](weight = [_3])',
+ $display_part,' ',$weight{$partid}).''."\n";
next;
} else {
@@ -3669,11 +3965,11 @@ sub viewstudentgrade {
sub editgrades {
my ($request) = @_;
- my $symb=&get_symb($request);
+ my ($symb)=&get_symb($request);
my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
my $title=''.&mt('Current Grade Status').' ';
- $title.=''.&mt('Current Resource: [_1]',$env{'form.probTitle'}).' '."\n";
- $title.=''.&mt('Section: [_1]',$section_display).' '."\n";
+ $title.=''.&mt('Current Resource').': '.$env{'form.probTitle'}.' '."\n";
+ $title.=''.&mt('Section:').' '.$section_display.' '."\n";
my $result= &Apache::loncommon::start_data_table().
&Apache::loncommon::start_data_table_header_row().
@@ -4058,11 +4354,12 @@ sub csvupload_fields {
sub csvuploadmap_footer {
my ($request,$i,$keyfields) =@_;
+ my $buttontext = &mt('Assign Grades');
$request->print(<
-
+
ENDPICK
}
@@ -4206,7 +4503,7 @@ ENDPICK
}
# FIXME do a check for any duplicated user ids...
# FIXME do a check for any invalid user ids?...
- $request->print('
+ $request->print('
'."\n");
$request->print(&show_grading_menu_form($symb));
return '';
@@ -4540,9 +4837,12 @@ sub displayPage {
&Apache::lonnet::clear_EXT_cache_status();
if (!&canview($usec)) {
- $request->print(''.&mt('Unable to view requested student. ([_1])',$env{'form.student'}).' ');
- $request->print(&show_grading_menu_form($symb));
- return;
+ $request->print(''.
+ &mt('Unable to view requested student. ([_1])',
+ $env{'form.student'}).
+ ' ');
+ $request->print(&show_grading_menu_form($symb));
+ return;
}
my $result=' '.$env{'form.title'}.' ';
$result.=' '.&mt('Student: [_1]',&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom)).
@@ -4614,8 +4914,8 @@ sub displayPage {
&Apache::loncommon::start_data_table_row().
''.$prob.
(scalar(@{$parts}) == 1 ? ''
- : ' ('.&mt('[_1]parts)',
- scalar(@{$parts}).' ')
+ : ' ('.&mt('[_1]parts',
+ scalar(@{$parts}).' ').')'
).
' ';
$studentTable.='';
@@ -4656,9 +4956,11 @@ sub displayPage {
}
} elsif ($env{'form.lastSub'} eq 'all') {
my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
+ my $identifier = (&canmodify($usec)? $prob : '');
$studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom,
$env{'request.course.id'},
- '','.submission');
+ '','.submission',undef,
+ $usec,$identifier);
}
if (&canmodify($usec)) {
@@ -4697,6 +4999,7 @@ sub displaySubByDates {
&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();
@@ -4717,7 +5020,9 @@ sub displaySubByDates {
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.=&Apache::loncommon::start_data_table_row().
@@ -4725,6 +5030,9 @@ sub displaySubByDates {
if ($isCODE) {
$studentTable.=''.$record->{$version.':resource.CODE'}.' ';
}
+ if ($isTask) {
+ $studentTable.=''.$interaction.' ';
+ }
my @versionKeys = split(/\:/,$$record{$version.':keys'});
my @displaySub = ();
foreach my $partid (@{$parts}) {
@@ -4744,7 +5052,7 @@ sub displaySubByDates {
my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
: ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
- $displaySub[0].='';
$displaySub[0].=''.&mt('Part: [_1]',$display_part).' '
.' '
.'('.&mt('Response ID: [_1]',$responseId).')'
@@ -4868,7 +5176,7 @@ sub updateGradeByPage {
$iterator->next(); # skip the first BEGIN_MAP
my $curRes = $iterator->next(); # for "current resource"
- my ($depth,$question,$prob,$changeflag)= (1,1,1,0);
+ my ($depth,$question,$prob,$changeflag,$hideflag)= (1,1,1,0,0);
while ($depth > 0) {
if($curRes == $iterator->BEGIN_MAP) { $depth++; }
if($curRes == $iterator->END_MAP) { $depth--; }
@@ -4889,6 +5197,12 @@ sub updateGradeByPage {
my @displayPts=();
my %aggregate = ();
my $aggregateflag = 0;
+ if ($env{'form.HIDE'.$prob}) {
+ my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
+ my ($version,$parts) = split(/:/,$env{'form.HIDE'.$prob},2);
+ my $numchgs = &makehidden($version,$parts,\%record,$symbx,$udom,$uname,1);
+ $hideflag += $numchgs;
+ }
foreach my $partid (@{$parts}) {
my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};
my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};
@@ -4980,8 +5294,11 @@ sub updateGradeByPage {
$studentTable.=&show_grading_menu_form($env{'form.symb'});
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);
+ $changeflag).' ');
+ my $hidemsg=($hideflag == 0 ? '' :
+ &mt('Submissions were marked "hidden" for [quant,_1,transaction].',
+ $hideflag).' ');
+ $request->print($hidemsg.$grademsg.$studentTable);
return '';
}
@@ -5016,7 +5333,7 @@ like.
Next each scanline is checked for any errors of either 'missing
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,
+have no more than one letter picked), invalid or duplicated CODE,
invalid student/employee ID
If the CODE option is used that determines the randomization of the
@@ -5106,6 +5423,11 @@ my %subdivided_bubble_lines; # no.
my %responsetype_per_response; # responsetype for each response
+my %masterseq_id_responsenum; # src_id (e.g., 12.3_0.11 etc.) for each
+ # numbered response. Needed when randomorder
+ # or randompick are in use. Key is ID, value
+ # is response number.
+
# Save and restore the bubble lines array to the form env.
@@ -5119,12 +5441,17 @@ sub save_bubble_lines {
$env{"form.scantron.responsetype.$line"} =
$responsetype_per_response{$line};
}
+ foreach my $resid (keys(%masterseq_id_responsenum)) {
+ my $line = $masterseq_id_responsenum{$resid};
+ $env{"form.scantron.residpart.$line"} = $resid;
+ }
}
sub restore_bubble_lines {
my $line = 0;
%bubble_lines_per_response = ();
+ %masterseq_id_responsenum = ();
while ($env{"form.scantron.bubblelines.$line"}) {
my $value = $env{"form.scantron.bubblelines.$line"};
$bubble_lines_per_response{$line} = $value;
@@ -5134,28 +5461,12 @@ sub restore_bubble_lines {
$env{"form.scantron.sub_bubblelines.$line"};
$responsetype_per_response{$line} =
$env{"form.scantron.responsetype.$line"};
+ my $id = $env{"form.scantron.residpart.$line"};
+ $masterseq_id_responsenum{$id} = $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
=item scantron_filenames
@@ -5485,7 +5796,7 @@ sub scantron_selectphase {
&Apache::lonpickcode::code_list($r,2);
- $r->print('
'.&mt('Sequence to be Graded:').' '.$title.'
@@ -6409,8 +6840,8 @@ sub scantron_warning_screen {
'.$CODElist.$lastbubblepoints.'
- '.&mt('If this information is correct, please click on \'[_1]\'.',&mt($button_text)).'
- '.&mt('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 click the 'Grading Menu' button to start over.").'
');
@@ -6490,9 +6921,11 @@ SCANTRONFORM
' '."\n";
$chunk .=
' '."\n";
+ $chunk .=
+ ' '."\n";
$result .= $chunk;
$line++;
- }
+ }
return $result;
}
@@ -6514,7 +6947,7 @@ sub scantron_validate_file {
if (!$symb) {return '';}
my $default_form_data=&defaultFormData($symb);
- # do the detection of only doing skipped records first befroe we delete
+ # do the detection of only doing skipped records first before we delete
# them when doing the corrections reset
if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') {
&reset_skipping_status();
@@ -6565,6 +6998,7 @@ sub scantron_validate_file {
while (!$stop && $currentphase < scalar(@validate_phases)) {
$r->print(&mt('Validating '.$validate_phases[$currentphase]).' ');
$r->rflush();
+
my $which="scantron_validate_".$validate_phases[$currentphase];
{
no strict 'refs';
@@ -6973,7 +7407,12 @@ sub scantron_validate_sequence {
my @resources=
$navmap->retrieveResources($map,\&scantron_filter_not_exam,1,0);
if (@resources) {
- $r->print("".&mt('Some resources in the sequence currently are not set to exam mode. Grading these resources currently may not work correctly.')."
");
+ $r->print(''
+ .&mt('Some resources in the sequence currently are not set to'
+ .' exam mode. Grading these resources currently may not'
+ .' work correctly.')
+ .'
'
+ );
return (1,$currentphase);
}
}
@@ -7054,7 +7493,8 @@ sub scantron_validate_ID {
sub scantron_get_correction {
- my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;
+ my ($r,$i,$scan_record,$scan_config,$line,$error,$arg,
+ $randomorder,$randompick,$respnumlookup,$startline)=@_;
#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
@@ -7104,7 +7544,7 @@ sub scantron_get_correction {
$r->print(&Apache::loncommon::selectstudent_link('scantronupload',
'scantron_username','scantron_domain'));
$r->print(": ");
- $r->print("\n@".
+ $r->print("\n:\n".
&Apache::loncommon::select_dom_form($env{'request.role.domain'},'scantron_domain'));
$r->print('');
@@ -7190,7 +7630,8 @@ ENDSCRIPT
# 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);
+ my $line_list = &questions_to_line_list($arg,$randomorder,$randompick,
+ $respnumlookup,$startline);
$r->print(' ');
@@ -7198,7 +7639,9 @@ ENDSCRIPT
$r->print("".&mt("Please indicate which bubble should be used for grading")."
");
foreach my $question (@{$arg}) {
my @linenums = &prompt_for_corrections($r,$question,$scan_config,
- $scan_record, $error);
+ $scan_record, $error,
+ $randomorder,$randompick,
+ $respnumlookup,$startline);
push(@lines_to_correct,@linenums);
}
$r->print(&verify_bubbles_checked(@lines_to_correct));
@@ -7212,13 +7655,16 @@ ENDSCRIPT
# a list of question numbers. Therefore:
#
- my $line_list = &questions_to_line_list($arg);
+ my $line_list = &questions_to_line_list($arg,$randomorder,$randompick,
+ $respnumlookup,$startline);
$r->print(' ');
foreach my $question (@{$arg}) {
my @linenums = &prompt_for_corrections($r,$question,$scan_config,
- $scan_record, $error);
+ $scan_record, $error,
+ $randomorder,$randompick,
+ $respnumlookup,$startline);
push(@lines_to_correct,@linenums);
}
$r->print(&verify_bubbles_checked(@lines_to_correct));
@@ -7271,12 +7717,20 @@ used to fill in the scantron_questions f
Arguments:
questions - Reference to an array of questions.
+ randomorder - True if randomorder in use.
+ randompick - True if randompick in use.
+ respnumlookup - Reference to HASH mapping question numbers in bubble lines
+ for current line to question number used for same question
+ in "Master Seqence" (as seen by Course Coordinator).
+ startline - Reference to hash where key is question number (0 is first)
+ and key is number of first bubble line for current student
+ or code-based randompick and/or randomorder.
=cut
sub questions_to_line_list {
- my ($questions) = @_;
+ my ($questions,$randomorder,$randompick,$respnumlookup,$startline) = @_;
my @lines;
foreach my $item (@{$questions}) {
@@ -7285,8 +7739,16 @@ sub questions_to_line_list {
if ($item =~ /^(\d+)\.(\d+)$/) {
$question = $1;
my $subquestion = $2;
- $first = $first_bubble_line{$question-1} + 1;
- my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
+ my $responsenum = $question-1;
+ if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
+ $responsenum = $respnumlookup->{$question-1};
+ if (ref($startline) eq 'HASH') {
+ $first = $startline->{$question-1} + 1;
+ }
+ } else {
+ $first = $first_bubble_line{$responsenum} + 1;
+ }
+ my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
my $subcount = 1;
while ($subcount<$subquestion) {
$first += $subans[$subcount-1];
@@ -7294,8 +7756,16 @@ sub questions_to_line_list {
}
$count = $subans[$subquestion-1];
} else {
- $first = $first_bubble_line{$question-1} + 1;
- $count = $bubble_lines_per_response{$question-1};
+ my $responsenum = $question-1;
+ if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
+ $responsenum = $respnumlookup->{$question-1};
+ if (ref($startline) eq 'HASH') {
+ $first = $startline->{$question-1} + 1;
+ }
+ } else {
+ $first = $first_bubble_line{$responsenum} + 1;
+ }
+ $count = $bubble_lines_per_response{$responsenum};
}
$last = $first+$count-1;
push(@lines, ($first..$last));
@@ -7317,6 +7787,14 @@ for multi and missing bubble cases).
$scan_config - The scantron file configuration hash.
$scan_record - Reference to the hash that has the the parsed scanlines.
$error - Type of error
+ $randomorder - True if randomorder in use.
+ $randompick - True if randompick in use.
+ $respnumlookup - Reference to HASH mapping question numbers in bubble lines
+ for current line to question number used for same question
+ in "Master Seqence" (as seen by Course Coordinator).
+ $startline - Reference to hash where key is question number (0 is first)
+ and value is number of first bubble line for current student
+ or code-based randompick and/or randomorder.
Implicit inputs:
%bubble_lines_per_response - Starting line numbers for each question.
@@ -7341,15 +7819,26 @@ for multi and missing bubble cases).
=cut
sub prompt_for_corrections {
- my ($r, $question, $scan_config, $scan_record, $error) = @_;
+ my ($r, $question, $scan_config, $scan_record, $error, $randomorder,
+ $randompick, $respnumlookup, $startline) = @_;
my ($current_line,$lines);
my @linenums;
my $questionnum = $question;
+ my ($first,$responsenum);
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});
+ if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
+ $responsenum = $respnumlookup->{$question-1};
+ if (ref($startline) eq 'HASH') {
+ $first = $startline->{$question-1};
+ }
+ } else {
+ $responsenum = $question-1;
+ $first = $first_bubble_line{$responsenum};
+ }
+ $current_line = $first + 1 ;
+ my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
my $subcount = 1;
while ($subcount<$subquestion) {
$current_line += $subans[$subcount-1];
@@ -7357,25 +7846,34 @@ sub prompt_for_corrections {
}
$lines = $subans[$subquestion-1];
} else {
- $current_line = $first_bubble_line{$question-1} + 1 ;
- $lines = $bubble_lines_per_response{$question-1};
+ if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
+ $responsenum = $respnumlookup->{$question-1};
+ if (ref($startline) eq 'HASH') {
+ $first = $startline->{$question-1};
+ }
+ } else {
+ $responsenum = $question-1;
+ $first = $first_bubble_line{$responsenum};
+ }
+ $current_line = $first + 1;
+ $lines = $bubble_lines_per_response{$responsenum};
}
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'.").' ');
+ if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
+ ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
+ ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
+ ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
+ ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
+ ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
+ $r->print(&mt("Although this particular question type requires handgrading, the instructions for this question in the bubblesheet 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,
+ &scantron_bubble_selector($r,$scan_config,$current_line,
$questionnum,$error,split('', $selected));
push(@linenums,$current_line);
$current_line++;
@@ -7629,11 +8127,42 @@ sub scantron_validate_doublebubble {
#get student info
my $classlist=&Apache::loncoursedata::get_classlist();
my %idmap=&username_to_idmap($classlist);
+ my (undef,undef,$sequence)=
+ &Apache::lonnet::decode_symb($env{'form.selectpage'});
#get scantron line setup
my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
my ($scanlines,$scan_data)=&scantron_getfile();
+
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ unless (ref($navmap)) {
+ $r->print(&navmap_errormsg());
+ return(1,$currentphase);
+ }
+ my $map=$navmap->getResourceByUrl($sequence);
+ my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
+ my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
+ %grader_randomlists_by_symb,%orderedforcode,%respnumlookup,%startline);
+ my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
+
my $nav_error;
+ if (ref($map)) {
+ $randomorder = $map->randomorder();
+ $randompick = $map->randompick();
+ if ($randomorder || $randompick) {
+ $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
+ if ($nav_error) {
+ $r->print(&navmap_errormsg());
+ return(1,$currentphase);
+ }
+ &graders_resources_pass(\@resources,\%grader_partids_by_symb,
+ \%grader_randomlists_by_symb,$bubbles_per_row);
+ }
+ } else {
+ $r->print(&navmap_errormsg());
+ return(1,$currentphase);
+ }
+
&scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the bubble line array.
if ($nav_error) {
$r->print(&navmap_errormsg());
@@ -7644,11 +8173,15 @@ sub scantron_validate_doublebubble {
my $line=&scantron_get_line($scanlines,$scan_data,$i);
if ($line=~/^[\s\cz]*$/) { next; }
my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
- $scan_data);
+ $scan_data,undef,\%idmap,$randomorder,
+ $randompick,$sequence,\@master_seq,
+ \%symb_to_resource,\%grader_partids_by_symb,
+ \%orderedforcode,\%respnumlookup,\%startline);
if (!defined($$scan_record{'scantron.doubleerror'})) { next; }
&scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line,
'doublebubble',
- $$scan_record{'scantron.doubleerror'});
+ $$scan_record{'scantron.doubleerror'},
+ $randomorder,$randompick,\%respnumlookup,\%startline);
return (1,$currentphase);
}
return (0,$currentphase+1);
@@ -7687,12 +8220,14 @@ sub scantron_get_maxbubble {
%first_bubble_line = ();
%subdivided_bubble_lines = ();
%responsetype_per_response = ();
+ %masterseq_id_responsenum = ();
my $response_number = 0;
my $bubble_line = 0;
foreach my $resource (@resources) {
+ my $resid = $resource->id();
my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,
- $udom,$bubbles_per_row);
+ $udom,undef,$bubbles_per_row);
if ((ref($analysis) eq 'HASH') && (ref($parts) eq 'ARRAY')) {
foreach my $part_id (@{$parts}) {
my $lines;
@@ -7741,6 +8276,7 @@ sub scantron_get_maxbubble {
$bubble_lines_per_response{$response_number} = $lines;
$responsetype_per_response{$response_number} =
$analysis->{$part_id.'.type'};
+ $masterseq_id_responsenum{$resid.'_'.$part_id} = $response_number;
$response_number++;
$bubble_line += $lines;
@@ -7773,21 +8309,59 @@ sub scantron_validate_missingbubbles {
#get student info
my $classlist=&Apache::loncoursedata::get_classlist();
my %idmap=&username_to_idmap($classlist);
+ my (undef,undef,$sequence)=
+ &Apache::lonnet::decode_symb($env{'form.selectpage'});
#get scantron line setup
my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
my ($scanlines,$scan_data)=&scantron_getfile();
+
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ unless (ref($navmap)) {
+ $r->print(&navmap_errormsg());
+ return(1,$currentphase);
+ }
+
+ my $map=$navmap->getResourceByUrl($sequence);
+ my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
+ my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
+ %grader_randomlists_by_symb,%orderedforcode,%respnumlookup,%startline);
+ my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
+
my $nav_error;
+ if (ref($map)) {
+ $randomorder = $map->randomorder();
+ $randompick = $map->randompick();
+ if ($randomorder || $randompick) {
+ $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
+ if ($nav_error) {
+ $r->print(&navmap_errormsg());
+ return(1,$currentphase);
+ }
+ &graders_resources_pass(\@resources,\%grader_partids_by_symb,
+ \%grader_randomlists_by_symb,$bubbles_per_row);
+ }
+ } else {
+ $r->print(&navmap_errormsg());
+ return(1,$currentphase);
+ }
+
+
my $max_bubble=&scantron_get_maxbubble(\$nav_error,\%scantron_config);
if ($nav_error) {
+ $r->print(&navmap_errormsg());
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);
if ($line=~/^[\s\cz]*$/) { next; }
- my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
- $scan_data);
+ my $scan_record =
+ &scantron_parse_scanline($line,$i,\%scantron_config,$scan_data,undef,\%idmap,
+ $randomorder,$randompick,$sequence,\@master_seq,
+ \%symb_to_resource,\%grader_partids_by_symb,
+ \%orderedforcode,\%respnumlookup,\%startline);
if (!defined($$scan_record{'scantron.missingerror'})) { next; }
my @to_correct;
@@ -7796,28 +8370,45 @@ sub scantron_validate_missingbubbles {
foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {
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;
+ my $question = $1;
+ my $subquestion = $2;
+ my ($first,$responsenum);
+ if ($randomorder || $randompick) {
+ $responsenum = $respnumlookup{$question-1};
+ $first = $startline{$question-1};
+ } else {
+ $responsenum = $question-1;
+ $first = $first_bubble_line{$responsenum};
+ }
+ if (!defined($first)) { next; }
+ my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
+ 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};
+ my ($first,$responsenum);
+ if ($randomorder || $randompick) {
+ $responsenum = $respnumlookup{$missing-1};
+ $first = $startline{$missing-1};
+ } else {
+ $responsenum = $missing-1;
+ $first = $first_bubble_line{$responsenum};
+ }
+ if (!defined($first)) { next; }
+ $lastbubble = $first + $bubble_lines_per_response{$responsenum};
}
if ($lastbubble > $max_bubble) { next; }
push(@to_correct,$missing);
}
if (@to_correct) {
&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
- $line,'missingbubble',\@to_correct);
+ $line,'missingbubble',\@to_correct,
+ $randomorder,$randompick,\%respnumlookup,
+ \%startline);
return (1,$currentphase);
}
@@ -7856,7 +8447,7 @@ sub hand_bubble_option {
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
';
+ ' '.&mt('0 points').' ';
}
return;
}
@@ -7872,6 +8463,7 @@ sub scantron_process_students {
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);
@@ -7879,37 +8471,28 @@ sub scantron_process_students {
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;
- 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) {
+ my $map=$navmap->getResourceByUrl($sequence);
+ my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
+ %grader_randomlists_by_symb);
+ if (ref($map)) {
+ $randomorder = $map->randomorder();
+ $randompick = $map->randompick();
+ } else {
$r->print(&navmap_errormsg());
return '';
}
+ my $nav_error;
+ my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
+ if ($randomorder || $randompick) {
+ $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
+ if ($nav_error) {
+ $r->print(&navmap_errormsg());
+ return '';
+ }
+ }
+ &graders_resources_pass(\@resources,\%grader_partids_by_symb,
+ \%grader_randomlists_by_symb,$bubbles_per_row);
my ($uname,$udom);
my $result= <print(' ');
@@ -7934,7 +8515,6 @@ SCANTRONFORM
my $i=-1;
my $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());
@@ -7954,6 +8534,7 @@ SCANTRONFORM
my %lettdig = &letter_to_digits();
my $numletts = scalar(keys(%lettdig));
+ my %orderedforcode;
while ($i<$scanlines->{'count'}) {
($uname,$udom)=('','');
@@ -7965,8 +8546,15 @@ SCANTRONFORM
'last student');
}
$started=1;
+ my %respnumlookup = ();
+ my %startline = ();
+ my $total;
my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
- $scan_data);
+ $scan_data,undef,\%idmap,$randomorder,
+ $randompick,$sequence,\@master_seq,
+ \%symb_to_resource,\%grader_partids_by_symb,
+ \%orderedforcode,\%respnumlookup,\%startline,
+ \$total);
unless ($uname=&scantron_find_student($scan_record,$scan_data,
\%idmap,$i)) {
&scantron_add_delay(\@delayqueue,$line,
@@ -7978,10 +8566,26 @@ SCANTRONFORM
'Student '.$uname.' has multiple sheets',2);
next;
}
+ my $usec = $classlist->{$uname}->[&Apache::loncoursedata::CL_SECTION];
+ my $user = $uname.':'.$usec;
($uname,$udom)=split(/:/,$uname);
+ my $scancode;
+ if ((exists($scan_record->{'scantron.CODE'})) &&
+ (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
+ $scancode = $scan_record->{'scantron.CODE'};
+ } else {
+ $scancode = '';
+ }
+
+ my @mapresources = @resources;
+ if ($randomorder || $randompick) {
+ @mapresources =
+ &users_order($user,$scancode,$sequence,\@master_seq,\%symb_to_resource,
+ \%orderedforcode);
+ }
my (%partids_by_symb,$res_error);
- foreach my $resource (@resources) {
+ foreach my $resource (@mapresources) {
my $ressymb;
if (ref($resource)) {
$ressymb = $resource->symb();
@@ -8013,17 +8617,11 @@ SCANTRONFORM
&scantron_putfile($scanlines,$scan_data);
}
- 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') {
+ \@mapresources,\%partids_by_symb,
+ $bubbles_per_row,$randomorder,$randompick,
+ \%respnumlookup,\%startline)
+ eq 'ssi_error') {
$ssi_error = 0; # So end of handler error message does not trigger.
$r->print("");
&ssi_print_error($r);
@@ -8032,27 +8630,43 @@ SCANTRONFORM
return ''; # Why return ''? Beats me.
}
+ if (($scancode) && ($randomorder || $randompick)) {
+ my $parmresult =
+ &Apache::lonparmset::storeparm_by_symb($symb,
+ '0_examcode',2,$scancode,
+ 'string_examcode',$uname,
+ $udom);
+ }
$completedstudents{$uname}={'line'=>$line};
if ($env{'form.verifyrecord'}) {
my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
+ if ($randompick) {
+ if ($total) {
+ $lastpos = $total*$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) {
+ foreach my $resource (@mapresources) {
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);
+ \%scantron_config,\%lettdig,$numletts,$randomorder,
+ $randompick,\%respnumlookup,\%startline);
$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') {
+ \@mapresources,\%partids_by_symb,
+ $bubbles_per_row,$randomorder,$randompick,
+ \%respnumlookup,\%startline)
+ eq 'ssi_error') {
$ssi_error = 0; # So end of handler error message does not trigger.
$r->print("");
&ssi_print_error($r);
@@ -8063,12 +8677,14 @@ SCANTRONFORM
}
$counter = -1;
$studentrecord = '';
- foreach my $resource (@resources) {
+ foreach my $resource (@mapresources) {
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);
+ \%scantron_config,\%lettdig,$numletts,
+ $randomorder,$randompick,\%respnumlookup,
+ \%startline);
$studentrecord .= $recording;
}
if ($studentrecord ne $studentdata) {
@@ -8086,11 +8702,11 @@ SCANTRONFORM
&Apache::loncommon::end_data_table_header_row()."\n".
&Apache::loncommon::start_data_table_row().
''.&mt('Bubblesheet').' '.
- ''.$studentdata.' '.
+ ''.$studentdata.' '.
&Apache::loncommon::end_data_table_row().
&Apache::loncommon::start_data_table_row().
''.&mt('Stored submissions').' '.
- ''.$studentrecord.' '."\n".
+ ''.$studentrecord.' '."\n".
&Apache::loncommon::end_data_table_row().
&Apache::loncommon::end_data_table().'');
} else {
@@ -8117,7 +8733,8 @@ SCANTRONFORM
}
sub graders_resources_pass {
- my ($resources,$grader_partids_by_symb,$grader_randomlists_by_symb) = @_;
+ 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}) {
@@ -8138,8 +8755,67 @@ sub graders_resources_pass {
return;
}
+=pod
+
+=item users_order
+
+ Returns array of resources in current map, ordered based on either CODE,
+ if this is a CODEd exam, or based on student's identity if this is a
+ "NAMEd" exam.
+
+ Should be used when randomorder and/or randompick applied when the
+ corresponding exam was printed, prior to students completing bubblesheets
+ for the version of the exam the student received.
+
+=cut
+
+sub users_order {
+ my ($user,$scancode,$mapurl,$master_seq,$symb_to_resource,$orderedforcode) = @_;
+ my @mapresources;
+ unless ((ref($master_seq) eq 'ARRAY') && (ref($symb_to_resource) eq 'HASH')) {
+ return @mapresources;
+ }
+ if ($scancode) {
+ if ((ref($orderedforcode) eq 'HASH') && (ref($orderedforcode->{$scancode}) eq 'ARRAY')) {
+ @mapresources = @{$orderedforcode->{$scancode}};
+ } else {
+ $env{'form.CODE'} = $scancode;
+ my $actual_seq =
+ &Apache::lonprintout::master_seq_to_person_seq($mapurl,
+ $master_seq,
+ $user,$scancode,1);
+ if (ref($actual_seq) eq 'ARRAY') {
+ @mapresources = map { $symb_to_resource->{$_}; } @{$actual_seq};
+ if (ref($orderedforcode) eq 'HASH') {
+ if (@mapresources > 0) {
+ $orderedforcode->{$scancode} = \@mapresources;
+ }
+ }
+ }
+ delete($env{'form.CODE'});
+ }
+ } else {
+ my $actual_seq =
+ &Apache::lonprintout::master_seq_to_person_seq($mapurl,
+ $master_seq,
+ $user,undef,1);
+ if (ref($actual_seq) eq 'ARRAY') {
+ @mapresources =
+ map { $symb_to_resource->{$_}; } @{$actual_seq};
+ }
+ }
+ return @mapresources;
+}
+
sub grade_student_bubbles {
- my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts,$bubbles_per_row) = @_;
+ my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts,$bubbles_per_row,
+ $randomorder,$randompick,$respnumlookup,$startline) = @_;
+ my $uselookup = 0;
+ if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH') &&
+ (ref($startline) eq 'HASH')) {
+ $uselookup = 1;
+ }
+
if (ref($resources) eq 'ARRAY') {
my $count = 0;
foreach my $resource (@{$resources}) {
@@ -8161,8 +8837,12 @@ sub grade_student_bubbles {
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};
+ if ($uselookup) {
+ $form{'scantron_questnum_start.'.$part} = $startline->{$count} + 1;
+ } else {
+ $form{'scantron_questnum_start.'.$part} =
+ 1+$env{'form.scantron.first_bubble_line.'.$count};
+ }
$count++;
}
}
@@ -8184,8 +8864,9 @@ sub scantron_upload_scantron_data {
'domainid',
'coursename',$dom);
my $syllabuslink = ''.&mt('Syllabus').' '.
- (' 'x2).&mt('(shows course personnel)');
- my $default_form_data=&defaultFormData(&get_symb($r,1));
+ (' 'x2).&mt('(shows course personnel)');
+ my ($symb) = &get_symb($r,1);
+ 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('
@@ -8266,23 +8947,30 @@ sub scantron_upload_scantron_data_save {
}
my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
my $uploadedfile;
- $r->print(''.&mt("Uploading file to [_1]",$coursedata{'description'}).' ');
+ $r->print(''.&mt("Uploading file to [_1]",$coursedata{'description'}).'
');
if (length($env{'form.upfile'}) < 2) {
- $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'},'<>&"').' '));
+ $r->print(
+ &Apache::lonhtmlcommon::confirm_success(
+ &mt('The file: [_1] you attempted to upload contained no information. Please check that you entered the correct filename.',
+ ''.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').' '),1));
} else {
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.' '));
+ $r->print(
+ &Apache::lonhtmlcommon::confirm_success(&mt('Upload successful')).' '.
+ &mt('Uploaded [_1] bytes of data into location: [_2]',
+ (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('[_1]Error:[_2] An error ([_3]) occurred when attempting to upload the file, [_4]',
- '',' ',$result,
+ $r->print(
+ &Apache::lonhtmlcommon::confirm_success(&mt('Upload failed'),1).' '.
+ &mt('An error ([_1]) occurred when attempting to upload the file: [_2]',
+ $result,
''.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').' '));
}
}
@@ -8304,7 +8992,7 @@ sub validate_uploaded_scantron_file {
my $output;
if (@lines) {
my (%counts,$max_match_format);
- my ($max_match_count,$max_match_pct) = (0,0);
+ my ($found_match_count,$max_match_count,$max_match_pct) = (0,0,0);
my $classlist = &Apache::loncoursedata::get_classlist($cdom,$cname);
my %idmap = &username_to_idmap($classlist);
foreach my $key (keys(%idmap)) {
@@ -8347,6 +9035,7 @@ sub validate_uploaded_scantron_file {
if (($max_match_format eq '') || ($percent_match > $max_match_pct)) {
$max_match_pct = $percent_match;
$max_match_format = $key;
+ $found_match_count = $counts{$key}{'found'};
$max_match_count = $counts{$key}{'total'};
}
}
@@ -8365,17 +9054,40 @@ sub validate_uploaded_scantron_file {
}
}
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').' '.
- ' ';
+ $output .= ' ';
+ if ($found_match_count == $max_match_count) {
+ # 100% matching entries
+ $output .= &Apache::lonhtmlcommon::confirm_success(
+ &mt('Comparison of student IDs: [_1] matching ([quant,_2,entry,entries])',
+ ''.$showpct.' ',$found_match_count)).' '.
+ &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);
+ } else {
+ # Not all entries matching? -> Show warning and additional info
+ $output .=
+ &Apache::lonhtmlcommon::confirm_success(
+ &mt('Comparison of student IDs: [_1] matching ([_2]/[quant,_3,entry,entries])',
+ ''.$showpct.' ',$found_match_count,$max_match_count).' '.
+ &mt('Not all entries could be matched!'),1).' '.
+ &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 is 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').' ';
+ $output = ''.&mt('Uploaded file contained no data').'
';
}
return $output;
}
@@ -8390,17 +9102,18 @@ sub valid_file {
sub scantron_download_scantron_data {
my ($r)=@_;
- my $default_form_data=&defaultFormData(&get_symb($r,1));
+ my ($symb) = &get_symb($r,1);
+ 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('
- '.&mt('The requested file name was invalid.').'
+ '.&mt('The requested filename was invalid.').'
');
- $r->print(&show_grading_menu_form(&get_symb($r,1)));
+ $r->print(&show_grading_menu_form($symb));
return;
}
my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file;
@@ -8411,7 +9124,7 @@ sub scantron_download_scantron_data {
&Apache::lonnet::allowuploaded('/adm/grades',$skipped);
$r->print('
- '.&mt('[_1]Original[_2] file as uploaded by the scantron office.',
+ '.&mt('[_1]Original[_2] file as uploaded by bubblesheet scanning office.',
'',' ').'
@@ -8423,7 +9136,7 @@ sub scantron_download_scantron_data {
'',' ').'
');
- $r->print(&show_grading_menu_form(&get_symb($r,1)));
+ $r->print(&show_grading_menu_form($symb));
return '';
}
@@ -8441,6 +9154,7 @@ sub checkscantron_results {
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);
@@ -8450,10 +9164,20 @@ sub checkscantron_results {
return '';
}
my $map=$navmap->getResourceByUrl($sequence);
+ my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
+ %grader_randomlists_by_symb,%orderedforcode);
+ if (ref($map)) {
+ $randomorder=$map->randomorder();
+ $randompick=$map->randompick();
+ }
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 $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
+ if ($nav_error) {
+ $r->print(&navmap_errormsg());
+ return '';
+ }
+ &graders_resources_pass(\@resources,\%grader_partids_by_symb,
+ \%grader_randomlists_by_symb,$bubbles_per_row);
my ($uname,$udom);
my (%scandata,%lastname,%bylast);
$r->print('
@@ -8462,12 +9186,9 @@ sub checkscantron_results {
my @delayqueue;
my %completedstudents;
- my $count=&Apache::grades::get_todo_count($scanlines,$scan_data);
- my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Bubblesheet/Submissions Comparison Status',
- 'Progress of Bubblesheet Data/Submission Records Comparison',$count,
- 'inline',undef,'checkscantron');
+ my $count=&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());
@@ -8492,8 +9213,8 @@ sub checkscantron_results {
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)) {
+ unless ($uname=&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;
@@ -8506,13 +9227,40 @@ sub checkscantron_results {
my $pid = $scan_record->{'scantron.ID'};
$lastname{$pid} = $scan_record->{'scantron.LastName'};
push(@{$bylast{$lastname{$pid}}},$pid);
+ my $usec = $classlist->{$uname}->[&Apache::loncoursedata::CL_SECTION];
+ my $user = $uname.':'.$usec;
+ ($username,$domain)=split(/:/,$uname);
+
+ my $scancode;
+ if ((exists($scan_record->{'scantron.CODE'})) &&
+ (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
+ $scancode = $scan_record->{'scantron.CODE'};
+ } else {
+ $scancode = '';
+ }
+
+ my @mapresources = @resources;
my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
+ my %respnumlookup=();
+ my %startline=();
+ if ($randomorder || $randompick) {
+ @mapresources =
+ &users_order($user,$scancode,$sequence,\@master_seq,\%symb_to_resource,
+ \%orderedforcode);
+ my $total = &get_respnum_lookups($sequence,$scan_data,\%idmap,$line,
+ $scan_record,\@master_seq,\%symb_to_resource,
+ \%grader_partids_by_symb,\%orderedforcode,
+ \%respnumlookup,\%startline);
+ if ($randompick && $total) {
+ $lastpos = $total*$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) {
+ foreach my $resource (@mapresources) {
my $parts;
my $ressymb = $resource->symb();
if ((exists($grader_randomlists_by_symb{$ressymb})) ||
@@ -8527,7 +9275,9 @@ sub checkscantron_results {
($counter,my $recording) =
&verify_scantron_grading($resource,$domain,$username,$cid,$counter,
$scandata{$pid},$parts,
- \%scantron_config,\%lettdig,$numletts);
+ \%scantron_config,\%lettdig,$numletts,
+ $randomorder,$randompick,
+ \%respnumlookup,\%startline);
$record{$pid} .= $recording;
}
}
@@ -8550,14 +9300,14 @@ sub checkscantron_results {
''.&mt('Bubblesheet').' '.$showscandata.' '.$last.' '.$pid.' '."\n".
' '."\n".
''."\n".
-'Submissions '.$showrecord.' '."\n";
+''.&mt('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".
+''.&mt('Submissions').' '.$record{$pid}.' '."\n".
' '."\n";
$failed ++;
}
@@ -8573,7 +9323,11 @@ sub checkscantron_results {
$env{'form.scantron_maxbubble'}).
''
);
- $r->print(''.&mt('Exact matches for [quant,_1,student] .',$passed).' '.&mt('Discrepancies detected for [quant,_1,student] .',$failed).'
');
+ $r->print(''
+ .&mt('Exact matches for [_1][quant,_2,student][_3].','',$passed,' ')
+ .' '
+ .&mt('Discrepancies detected for [_1][quant,_2,student][_3].','',$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".
@@ -8599,7 +9353,8 @@ sub checkscantron_results {
sub verify_scantron_grading {
my ($resource,$domain,$username,$cid,$counter,$scandata,$partids,
- $scantron_config,$lettdig,$numletts) = @_;
+ $scantron_config,$lettdig,$numletts,$randomorder,$randompick,
+ $respnumlookup,$startline) = @_;
my ($record,%expected,%startpos);
return ($counter,$record) if (!ref($resource));
return ($counter,$record) if (!$resource->is_problem());
@@ -8608,15 +9363,21 @@ sub verify_scantron_grading {
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"});
+ my $respnum = $counter;
+ if ($randomorder || $randompick) {
+ $respnum = $respnumlookup->{$counter};
+ $startpos{$part_id} = $startline->{$counter} + 1;
+ } else {
+ $startpos{$part_id} = $env{"form.scantron.first_bubble_line.$counter"};
+ }
+ if ($env{"form.scantron.sub_bubblelines.$respnum"}) {
+ my @sub_lines = split(/,/,$env{"form.scantron.sub_bubblelines.$respnum"});
foreach my $item (@sub_lines) {
$expected{$part_id} += $item;
}
} else {
- $expected{$part_id} = $env{"form.scantron.bubblelines.$counter"};
+ $expected{$part_id} = $env{"form.scantron.bubblelines.$respnum"};
}
- $startpos{$part_id} = $env{"form.scantron.first_bubble_line.$counter"};
}
if ($symb) {
my %recorded;
@@ -8712,7 +9473,7 @@ sub verify_scantron_grading {
return ($counter,$record);
}
-sub letter_to_digits {
+sub letter_to_digits {
my %lettdig = (
A => 1,
B => 2,
@@ -8759,6 +9520,13 @@ sub savedState {
return \%savedState;
}
+#--- Href with symb and command ---
+
+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);
@@ -9066,6 +9834,21 @@ sub init_perm {
}
}
+sub init_old_essays {
+ my ($symb,$apath,$adom,$aname) = @_;
+ if ($symb ne '') {
+ my %essays = &Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
+ if (keys(%essays) > 0) {
+ $old_essays{$symb} = \%essays;
+ }
+ }
+ return;
+}
+
+sub reset_old_essays {
+ undef(%old_essays);
+}
+
sub gather_clicker_ids {
my %clicker_ids;
@@ -9167,7 +9950,8 @@ sub process_clicker {
my $pincorrect=&mt("Percentage points for incorrect solution");
my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',
{'iclicker' => 'i>clicker',
- 'interwrite' => 'interwrite PRS'});
+ 'interwrite' => 'interwrite PRS',
+ 'turning' => 'Turning Technologies'});
$symb = &Apache::lonenc::check_encrypt($symb);
$result.=<
@@ -9296,16 +10080,18 @@ sub process_clicker_file {
$number++;
}
$result.="\n";
- if ($number==0) {
- $result.=''.&mt('No IDs found to determine correct answer').' ';
- return $result.&show_grading_menu_form($symb);
- }
+ if ($number==0) {
+ $result .=
+ &Apache::lonhtmlcommon::confirm_success(
+ &mt('No IDs found to determine correct answer'),1);
+ return $result,.&show_grading_menu_form($symb);
+ }
}
if (length($env{'form.upfile'}) < 2) {
- $result.=&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'},'<>&"').' ');
+ $result .=
+ &Apache::lonhtmlcommon::confirm_success(
+ &mt('The file: [_1] you attempted to upload contained no information. Please check that you entered the correct filename.',
+ ''.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').' '),1);
return $result.&show_grading_menu_form($symb);
}
@@ -9340,6 +10126,9 @@ 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 correct and [_2] percent for incorrect responses',
@@ -9394,7 +10183,7 @@ ENDHEADER
&mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);
if (($env{'form.gradingmechanism'} ne 'attendance') && ($env{'form.gradingmechanism'} ne 'given')) {
if ($correct_count==0) {
- $errormsg.="Found no correct answers answers for grading!";
+ $errormsg.="Found no correct answers for grading!";
} elsif ($correct_count>1) {
$result.=''.&mt("Found [_1] entries for grading!",$correct_count).' ';
}
@@ -9471,6 +10260,31 @@ sub interwrite_eval {
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);
@@ -9544,7 +10358,7 @@ sub assign_clicker_grades {
if ($user) {
if ($users{$user}) {
$result.=''.
- &mt("More than one entry found for [_1] !",$user).
+ &mt('More than one entry found for [_1]!',''.$user.' ').
' ';
}
$users{$user}=1;
@@ -9612,6 +10426,19 @@ sub navmap_errormsg {
'';
}
+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 {
+ $r->print(&Apache::loncommon::start_page('Grading',$js,
+ {'bread_crumbs' => $crumbs}));
+ }
+ unless ($nodisplayflag) {
+ $r->print(&Apache::lonhtmlcommon::resource_info_box($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp));
+ }
+}
+
sub handler {
my $request=$_[0];
&reset_caches();
@@ -9633,7 +10460,7 @@ sub handler {
$ssi_error = 0;
my $brcrum = [{href=>"/adm/grades",text=>"Grading"}];
my $start_page = &Apache::loncommon::start_page('Grading',undef,
- {'bread_crumbs' => $brcrum});
+ {'bread_crumbs' => $brcrum});
if ($symb eq '' && $command eq '') {
if ($env{'user.adv'}) {
&Apache::loncommon::content_type($request,'text/html');
@@ -9666,6 +10493,7 @@ sub handler {
&init_perm();
if (!%perm) {
$request->internal_redirect('/adm/quickgrades');
+ return OK;
} else {
&Apache::loncommon::content_type($request,'text/html');
$request->send_http_header;
@@ -9686,9 +10514,33 @@ sub handler {
}
&Apache::loncommon::content_type($request,'text/html');
$request->send_http_header;
- $request->print($start_page);
+ unless ((($command eq 'submission' || $command eq 'versionsub')) && ($perm{'vgr'})) {
+ $request->print($start_page);
+ }
if ($command eq 'submission' && $perm{'vgr'}) {
+ 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) : &submission($request,0,0));
+ } 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);
} elsif ($command eq 'displayPage' && $perm{'vgr'}) {
@@ -9797,11 +10649,11 @@ At least the logic that drives this has
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
+ If we still have a problem, 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:
+ in LON-CAPA BZ 5631 see:
http://bugs.lon-capa.org/show_bug.cgi?id=5631
by informing the user that this happened.
@@ -9859,6 +10711,16 @@ ssi_with_retries()
- missingbubble - array ref of the bubble lines that have missing
bubble errors
+ $randomorder - True if exam folder has randomorder set
+ $randompick - True if exam folder has randompick set
+ $respnumlookup - Reference to HASH mapping question numbers in bubble lines
+ for current line to question number used for same question
+ in "Master Seqence" (as seen by Course Coordinator).
+ $startline - Reference to hash where key is question number (0 is first)
+ and value is number of first bubble line for current student
+ or code-based randompick and/or randomorder.
+
+
=item scantron_get_maxbubble() :
Arguments:
@@ -9879,7 +10741,7 @@ ssi_with_retries()
$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
+ 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.