--- loncom/homework/grades.pm 2009/06/06 19:26:50 1.528.2.13 +++ loncom/homework/grades.pm 2009/05/18 20:07:34 1.574.2.1 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.528.2.13 2009/06/06 19:26:50 raeburn Exp $ +# $Id: grades.pm,v 1.574.2.1 2009/05/18 20:07:34 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -26,6 +26,8 @@ # http://www.lon-capa.org/ # + + package Apache::grades; use strict; use Apache::style; @@ -58,46 +60,6 @@ my $ssi_error_resource; my $ssi_error_message; -# Do an ssi with retries: -# While I'd love to factor out this with the vesrion in lonprintout, -# that would either require a data coupling between modules, which I refuse to perpetuate -# (there's quite enough of that already), or would require the invention of another infrastructure -# I'm not quite ready to invent (e.g. an ssi_with_retry object). -# -# At least the logic that drives this has been pulled out into loncommon. - - -# -# ssi_with_retries - Does the server side include of a resource. -# if the ssi call returns an error we'll retry it up to -# the number of times requested by the caller. -# If we still have a proble, no text is appended to the -# output and we set some global variables. -# to indicate to the caller an SSI error occurred. -# All of this is supposed to deal with the issues described -# in LonCAPA BZ 5631 see: -# http://bugs.lon-capa.org/show_bug.cgi?id=5631 -# by informing the user that this happened. -# -# Parameters: -# resource - The resource to include. This is passed directly, without -# interpretation to lonnet::ssi. -# form - The form hash parameters that guide the interpretation of the resource -# -# retries - Number of retries allowed before giving up completely. -# Returns: -# On success, returns the rendered resource identified by the resource parameter. -# Side Effects: -# The following global variables can be set: -# ssi_error - If an unrecoverable error occurred this becomes true. -# It is up to the caller to initialize this to false -# if desired. -# ssi_error_resource - If an unrecoverable error occurred, this is the value -# of the resource that could not be rendered by the ssi -# call. -# ssi_error_message - The error string fetched from the ssi response -# in the event of an error. -# sub ssi_with_retries { my ($resource, $retries, %form) = @_; my ($content, $response) = &Apache::loncommon::ssi_with_retries($resource, $retries, %form); @@ -256,8 +218,8 @@ sub showResourceInfo { } my $display_part=&get_display_part($partID,$symb); $result.='<td><b>'.&mt('Part').': </b>'.$display_part. - ' <span class="LC_internal_info">'.$resID.'</span></td>'. - '<td><b>'.&mt('Type').': </b>'.$responsetype.'</td></tr>'; + ' <span class="LC_internal_info">'.$resID.'</span></td>'. + '<td><b>'.&mt('Type').': </b>'.$responsetype.'</td></tr>'; # '<td>'.&mt('<b>Handgrade: </b>[_1]',$handgrade).'</td></tr>'; } } @@ -272,28 +234,54 @@ sub reset_caches { { my %analyze_cache; + my %analyze_cache_formkeys; sub reset_analyze_cache { undef(%analyze_cache); + undef(%analyze_cache_formkeys); } sub get_analyze { - my ($symb,$uname,$udom,$no_increment)=@_; + my ($symb,$uname,$udom,$no_increment,$add_to_hash)=@_; my $key = "$symb\0$uname\0$udom"; - return $analyze_cache{$key} if (exists($analyze_cache{$key})); + if (exists($analyze_cache{$key})) { + my $getupdate = 0; + if (ref($add_to_hash) eq 'HASH') { + foreach my $item (keys(%{$add_to_hash})) { + if (ref($analyze_cache_formkeys{$key}) eq 'HASH') { + if (!exists($analyze_cache_formkeys{$key}{$item})) { + $getupdate = 1; + last; + } + } else { + $getupdate = 1; + } + } + } + if (!$getupdate) { + return $analyze_cache{$key}; + } + } my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb); $url=&Apache::lonnet::clutter($url); - my $subresult=&ssi_with_retries($url, $ssi_retries, - ('grade_target' => 'analyze', - 'grade_domain' => $udom, - 'grade_symb' => $symb, - 'grade_courseid' => - $env{'request.course.id'}, - 'grade_username' => $uname, - 'grade_noincrement' => $no_increment)); + my %form = ('grade_target' => 'analyze', + 'grade_domain' => $udom, + 'grade_symb' => $symb, + 'grade_courseid' => $env{'request.course.id'}, + 'grade_username' => $uname, + 'grade_noincrement' => $no_increment); + if (ref($add_to_hash)) { + %form = (%form,%{$add_to_hash}); + } + my $subresult=&ssi_with_retries($url, $ssi_retries,%form); (undef,$subresult)=split(/_HASH_REF__/,$subresult,2); my %analyze=&Apache::lonnet::str2hash($subresult); + if (ref($add_to_hash) eq 'HASH') { + $analyze_cache_formkeys{$key} = $add_to_hash; + } else { + $analyze_cache_formkeys{$key} = {}; + } return $analyze_cache{$key} = \%analyze; } @@ -308,13 +296,39 @@ sub reset_caches { my $analyze = &get_analyze($symb,$uname,$udom); my $foils = &get_order($partid,$respid,$symb,$uname,$udom); if (ref($foils) eq 'ARRAY') { - foreach my $foil (@{$foils}) { - if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') { - return $foil; + foreach my $foil (@{$foils}) { + if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') { + return $foil; + } + } + } + } + + sub scantron_partids_tograde { + my ($resource,$cid,$uname,$udom,$check_for_randomlist) = @_; + my (%analysis,@parts); + if (ref($resource)) { + my $symb = $resource->symb(); + my $add_to_form; + if ($check_for_randomlist) { + $add_to_form = { 'check_parts_withrandomlist' => 1,}; + } + my $analyze = &get_analyze($symb,$uname,$udom,undef,$add_to_form); + if (ref($analyze) eq 'HASH') { + %analysis = %{$analyze}; + } + if (ref($analysis{'parts'}) eq 'ARRAY') { + foreach my $part (@{$analysis{'parts'}}) { + my ($id,$respid) = split(/\./,$part); + if (!&Apache::loncommon::check_if_partid_hidden($id,$symb,$udom,$uname)) { + push(@parts,$part); + } } } } + return (\%analysis,\@parts); } + } #--- Clean response type for display @@ -740,7 +754,7 @@ sub verifyreceipt { my $title.= '<h3><span class="LC_info">'. - &mt('Verifying Receipt No. [_1]',$receipt). + &mt('Verifying Receipt No. [_1]',$receipt). '</span></h3>'."\n". '<h4>'.&mt('<b>Resource: </b>[_1]',$env{'form.probTitle'}). '</h4>'."\n"; @@ -824,16 +838,16 @@ sub listStudents { $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'}; - my $result='<h3><span class="LC_info"> '. - &mt("$viewgrade Submissions for a Student or a Group of Students") + my $result='<h3><span class="LC_info"> ' + .&mt("$viewgrade Submissions for a Student or a Group of Students") .'</span></h3>'; my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($symb,$env{'form.probTitle'},($env{'form.showgrading'} eq 'yes')); my %lt = &Apache::lonlocal::texthash ( - 'multiple' => 'Please select a student or group of students before clicking on the Next button.', - 'single' => 'Please select the student before clicking on the Next button.', - ); + 'multiple' => 'Please select a student or group of students before clicking on the Next button.', + 'single' => 'Please select the student before clicking on the Next button.', + ); $request->print(<<LISTJAVASCRIPT); <script type="text/javascript" language="javascript"> function checkSelect(checkBox) { @@ -875,16 +889,17 @@ LISTJAVASCRIPT my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'. "\n".$table; - $gradeTable .= - ' <b>'.&mt('View Problem Text').': </b>'. - '<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n". - '<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n". - '<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label><br />'."\n"; - $gradeTable .= - ' <b>'.&mt('View Answer').': </b>'. - '<label><input type="radio" name="vAns" value="no" /> '.&mt('no').' </label>'."\n". - '<label><input type="radio" name="vAns" value="yes" /> '.&mt('one student').' </label>'."\n". - '<label><input type="radio" name="vAns" value="all" checked="checked" /> '.&mt('all students').' </label><br />'."\n"; + $gradeTable .= &Apache::lonhtmlcommon::start_pick_box(); + $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Problem Text')) + .'<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n" + .'<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n" + .'<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label><br />'."\n" + .&Apache::lonhtmlcommon::row_closure(); + $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Answer')) + .'<label><input type="radio" name="vAns" value="no" /> '.&mt('no').' </label>'."\n" + .'<label><input type="radio" name="vAns" value="yes" /> '.&mt('one student').' </label>'."\n" + .'<label><input type="radio" name="vAns" value="all" checked="checked" /> '.&mt('all students').' </label><br />'."\n" + .&Apache::lonhtmlcommon::row_closure(); my $submission_options; if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) { @@ -899,19 +914,20 @@ LISTJAVASCRIPT '<label><input type="radio" name="lastSub" value="last" /> '.&mt('last submission & parts info').' </label>'."\n". '<label><input type="radio" name="lastSub" value="datesub" /> '.&mt('by dates and submissions').' </label>'."\n". '<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').'</label>'; - $gradeTable .= - ' <b>'.&mt('Submissions').': </b>'.$submission_options.'<br />'."\n"; + $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Submissions')) + .$submission_options + .&Apache::lonhtmlcommon::row_closure(); + + $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Grading Increments')) + .'<select name="increment">' + .'<option value="1">'.&mt('Whole Points').'</option>' + .'<option value=".5">'.&mt('Half Points').'</option>' + .'<option value=".25">'.&mt('Quarter Points').'</option>' + .'<option value=".1">'.&mt('Tenths of a Point').'</option>' + .'</select>' + .&Apache::lonhtmlcommon::row_closure(); $gradeTable .= - ' <b>'.&mt('Grading Increments').': </b>'. - '<select name="increment">'. - '<option value="1">'.&mt('Whole Points').'</option>'. - '<option value=".5">'.&mt('Half Points').'</option>'. - '<option value=".25">'.&mt('Quarter Points').'</option>'. - '<option value=".1">'.&mt('Tenths of a Point').'</option>'. - '</select>'; - - $gradeTable .= &build_section_inputs(). '<input type="hidden" name="submitonly" value="'.$submitonly.'" />'."\n". '<input type="hidden" name="handgrade" value="'.$env{'form.handgrade'}.'" /><br />'."\n". @@ -922,14 +938,23 @@ LISTJAVASCRIPT '<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n"; if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) { - $gradeTable.='<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n"; + $gradeTable .= '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n"; } else { - $gradeTable.=&mt('<b>Student Status:</b> [_1]', - &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);')).'<br />'; + $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Student Status')) + .&Apache::lonhtmlcommon::StatusOptions( + $saveStatus,undef,1,'javascript:reLoadList(this.form);') + .&Apache::lonhtmlcommon::row_closure(); } - $gradeTable.=&mt('To '.lc($viewgrade)." a submission or a group of submissions, click on the check box(es) next to the student's name(s). Then click on the Next button.").'<br />'."\n". - '<input type="hidden" name="command" value="processGroup" />'."\n"; + $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Check For Plagiarism')) + .'<input type="checkbox" name="checkPlag" checked="checked" />' + .&Apache::lonhtmlcommon::row_closure(1) + .&Apache::lonhtmlcommon::end_pick_box(); + + $gradeTable .= '<p>' + .&mt('To '.lc($viewgrade)." a submission or a group of submissions, click on the check box(es) next to the student's name(s). Then click on the Next button.")."\n" + .'<input type="hidden" name="command" value="processGroup" />' + .'</p>'; # checkall buttons $gradeTable.=&check_script('gradesub', 'stuinfo'); @@ -937,7 +962,6 @@ LISTJAVASCRIPT 'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n". 'value="'.&mt('Next').' →" /> <br />'."\n"; $gradeTable.=&check_buttons(); - $gradeTable.='<label><input type="checkbox" name="checkPlag" checked="checked" />'.&mt('Check For Plagiarism').'</label>'; my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup); $gradeTable.= &Apache::loncommon::start_data_table(). &Apache::loncommon::start_data_table_header_row(); @@ -1020,7 +1044,7 @@ LISTJAVASCRIPT $gradeTable.= &Apache::loncommon::start_data_table_row(); } $gradeTable.='<td align="right">'.$ctr.' </td>'. - '<td align="center"><label><input type=checkbox name="stuinfo" value="'. + '<td align="center"><label><input type="checkbox" name="stuinfo" value="'. $student.':'.$$fullname{$student}.':::SECTION'.$section. ') " /> </label></td>'."\n".'<td>'. &nameUserString(undef,$$fullname{$student},$uname,$udom). @@ -1151,7 +1175,7 @@ sub processGroup { #--- Javascript to handle the submission page functionality --- sub sub_page_js { my $request = shift; - my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = '); + my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = '); $request->print(<<SUBJAVASCRIPT); <script type="text/javascript" language="javascript"> function updateRadio(formname,id,weight) { @@ -1527,8 +1551,8 @@ INNERJS pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">"); pDoc.write("<h3><span class=\\"LC_info\\"> Compose Message for \"+fullname+\"<\\/span><\\/h3><br /><br />"); - pDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">"); - pDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">"); + pDoc.write('<table border="0" width="100%"><tr><td bgcolor="#777777">'); + pDoc.write('<table border="0" width="100%"><tr bgcolor="#DDFFFF">'); pDoc.write("<td><b>Type<\\/b><\\/td><td><b>Include<\\/b><\\/td><td><b>Message<\\/td><\\/tr>"); } function displaySubject(msg,shwsel) { @@ -1612,8 +1636,8 @@ INNERJS hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">"); hDoc.write("<h3><span class=\\"LC_info\\"> Keyword Highlight Options<\\/span><\\/h3><br /><br />"); - hDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">"); - hDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">"); + hDoc.write('<table border="0" width="100%"><tr><td bgcolor="#777777">'); + hDoc.write('<table border="0" width="100%"><tr bgcolor="#DDFFFF">'); hDoc.write("<td><b>Text Color<\\/b><\\/td><td><b>Font Size<\\/b><\\/td><td><b>Font Style<\\/td><\\/tr>"); } @@ -1708,8 +1732,9 @@ sub gradeBox { $line.='<option value="reset status">'.&mt('reset status').'</option></select>'."\n"; + #&mt('<td><b>Part:</b></td><td>[_1]</td><td><b>Points:</b></td><td>[_2]</td><td>or</td><td>[_3]</td>',$display_part,$radio,$line); $result .= - '<td><b>'.&mt('Part').':</b></td><td>'.$display_part.'</td><td><b>'.&mt('Points').':</b></td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>'; + '<td><b>'.&mt('Part').':</b></td><td>'.$display_part.'</td><td><b>'.&mt('Points').':</b></td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>'. $result.='</tr></table>'."\n"; $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n". @@ -2110,10 +2135,9 @@ KEYWORDS {'one_time' => 1}); $similar="<hr /><h3><span class=\"LC_warning\">". - &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'}). @@ -2137,7 +2161,7 @@ KEYWORDS foreach my $file (@$files) { $file_counter++; &Apache::lonnet::allowuploaded('/adm/grades',$file); - $lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border=0"> '.$file.'</a>'; + $lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border="0" /> '.$file.'</a>'; } $lastsubonly.='<br />'; } @@ -2272,7 +2296,7 @@ KEYWORDS '<option>7</option><option>10</option></select>'."\n"; my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1'); $ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</; - $endform.=&mt('[_1]student(s)',$ntstu); + $endform.=&mt('[quant,_1,student]',$ntstu); $endform.=' <input type="button" value="'.&mt('Previous').'" '. 'onClick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> '."\n". '<input type="button" value="'.&mt('Next').'" '. @@ -2369,7 +2393,7 @@ sub get_last_submission { } if (!@string) { $string[0] = - '<span class="LC_warning">Nothing submitted - no attempts.</span>'; + '<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span>'; } return (\@string,\$timestamp); } @@ -2438,7 +2462,7 @@ sub processHandGrade { undef,$feedurl,undef, undef,undef,$showsymb, $restitle); - $request->print('<br />'.&mt('Sending message to [_1]:[_2]',$uname,$udom).': '. + $request->print('<br />'.&mt('Sending message to [_1]',$uname.':'.$udom).': '. $msgstatus); } if ($env{'form.collaborator'.$ctr}) { @@ -3213,15 +3237,15 @@ sub viewgrades { my ($common_header,$specific_header); if ($env{'form.section'} eq 'all') { - $common_header = &mt('Assign Common Grade to Class'); + $common_header = &mt('Assign Common Grade to Class'); $specific_header = &mt('Assign Grade to Specific Students in Class'); } elsif ($env{'form.section'} eq 'none') { $common_header = &mt('Assign Common Grade to Students in no Section'); - $specific_header = &mt('Assign Grade to Specific Students in no Section'); + $specific_header = &mt('Assign Grade to Specific Students in no Section'); } else { my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section')); $common_header = &mt('Assign Common Grade to Students in Section(s) [_1]',$section_display); - $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1]',$section_display); + $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1]',$section_display); } $result.= '<h3>'.$common_header.'</h3>'.&Apache::loncommon::start_data_table(); #radio buttons/text box for assigning points for a section or class. @@ -3270,7 +3294,7 @@ sub viewgrades { $result.= &Apache::loncommon::start_data_table_row()."\n". - '<td><b>'.&mt('Part').':</b></td><td>'.$display_part.'</td><td><b>'.&mt('Points').':</b></td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>'. + '<td><b>'.&mt('Part').':</b></td><td>'.$display_part.'</td><td><b>'.&mt('Points').':</b></td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>'. &Apache::loncommon::end_data_table_row()."\n"; $ctsparts++; } @@ -3291,22 +3315,22 @@ sub viewgrades { my @partids = (); foreach my $part (@parts) { my $display=&Apache::lonnet::metadata($url,$part.'.display'); - my $narrowtext = &mt('Tries').'<br />'; - $display =~ s{^Number of Attempts}{$narrowtext}; # makes the column narrower + my $narrowtext = &mt('Tries'); + $display =~ s|^Number of Attempts|$narrowtext <br />|; # makes the column narrower if (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); } my ($partid) = &split_part_type($part); push(@partids,$partid); my $display_part=&get_display_part($partid,$symb); if ($display =~ /^Partial Credit Factor/) { $result.='<th>'. - &mt('Score Part: [_1] (weight = [_2])', - $display_part.'<br />',$weight{$partid}).'</th>'."\n"; + &mt('Score Part: [_1]<br /> (weight = [_2])', + $display_part,$weight{$partid}).'</th>'."\n"; next; } else { if ($display =~ /Problem Status/) { - my $grade_status_mt = &mt('Grade Status').'<br />'; - $display =~ s{Problem Status}{$grade_status_mt}; + my $grade_status_mt = &mt('Grade Status'); + $display =~ s{Problem Status}{$grade_status_mt<br />}; } my $part_mt = &mt('Part:'); $display =~s{\[Part: \Q$partid\E\]}{$part_mt $display_part}; @@ -3465,7 +3489,7 @@ sub editgrades { my $display=&Apache::lonnet::metadata($url,$stores.'.display'); $display =~ s/\[Part: \Q$part\E\]//; my $narrowtext = &mt('Tries'); - $display =~ s{Number of Attempts}{$narrowtext}; + $display =~ s/Number of Attempts/$narrowtext/; $header .= '<th align="center">'.&mt('Old').' '.$display.'</th>'. '<th align="center">'.&mt('New').' '.$display.'</th>'; $columns{$partid}+=2; @@ -3656,7 +3680,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(<<ENDPICK); function verify(vf) { @@ -3696,7 +3720,7 @@ ENDPICK } sub csvupload_javascript_forward_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(<<ENDPICK); function verify(vf) { @@ -3779,7 +3803,7 @@ ENDPICK sub csvupload_fields { my ($symb) = @_; my (@parts) = &getpartlist($symb); - my @fields=(['ID','Student ID'], + my @fields=(['ID','Student/Employee ID'], ['username','Student Username'], ['domain','Student Domain']); my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb); @@ -3835,7 +3859,7 @@ sub upcsvScores_form { $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n"; $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n"; $result.=' <b>'.&mt('Specify a file containing the class scores for current resource.'). - '.</b></td></tr>'."\n"; + '</b></td></tr>'."\n"; $result.='<tr bgcolor=#ffffe6><td>'."\n"; my $upload=&mt("Upload Scores"); my $upfile_select=&Apache::loncommon::upfile_select_html(); @@ -4065,14 +4089,14 @@ sub csvuploadassign { $countdone++; } } - $request->print('<br /><span class="LC_info">'.&mt("Saved [_1] students",$countdone)."</span>\n"); + $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt("Saved scores for [quant,_1,student]",$countdone),$countdone==0)); if (@skipped) { - $request->print('<p><span class="LC_warning">'.&mt('Skipped Students').'</span></p>'); - foreach my $student (@skipped) { $request->print("$student<br />\n"); } + $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('No scores stored for the following username(s):'),1).'<br />'); + $request->print(join(', ',@skipped)); } if (@notallowed) { - $request->print('<p><span class="LC_error">'.&mt('Students Not Allowed to Modify').'</span></p>'); - foreach my $student (@notallowed) { $request->print("$student<br />\n"); } + $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('Modification of scores not allowed for the following username(s):'),1).'<br />'); + $request->print(join(', ',@notallowed)); } $request->print("<br />\n"); $request->print(&show_grading_menu_form($symb)); @@ -4129,7 +4153,7 @@ LISTJAVASCRIPT $ctr++; } $select.= '</select>'; - $result.=' <b>'.&mt('Problems from').":</b> $select<br />\n"; + $result.=' <b>'.&mt('Problems from').':</b> '.$select."<br />\n"; $ctr=0; foreach (@$titles) { @@ -4144,13 +4168,13 @@ LISTJAVASCRIPT my $options = '<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n". '<label><input type="radio" name="vProb" value="yes" /> '.&mt('yes').' </label>'."<br />\n"; - $result.=' <b>'.&mt('View Problem Text').": </b> $options"; + $result.=' <b>'.&mt('View Problem Text').': </b>'.$options; $options = '<label><input type="radio" name="lastSub" value="none" /> '.&mt('none').' </label>'."\n". '<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> '.&mt('by dates and submissions').'</label>'."\n". '<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').' </label>'."\n"; - $result.=' >b>'.&mt('Submissions').": </b>$options"; + $result.=' <b>'.&mt('Submissions').': </b>'.$options; $result.=&build_section_inputs(); my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status')); @@ -4159,7 +4183,7 @@ LISTJAVASCRIPT '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n". '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n"; - $result.=' <b>'.&mt('Use CODE').': </b> <input type="text" name="CODE" value="" /><br />'."\n"; + $result.=' <b>'.&mt('Use CODE').': </b> <input type="text" name="CODE" value="" /> <br />'."\n"; $result.=' <input type="button" '. 'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' →" /><br />'."\n"; @@ -4201,7 +4225,7 @@ LISTJAVASCRIPT } $studentTable.=&Apache::loncommon::end_data_table()."\n"; $studentTable.='<input type="button" '. - 'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next->').'" /></form>'."\n"; + 'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' →" /></form>'."\n"; $studentTable.=&show_grading_menu_form($symb); $request->print($studentTable); @@ -4692,10 +4716,10 @@ Next each scanline is checked for any er bubbles' (it's an error because it may have been mis-scanned because too light bubbling), 'double bubble' (each bubble line should have no more that one letter picked), invalid or duplicated CODE, -invalid student ID +invalid student/employee ID If the CODE option is used that determines the randomization of the -homework problems, either way the student ID is looked up into a +homework problems, either way the student/employee ID is looked up into a username:domain. During the validation phase the instructor can choose to skip scanlines. @@ -4765,7 +4789,7 @@ sub getSequenceDropDown { } my %bubble_lines_per_response; # no. bubble lines for each response. - # index is "symb.part_id" + # key is zero-based index - 0, 1, 2 ... my %first_bubble_line; # First bubble line no. for each bubble. @@ -4806,7 +4830,6 @@ sub restore_bubble_lines { $env{"form.scantron.responsetype.$line"}; $line++; } - } # Given the parsed scanline, get the response for @@ -4815,7 +4838,6 @@ sub restore_bubble_lines { 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}; @@ -5046,10 +5068,10 @@ sub scantron_selectphase { <td> '.&mt('Sequence to grade:').' </td><td> '.$sequence_selector.' </td> '.&Apache::loncommon::end_data_table_row().' '.&Apache::loncommon::start_data_table_row().' - <td> '.&mt('Filename of scoring office file:').' </td><td> '.$file_selector.' </td> + <td> '.&mt('Filename of bubblesheet data file:').' </td><td> '.$file_selector.' </td> '.&Apache::loncommon::end_data_table_row().' '.&Apache::loncommon::start_data_table_row().' - <td> '.&mt('Format of data file:').' </td><td> '.$format_selector.' </td> + <td> '.&mt('Format of bubblesheet data file:').' </td><td> '.$format_selector.' </td> '.&Apache::loncommon::end_data_table_row().' '.&Apache::loncommon::start_data_table_row().' <td> '.&mt('Saved CODEs to validate against:').' </td><td> '.$CODE_selector.' </td> @@ -5067,7 +5089,7 @@ sub scantron_selectphase { '.&Apache::loncommon::end_data_table_row().' '.&Apache::loncommon::start_data_table_row().' <td colspan="2"> - <input type="submit" value="'.&mt('Grading: Validate Scantron Records').'" /> + <input type="submit" value="'.&mt('Grading: Validate Bubblesheet Records').'" /> </td> '.&Apache::loncommon::end_data_table_row().' '.&Apache::loncommon::end_data_table().' @@ -5086,7 +5108,7 @@ sub scantron_selectphase { '.&Apache::loncommon::start_data_table('LC_scantron_action').' '.&Apache::loncommon::start_data_table_header_row().' <th> - '.&mt('Specify a Scantron data file to upload.').' + '.&mt('Specify a bubblesheet data file to upload.').' </th> '.&Apache::loncommon::end_data_table_header_row().' '.&Apache::loncommon::start_data_table_row().' @@ -5113,7 +5135,7 @@ sub scantron_selectphase { <input name="command" value="scantronupload_save" type="hidden" /> '.&mt('File to upload: [_1]','<input type="file" name="upfile" size="50" />').' <br /> - <input type="button" onClick="javascript:checkUpload(this.form);" value="'.&mt('Upload Scantron Data').'" /> + <input type="button" onClick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" /> </form> '); @@ -5155,7 +5177,7 @@ sub scantron_selectphase { &Apache::loncommon::start_data_table('LC_scantron_action')."\n". &Apache::loncommon::start_data_table_header_row()."\n". '<th colspan="2"> - '.&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". '</th>'."\n". &Apache::loncommon::end_data_table_header_row()."\n". &Apache::loncommon::start_data_table_row()."\n". @@ -5171,6 +5193,10 @@ sub scantron_selectphase { '<td> '.$format_selector.' </td>'."\n". &Apache::loncommon::end_data_table_row()."\n". &Apache::loncommon::start_data_table_row()."\n". + '<td> '.&mt('Options').' </td>'."\n". + '<td> <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> '.&mt('Skip hidden resources').'</label></td>'. + &Apache::loncommon::end_data_table_row()."\n". + &Apache::loncommon::start_data_table_row()."\n". '<td colspan="2">'."\n". '<input type="hidden" name="command" value="checksubmissions" />'."\n". '<input type="submit" value="'.&mt('Review Scantron Data and Submission Records').'" />'."\n". @@ -5214,8 +5240,8 @@ 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 ID number starts - IDlength - length of the student ID info + IDstart - column where the student/employee ID starts + IDlength - length of the student/employee ID info Qstart - column where the information from the bubbled 'questions' start Qlength - number of columns comprising a single bubble line from @@ -5275,7 +5301,7 @@ sub get_scantron_config { =item username_to_idmap - creates a hash keyed by student id with values of the corresponding + creates a hash keyed by student/employee ID with values of the corresponding student username:domain. Arguments: @@ -5314,7 +5340,7 @@ sub username_to_idmap { $whichline - line number of the passed in scanline $field - type of change to process (either - 'ID' -> correct the student ID number + 'ID' -> correct the student/employee ID 'CODE' -> correct the CODE 'answer' -> fixup the submitted answers) @@ -5488,7 +5514,7 @@ sub digits_to_letters { CODE_ignore_dup - 1 if the CODE is a duplicated use when unique CODEs were selected, but the usage has been forced by the operator - ID - student ID + ID - student/employee ID PaperID - if used, the ID number printed on the sheet when the paper was scanned FirstName - first name from the sheet @@ -6214,8 +6240,8 @@ sub scantron_validate_file { } } if (!$stop) { - my $warning=&scantron_warning_screen('Start Grading'); - $r->print(&mt('Validation process complete.').'<br />'. + my $warning=&scantron_warning_screen('Start Grading'); + $r->print(&mt('Validation process complete.').'<br />'. $warning. &mt('Perform verification for each student after storage of submissions?'). ' <span class="LC_nobreak"><label>'. @@ -6223,16 +6249,17 @@ sub scantron_validate_file { (' 'x3).'<label>'. '<input type="radio" name="verifyrecord" value="0" checked="checked" />'.&mt('No'). '</label></span><br />'. - &mt('Grading will take longer if you use verification.').'<br />'. &mt("Alternatively, the 'Review scantron data' utility (see grading menu) can be used for all students after grading is complete.").'<br /><br />'. + &mt('Grading will take longer if you use verification.').'<br />'. + &mt("Alternatively, the 'Review bubblesheet data' utility (see grading menu) can be used for all students after grading is complete.").'<br /><br />'. '<input type="submit" name="submit" value="'.&mt('Start Grading').'" />'. '<input type="hidden" name="command" value="scantron_process" />'."\n"); } else { - $r->print('<input type="hidden" name="command" value="scantron_validate" />'); - $r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />"); + $r->print('<input type="hidden" name="command" value="scantron_validate" />'); + $r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />"); } if ($stop) { if ($validate_phases[$currentphase] eq 'sequence') { - $r->print('<input type="submit" name="submit" value="'.&mt('Ignore').' →" />'); + $r->print('<input type="submit" name="submit" value="'.&mt('Ignore').' → " />'); $r->print(' '.&mt('this error').' <br />'); $r->print(" <p>".&mt("Or click the 'Grading Menu' button to start over.")."</p>"); @@ -6618,14 +6645,7 @@ sub scantron_validate_sequence { return (0,$currentphase+1); } -=pod - -=item scantron_validate_ID - Validates all scanlines in the selected file to not have any - invalid or underspecified student IDs - -=cut sub scantron_validate_ID { my ($r,$currentphase) = @_; @@ -6691,35 +6711,6 @@ sub scantron_validate_ID { return (0,$currentphase+1); } -=pod - -=item scantron_get_correction - - Builds the interface screen to interact with the operator to fix a - specific error condition in a specific scanline - - Arguments: - $r - Apache request object - $i - number of the current scanline - $scan_record - hash ref as returned from &scantron_parse_scanline() - $scan_config - hash ref as returned from &get_scantron_config() - $line - full contents of the current scanline - $error - error condition, valid values are - 'incorrectCODE', 'duplicateCODE', - 'doublebubble', 'missingbubble', - 'duplicateID', 'incorrectID' - $arg - extra information needed - For errors: - - duplicateID - paper number that this studentID was seen before on - - duplicateCODE - array ref of the paper numbers this CODE was - seen on before - - incorrectCODE - current incorrect CODE - - doublebubble - array ref of the bubble lines that have double - bubble errors - - missingbubble - array ref of the bubble lines that have missing - bubble errors - -=cut sub scantron_get_correction { my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_; @@ -6785,10 +6776,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(" <label> - <input type='radio' name='scantron_CODE_resolution' value='use_closest_$i' $checked /> + <input type='radio' name='scantron_CODE_resolution' value='use_closest_$i'$checked /> ".&mt("Use the similar CODE [_1] instead.", "<b><tt>".$testcode."</tt></b>")." </label> @@ -6799,10 +6790,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(" <label> - <input type='radio' name='scantron_CODE_resolution' value='use_unfound' $checked /> + <input type='radio' name='scantron_CODE_resolution' value='use_unfound'$checked /> ".&mt("Use the CODE [_1] that is was on the paper, ignoring the error.", "<b><tt>".$$scan_record{'scantron.CODE'}."</tt></b>")." </label>"); @@ -6833,7 +6824,7 @@ ENDSCRIPT ".&mt("[_1]Select[_2] a CODE from the list of all CODEs and use it.", "<a target='_blank' href='$href'>","</a>")." </label> - ".&mt('Selected CODE is [_1]','<input readonly="readonly" type="text" size="8" name="scantron_CODE_selectedvalue" onfocus="javascript:change_radio(\'use_found\')" onchange="javascript:change_radio(\'use_found\')" />')); + ".&mt("Selected CODE is [_1]",'<input readonly="readonly" type="text" size="8" name="scantron_CODE_selectedvalue" onfocus="javascript:change_radio(\'use_found\')" onchange="javascript:change_radio(\'use_found\')" />')); $r->print("\n<br />"); } $r->print(" @@ -7026,7 +7017,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).'<br /><br />'.&mt('A non-zero score can be assigned to the student during scantron grading by selecting a bubble in at least one line.').'<br />'.&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.').'<br />'.&mt("To assign a score of zero for this question, mark all lines as 'No bubble'.").'<br /><br />'); + $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).'<br /><br />'.&mt('A non-zero score can be assigned to the student during bubblesheet grading by selecting a bubble in at least one line.').'<br />'.&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.').'<br />'.&mt("To assign a score of zero for this question, mark all lines as 'No bubble'.").'<br /><br />'); } else { $r->print(&mt("Select at most one bubble in a single line and select 'No Bubble' in all the other lines. ")."<br />"); } @@ -7290,25 +7281,6 @@ sub scantron_validate_doublebubble { return (0,$currentphase+1); } -=pod - -=item scantron_get_maxbubble - - Returns the maximum number of bubble lines that are expected to - occur. Does this by walking the selected sequence rendering the - resource and then checking &Apache::lonxml::get_problem_counter() - for what the current value of the problem counter is. - - Caches the results to $env{'form.scantron_maxbubble'}, - $env{'form.scantron.bubble_lines.n'}, - $env{'form.scantron.first_bubble_line.n'} and - $env{"form.scantron.sub_bubblelines.n"} - which are the total number of bubble, lines, the number of bubble - lines for response n and number of the first bubble line for response n, - and a comma separated list of numbers of bubble lines for sub-questions - (for optionresponse, matchresponse, and rankresponse items), for response n. - -=cut sub scantron_get_maxbubble { if (defined($env{'form.scantron_maxbubble'}) && @@ -7326,22 +7298,21 @@ sub scantron_get_maxbubble { &Apache::lonxml::clear_problem_counter(); - my $uname = $env{'form.student'}; - my $udom = $env{'form.userdom'}; + my $uname = $env{'user.name'}; + my $udom = $env{'user.domain'}; my $cid = $env{'request.course.id'}; my $total_lines = 0; %bubble_lines_per_response = (); %first_bubble_line = (); %subdivided_bubble_lines = (); %responsetype_per_response = (); - + my $response_number = 0; my $bubble_line = 0; foreach my $resource (@resources) { my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,$udom); if ((ref($analysis) eq 'HASH') && (ref($parts) eq 'ARRAY')) { - foreach my $part_id (@{$parts}) { - + foreach my $part_id (@{$parts}) { my $lines; # TODO - make this a persistent hash not an array. @@ -7402,43 +7373,6 @@ sub scantron_get_maxbubble { return $env{'form.scantron_maxbubble'}; } -sub scantron_partids_tograde { - my ($resource,$cid,$uname,$udom) = @_; - my (%analysis,@parts); - - if (ref($resource)) { - my $symb = $resource->symb(); - my $result=&ssi_with_retries($resource->src(), $ssi_retries, - ('symb' => $symb, - 'grade_target' => 'analyze', - 'grade_courseid' => $cid, - 'grade_domain' => $udom, - 'grade_username' => $uname)); - my (undef, $an) = split(/_HASH_REF__/,$result, 2); - %analysis = &Apache::lonnet::str2hash($an); - - if (ref($analysis{'parts'}) eq 'ARRAY') { - foreach my $part (@{$analysis{'parts'}}) { - my ($id,$respid) = split(/\./,$part); - if (!&Apache::loncommon::check_if_partid_hidden($id,$symb,$udom,$uname)) { - push(@parts,$part); - } - } - } - } - return (\%analysis,\@parts); -} - -=pod - -=item scantron_validate_missingbubbles - - Validates all scanlines in the selected file to not have any - answers that don't have bubbles that have not been verified - to be bubble free. - -=cut - sub scantron_validate_missingbubbles { my ($r,$currentphase) = @_; #get student info @@ -7492,29 +7426,6 @@ sub scantron_validate_missingbubbles { return (0,$currentphase+1); } -=pod - -=item scantron_process_students - - Routine that does the actual grading of the bubble sheet information. - - The parsed scanline hash is added to %env - - Then foreach unskipped scanline it does an &Apache::lonnet::ssi() - foreach resource , with the form data of - - 'submitted' =>'scantron' - 'grade_target' =>'grade', - 'grade_username'=> username of student - 'grade_domain' => domain of student - 'grade_courseid'=> of course - 'grade_symb' => symb of resource to grade - - This triggers a grading pass. The problem grading code takes care - of converting the bubbled letter information (now in %env) into a - valid submission. - -=cut sub scantron_process_students { my ($r) = @_; @@ -7533,15 +7444,24 @@ sub scantron_process_students { my $navmap=Apache::lonnavmaps::navmap->new(); my $map=$navmap->getResourceByUrl($sequence); my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0); - - my ($uname,$udom,%partids_by_symb); + my (%grader_partids_by_symb,%grader_randomlists_by_symb); + &graders_resources_pass(\@resources,\%grader_partids_by_symb, + \%grader_randomlists_by_symb); foreach my $resource (@resources) { my $ressymb = $resource->symb(); my ($analysis,$parts) = - &scantron_partids_tograde($resource,$env{'request.course.id'},$uname,$udom); - $partids_by_symb{$ressymb} = $parts; + &scantron_partids_tograde($resource,$env{'request.course.id'}, + $env{'user.name'},$env{'user.domain'},1); + $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'}; + } + } } -# $r->print("geto ".scalar(@resources)."<br />"); + + my ($uname,$udom); my $result= <<SCANTRONFORM; <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload"> <input type="hidden" name="command" value="scantron_configphase" /> @@ -7550,7 +7470,7 @@ SCANTRONFORM $r->print($result); my @delayqueue; - my (%completedstudents,,%scandata); + my (%completedstudents,%scandata); my $lock=&Apache::lonnet::set_lock(&mt('Grading bubblesheet exam')); my $count=&get_todo_count($scanlines,$scan_data); @@ -7565,7 +7485,6 @@ SCANTRONFORM my $started; &scantron_get_maxbubble(); # Need the bubble lines array to parse. - # If an ssi failed in scantron_get_maxbubble, put an error message out to # the user and return. @@ -7606,13 +7525,26 @@ SCANTRONFORM } ($uname,$udom)=split(/:/,$uname); + my %partids_by_symb; + foreach my $resource (@resources) { + my $ressymb = $resource->symb(); + if ((exists($grader_randomlists_by_symb{$ressymb})) || + (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) { + my ($analysis,$parts) = + &scantron_partids_tograde($resource,$env{'request.course.id'},$uname,$udom); + $partids_by_symb{$ressymb} = $parts; + } else { + $partids_by_symb{$ressymb} = $grader_partids_by_symb{$ressymb}; + } + } + &Apache::lonxml::clear_problem_counter(); &Apache::lonnet::appenv($scan_record); if (&scantron_clear_skip($scanlines,$scan_data,$i)) { &scantron_putfile($scanlines,$scan_data); } - + my $scancode; if ((exists($scan_record->{'scantron.CODE'})) && (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) { @@ -7622,7 +7554,7 @@ SCANTRONFORM } if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode, - @resources) eq 'ssi_error') { + \@resources,\%partids_by_symb) eq 'ssi_error') { $ssi_error = 0; # So end of handler error message does not trigger. $r->print("</form>"); &ssi_print_error($r); @@ -7640,16 +7572,17 @@ SCANTRONFORM my $studentrecord = ''; my $counter = -1; foreach my $resource (@resources) { + my $ressymb = $resource->symb(); ($counter,my $recording) = &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'}, - $counter,$studentdata,\%partids_by_symb, + $counter,$studentdata,$partids_by_symb{$ressymb}, \%scantron_config,\%lettdig,$numletts); $studentrecord .= $recording; } if ($studentrecord ne $studentdata) { &Apache::lonxml::clear_problem_counter(); if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode, - \@resources) eq 'ssi_error') { + \@resources,\%partids_by_symb) eq 'ssi_error') { $ssi_error = 0; # So end of handler error message does not trigger. $r->print("</form>"); &ssi_print_error($r); @@ -7661,9 +7594,10 @@ SCANTRONFORM $counter = -1; $studentrecord = ''; foreach my $resource (@resources) { + my $ressymb = $resource->symb(); ($counter,my $recording) = &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'}, - $counter,$studentdata,\%partids_by_symb, + $counter,$studentdata,$partids_by_symb{$ressymb}, \%scantron_config,\%lettdig,$numletts); $studentrecord .= $recording; } @@ -7697,7 +7631,7 @@ SCANTRONFORM } } } - if (&Apache::loncommon::connection_aborted($r)) { last; } + if (&Apache::loncommon::connection_aborted($r)) { last; } } continue { &Apache::lonxml::clear_problem_counter(); &Apache::lonnet::delenv('scantron.'); @@ -7712,39 +7646,68 @@ SCANTRONFORM return ''; } -sub grade_student_bubbles { - my ($r,$uname,$udom,$scan_record,$scancode,@resources) = @_; - foreach my $resource (@resources) { - my %form = ('submitted' => 'scantron', - 'grade_target' => 'grade', - 'grade_username'=> $uname, - 'grade_domain' => $udom, - 'grade_courseid'=> $env{'request.course.id'}, - 'grade_symb' => $resource->symb(), - 'CODE' => $scancode); - my $result=&ssi_with_retries($resource->src(),$ssi_retries,%form); - return 'ssi_error' if ($ssi_error); - last if (&Apache::loncommon::connection_aborted($r)); +sub graders_resources_pass { + my ($resources,$grader_partids_by_symb,$grader_randomlists_by_symb) = @_; + if ((ref($resources) eq 'ARRAY') && (ref($grader_partids_by_symb)) && + (ref($grader_randomlists_by_symb) eq 'HASH')) { + foreach my $resource (@{$resources}) { + my $ressymb = $resource->symb(); + my ($analysis,$parts) = + &scantron_partids_tograde($resource,$env{'request.course.id'}, + $env{'user.name'},$env{'user.domain'},1); + $grader_partids_by_symb->{$ressymb} = $parts; + if (ref($analysis) eq 'HASH') { + if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') { + $grader_randomlists_by_symb->{$ressymb} = + $analysis->{'parts_withrandomlist'}; + } + } + } } return; } -=pod - -=item scantron_upload_scantron_data - - Creates the screen for adding a new bubble sheet data file to a course. - -=cut +sub grade_student_bubbles { + my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts) = @_; + if (ref($resources) eq 'ARRAY') { + my $count = 0; + foreach my $resource (@{$resources}) { + my $ressymb = $resource->symb(); + my %form = ('submitted' => 'scantron', + 'grade_target' => 'grade', + 'grade_username' => $uname, + 'grade_domain' => $udom, + 'grade_courseid' => $env{'request.course.id'}, + 'grade_symb' => $ressymb, + 'CODE' => $scancode + ); + if (ref($parts) eq 'HASH') { + if (ref($parts->{$ressymb}) eq 'ARRAY') { + foreach my $part (@{$parts->{$ressymb}}) { + $form{'scantron_questnum_start.'.$part} = + 1+$env{'form.scantron.first_bubble_line.'.$count}; + $count++; + } + } + } + my $result=&ssi_with_retries($resource->src(),$ssi_retries,%form); + return 'ssi_error' if ($ssi_error); + last if (&Apache::loncommon::connection_aborted($r)); + } + } + return; +} sub scantron_upload_scantron_data { my ($r)=@_; - $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'})); + my $dom = $env{'request.role.domain'}; + my $domdesc = &Apache::lonnet::domain($dom,'description'); + $r->print(&Apache::loncommon::coursebrowser_javascript($dom)); my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid', 'domainid', - 'coursename'); - my $domsel=&Apache::loncommon::select_dom_form($env{'request.role.domain'}, - 'domainid'); + 'coursename',$dom); + my $syllabuslink = '<a href="javascript:ToSyllabus();">'.&mt('Syllabus').'</a>'. + (' 'x2).&mt('(shows course personnel)'); my $default_form_data=&defaultFormData(&get_symb($r,1)); $r->print(' <script type="text/javascript" language="javascript"> @@ -7753,23 +7716,48 @@ sub scantron_upload_scantron_data { alert("'.&mt('Please use the browse button to select a file from your local directory.').'"); return false; } + if (formname.courseid.value == "") { + 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.').'"); + return false; + } formname.submit(); } + + function ToSyllabus() { + var cdom = '."'$dom'".'; + var cnum = document.rules.courseid.value; + if (cdom == "" || cdom == null) { + return; + } + if (cnum == "" || cnum == null) { + return; + } + syllwin=window.open("/public/"+cdom+"/"+cnum+"/syllabus","LONCAPASyllabus", + "height=350,width=350,scrollbars=yes,menubar=no"); + return; + } + </script> +<h3>'.&mt('Send scanned bubblesheet data to a course').'</h3> + <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post"> -'.$default_form_data.' -<table> -<tr><td>'.$select_link.' </td></tr> -<tr><td>'.&mt('Course ID:').' </td> - <td><input name="courseid" type="text" /> </td></tr> -<tr><td>'.&mt('Course Name:').' </td> - <td><input name="coursename" type="text" /> </td></tr> -<tr><td>'.&mt('Domain:').' </td> - <td>'.$domsel.' </td></tr> -<tr><td>'.&mt('File to upload:').'</td> - <td><input type="file" name="upfile" size="50" /></td></tr> -</table> +'.$default_form_data. + &Apache::lonhtmlcommon::start_pick_box(). + &Apache::lonhtmlcommon::row_title(&mt('Course ID')). + '<input name="courseid" type="text" size="30" />'.$select_link. + &Apache::lonhtmlcommon::row_closure(). + &Apache::lonhtmlcommon::row_title(&mt('Course Name')). + '<input name="coursename" type="text" size="30" />'.$syllabuslink. + &Apache::lonhtmlcommon::row_closure(). + &Apache::lonhtmlcommon::row_title(&mt('Domain')). + '<input name="domainid" type="hidden" />'.$domdesc. + &Apache::lonhtmlcommon::row_closure(). + &Apache::lonhtmlcommon::row_title(&mt('File to upload')). + '<input type="file" name="upfile" size="50" />'. + &Apache::lonhtmlcommon::row_closure(1). + &Apache::lonhtmlcommon::end_pick_box().'<br /> + <input name="command" value="scantronupload_save" type="hidden" /> <input type="button" onClick="javascript:checkUpload(this.form);" value="'.&mt('Upload Scantron Data').'" /> </form> @@ -7777,14 +7765,6 @@ sub scantron_upload_scantron_data { return ''; } -=pod - -=item scantron_upload_scantron_data_save - - Adds a provided bubble information data file to the course if user - has the correct privileges to do so. - -=cut sub scantron_upload_scantron_data_save { my($r)=@_; @@ -7806,36 +7786,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'})." <br />"); - 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('<h3>'.&mt("Uploading file to [_1]",$coursedata{'description'}).'</h3>'); if (length($env{'form.upfile'}) < 2) { - $r->print(&mt("<span class=\"LC_error\">Error:</span> The file you attempted to upload, [_1] contained no information. Please check that you entered the correct filename.",'<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</span>")); + $r->print(&mt('[_1]Error:[_2] The file you attempted to upload, [_3] contained no information. Please check that you entered the correct filename.','<span class="LC_error">','</span>','<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>')); } else { - my $result=&Apache::lonnet::finishuserfileupload($env{'form.courseid'},$env{'form.domainid'},'upfile',$fname); - if ($result =~ m|^/uploaded/|) { - $r->print(&mt("<span class=\"LC_success\">Success:</span> Successfully uploaded [_1] bytes of data into location [_2]", - (length($env{'form.upfile'})-1), - '<span class="LC_filename">'.$result."</span>")); + 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]', + '<span class="LC_success">','</span>',(length($env{'form.upfile'})-1), + '<span class="LC_filename">'.$result.'</span>')); + ($uploadedfile) = ($result =~ m{/([^/]+)$}); + $r->print(&validate_uploaded_scantron_file($env{'form.domainid'}, + $env{'form.courseid'},$uploadedfile)); } else { - $r->print(&mt("<span class=\"LC_error\">Error:</span> An error ([_1]) occurred when attempting to upload the file, [_2]", - $result, - '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</span>")); - + $r->print(&mt('[_1]Error:[_2] An error ([_3]) occurred when attempting to upload the file, [_4]', + '<span class="LC_error">','</span>',$result, + '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>')); } } if ($symb) { @@ -7846,13 +7815,91 @@ sub scantron_upload_scantron_data_save { return ''; } -=pod - -=item valid_file - - Validates that the requested bubble data file exists in the course. - -=cut +sub validate_uploaded_scantron_file { + my ($cdom,$cname,$fname) = @_; + my $scanlines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.$fname); + my @lines; + if ($scanlines ne '-1') { + @lines=split("\n",$scanlines,-1); + } + my $output; + if (@lines) { + my (%counts,$max_match_format); + my ($max_match_count,$max_match_pct) = (0,0); + my $classlist = &Apache::loncoursedata::get_classlist($cdom,$cname); + my %idmap = &username_to_idmap($classlist); + foreach my $key (keys(%idmap)) { + my $lckey = lc($key); + $idmap{$lckey} = $idmap{$key}; + } + my %unique_formats; + my @formatlines = &get_scantronformat_file(); + foreach my $line (@formatlines) { + chomp($line); + my @config = split(/:/,$line); + my $idstart = $config[5]; + my $idlength = $config[6]; + if (($idstart ne '') && ($idlength > 0)) { + if (ref($unique_formats{$idstart.':'.$idlength}) eq 'ARRAY') { + push(@{$unique_formats{$idstart.':'.$idlength}},$config[0].':'.$config[1]); + } else { + $unique_formats{$idstart.':'.$idlength} = [$config[0].':'.$config[1]]; + } + } + } + foreach my $key (keys(%unique_formats)) { + my ($idstart,$idlength) = split(':',$key); + %{$counts{$key}} = ( + 'found' => 0, + 'total' => 0, + ); + foreach my $line (@lines) { + next if ($line =~ /^#/); + next if ($line =~ /^[\s\cz]*$/); + my $id = substr($line,$idstart-1,$idlength); + $id = lc($id); + if (exists($idmap{$id})) { + $counts{$key}{'found'} ++; + } + $counts{$key}{'total'} ++; + } + if ($counts{$key}{'total'}) { + my $percent_match = (100*$counts{$key}{'found'})/($counts{$key}{'total'}); + if (($max_match_format eq '') || ($percent_match > $max_match_pct)) { + $max_match_pct = $percent_match; + $max_match_format = $key; + $max_match_count = $counts{$key}{'total'}; + } + } + } + if (ref($unique_formats{$max_match_format}) eq 'ARRAY') { + my $format_descs; + my $numwithformat = @{$unique_formats{$max_match_format}}; + for (my $i=0; $i<$numwithformat; $i++) { + my ($name,$desc) = split(':',$unique_formats{$max_match_format}[$i]); + if ($i<$numwithformat-2) { + $format_descs .= '"<i>'.$desc.'</i>", '; + } elsif ($i==$numwithformat-2) { + $format_descs .= '"<i>'.$desc.'</i>" '.&mt('and').' '; + } elsif ($i==$numwithformat-1) { + $format_descs .= '"<i>'.$desc.'</i>"'; + } + } + my $showpct = sprintf("%.0f",$max_match_pct).'%'; + $output .= '<br />'.&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]).','<b>'.$showpct.'</b>','<b>'.$max_match_count.'</b>',$format_descs). + '<br />'.&mt('A low percentage of matches results from one of the following:').'<ul>'. + '<li>'.&mt('The file was uploaded to the wrong course').'</li>'. + '<li>'.&mt('The data are not in the format expected for the domain: [_1]', + '<i>'.$cdom.'</i>').'</li>'. + '<li>'.&mt('Students did not bubble their IDs, or mis-bubbled them').'</li>'. + '<li>'.&mt('The course roster is not up to date').'</li>'. + '</ul>'; + } + } else { + $output = '<span class="LC_warning">'.&mt('Uploaded file contained no data').'</span>'; + } + return $output; +} sub valid_file { my ($requested_file)=@_; @@ -7862,16 +7909,6 @@ sub valid_file { return 0; } -=pod - -=item scantron_download_scantron_data - - Shows a list of the three internal files (original, corrected, - skipped) for a specific bubble sheet data file that exists in the - course. - -=cut - sub scantron_download_scantron_data { my ($r)=@_; my $default_form_data=&defaultFormData(&get_symb($r,1)); @@ -7930,14 +7967,11 @@ sub checkscantron_results { my %idmap=&Apache::grades::username_to_idmap($classlist); my $navmap=Apache::lonnavmaps::navmap->new(); my $map=$navmap->getResourceByUrl($sequence); - my @resources=$navmap->retrieveResources($map,undef,1,0); - my ($uname,$udom,%partids_by_symb); - foreach my $resource (@resources) { - my $ressymb = $resource->symb(); - my ($analysis,$parts) = - &scantron_partids_tograde($resource,$env{'request.course.id'},$uname,$udom); - $partids_by_symb{$ressymb} = $parts; - } + 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 ($uname,$udom); my (%scandata,%lastname,%bylast); $r->print(' <form method="post" enctype="multipart/form-data" action="/adm/grades" name="checkscantron">'."\n"); @@ -7951,7 +7985,7 @@ sub checkscantron_results { 'inline',undef,'checkscantron'); my ($username,$domain,$started); - &Apache::grades::scantron_get_maxbubble(); # Need the bubble lines array to parse. + &scantron_get_maxbubble(); # Need the bubble lines array to parse. &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state, 'Processing first student'); @@ -7992,9 +8026,18 @@ sub checkscantron_results { ($username,$domain)=split(/:/,$uname); my $counter = -1; foreach my $resource (@resources) { + my $parts; + my $ressymb = $resource->symb(); + if ((exists($grader_randomlists_by_symb{$ressymb})) || + (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) { + (my $analysis,$parts) = + &scantron_partids_tograde($resource,$env{'request.course.id'},$username,$domain); + } else { + $parts = $grader_partids_by_symb{$ressymb}; + } ($counter,my $recording) = &verify_scantron_grading($resource,$domain,$username,$cid,$counter, - $scandata{$pid},\%partids_by_symb, + $scandata{$pid},$parts, \%scantron_config,\%lettdig,$numletts); $record{$pid} .= $recording; } @@ -8033,10 +8076,10 @@ sub checkscantron_results { } } } - $r->print('<p>'.&mt('Comparison of scantron data (including corrections) with corresponding submission records (most recent submission) for <b>[quant,_1,student]</b> ([_2] scantron lines/student).',$numstudents,$env{'form.scantron_maxbubble'}).'</p>'); + $r->print('<p>'.&mt('Comparison of bubblesheet data (including corrections) with corresponding submission records (most recent submission) for <b>[quant,_1,student]</b> ([_2] scantron lines/student).',$numstudents,$env{'form.scantron_maxbubble'}).'</p>'); $r->print('<p>'.&mt('Exact matches for <b>[quant,_1,student]</b>.',$passed).'<br />'.&mt('Discrepancies detected for <b>[quant,_1,student]</b>.',$failed).'</p>'); if ($passed) { - $r->print(&mt('Students with exact correspondence between scantron data and submissions are as follows:').'<br /><br />'); + $r->print(&mt('Students with exact correspondence between bubblesheet data and submissions are as follows:').'<br /><br />'); $r->print(&Apache::loncommon::start_data_table()."\n". &Apache::loncommon::start_data_table_header_row()."\n". '<th>'.&mt('Source').'</th><th>'.&mt('Bubble records').'</th><th>'.&mt('Name').'</th><th>'.&mt('ID').'</th>'. @@ -8045,29 +8088,28 @@ sub checkscantron_results { &Apache::loncommon::end_data_table().'<br />'); } if ($failed) { - $r->print(&mt('Students with differences between scantron data and submissions are as follows:').'<br /><br />'); + $r->print(&mt('Students with differences between bubblesheet data and submissions are as follows:').'<br /><br />'); $r->print(&Apache::loncommon::start_data_table()."\n". &Apache::loncommon::start_data_table_header_row()."\n". '<th>'.&mt('Source').'</th><th>'.&mt('Bubble records').'</th><th>'.&mt('Name').'</th><th>'.&mt('ID').'</th>'. &Apache::loncommon::end_data_table_header_row()."\n". $badstudents."\n". &Apache::loncommon::end_data_table()).'<br />'. - &mt('Differences can occur if submissions were modified using manual grading after a scantron grading pass.').'<br />'.&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.').'<br />'.&mt('If unexpected discrepancies were detected, it is recommended that you inspect the original bubblesheets.'); } $r->print('</form><br />'.$grading_menu_button); return; } sub verify_scantron_grading { - my ($resource,$domain,$username,$cid,$counter,$scandata,$partids_by_symb, + my ($resource,$domain,$username,$cid,$counter,$scandata,$partids, $scantron_config,$lettdig,$numletts) = @_; my ($record,%expected,%startpos); return ($counter,$record) if (!ref($resource)); return ($counter,$record) if (!$resource->is_problem()); my $symb = $resource->symb(); - return ($counter,$record) if (ref($partids_by_symb) ne 'HASH'); - return ($counter,$record) if (ref($partids_by_symb->{$symb}) ne 'ARRAY'); - foreach my $part_id (@{$partids_by_symb->{$symb}}) { + return ($counter,$record) if (ref($partids) ne 'ARRAY'); + foreach my $part_id (@{$partids}) { $counter ++; $expected{$part_id} = 0; if ($env{"form.scantron.sub_bubblelines.$counter"}) { @@ -8160,7 +8202,7 @@ sub verify_scantron_grading { } } } - foreach my $part_id (@{$partids_by_symb->{$symb}}) { + foreach my $part_id (@{$partids}) { if ($recorded{$part_id} eq '') { for (my $i=0; $i<$expected{$part_id}; $i++) { for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) { @@ -8174,7 +8216,7 @@ sub verify_scantron_grading { return ($counter,$record); } -sub letter_to_digits { +sub letter_to_digits { my %lettdig = ( A => 1, B => 2, @@ -8190,11 +8232,6 @@ sub letter_to_digits { return %lettdig; } -=pod - -=back - -=cut #-------- end of section for handling grading scantron forms ------- # @@ -8244,34 +8281,33 @@ sub grading_menu { my $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields); my @menu = ({ url => $url, name => &mt('Manual Grading/View Submissions'), - short_description => + short_description => &mt('Start the process of hand grading submissions.'), }); $fields{'command'} = 'csvform'; $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields); push(@menu, { url => $url, name => &mt('Upload Scores'), - short_description => + short_description => &mt('Specify a file containing the class scores for current resource.')}); $fields{'command'} = 'processclicker'; $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields); push(@menu, { url => $url, name => &mt('Process Clicker'), - short_description => + short_description => &mt('Specify a file containing the clicker information for this resource.')}); $fields{'command'} = 'scantron_selectphase'; $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields); push(@menu, { url => $url, name => &mt('Grade/Manage/Review Scantron Forms'), - short_description => + short_description => &mt('Grade scantron exams, upload/download scantron data files, and review previously graded scantron exams.')}); $fields{'command'} = 'verify'; $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields); push(@menu, { url => "", name => &mt('Verify Receipt'), - short_description => + short_description => &mt('')}); - # # Create the menu my $Str; # $Str .= '<h2>'.&mt('Please select a grading task').'</h2>'; @@ -8283,7 +8319,6 @@ sub grading_menu { '<input type="hidden" name="saveState" value="" />'."\n". '<input type="hidden" name="gradingMenu" value="1" />'."\n". '<input type="hidden" name="showgrading" value="yes" />'."\n"; - foreach my $menudata (@menu) { if ($menudata->{'name'} ne &mt('Verify Receipt')) { $Str .=' <h3><a '. @@ -8296,7 +8331,7 @@ sub grading_menu { $menudata->{'jscript'}. ' onClick="javascript:checkChoice(document.forms.gradingMenu,\'5\',\'verify\')" '. ' /> '. - &Apache::lonnet::recprefix($env{'request.course.id'}). + &Apache::lonnet::recprefix($env{'request.course.id'}). '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />'; } $Str .= ' '.(' 'x8).$menudata->{'short_description'}. @@ -8331,7 +8366,7 @@ sub grading_menu { if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;} if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;} if (checkOpt) { - alert("$receiptalert$receiptalert"); + alert("$receiptalert"); formname.receipt.value = ""; formname.receipt.focus(); return false; @@ -8399,6 +8434,15 @@ GRADINGMENUJS my $saveSub = ($$savedState{'saveSub'} eq '' ? 'all' : $$savedState{'saveSub'}); my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'}); + # Preselect sections + my $selsec=""; + if (ref($sections)) { + foreach my $section (sort(@$sections)) { + $selsec.='<option value="'.$section.'" '. + ($saveSec eq $section ? 'selected="selected"':'').'>'.$section.'</option>'."\n"; + } + } + $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n". '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n". '<input type="hidden" name="handgrade" value="'.$hdgrade.'" />'."\n". @@ -8423,12 +8467,12 @@ GRADINGMENUJS '.&mt('Sections').' </div> <div class="LC_grade_select_mode_selector_body"> - <select name="section" multiple="multiple" size="5">'."\n"; + <select name="section" multiple="multiple" size="5">'."\n"; if (ref($sections)) { - foreach my $section (sort(@$sections)) { - $result.='<option value="'.$section.'" '. - ($saveSec eq $section ? 'selected="selected"':'').'>'.$section.'</option>'."\n"; - } + foreach my $section (sort(@$sections)) { + $result.='<option value="'.$section.'" '. + ($saveSec eq $section ? 'selected="selected"':'').'>'.$section.'</option>'."\n"; + } } $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> '; $result.=' @@ -8456,10 +8500,10 @@ GRADINGMENUJS </div> <div class="LC_grade_select_mode_selector_body"> <select name="submitonly" size="5"> - <option value="yes" '. ($saveSub eq 'yes' ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option> - <option value="queued" '. ($saveSub eq 'queued' ? 'selected="selected"' : '').'>'.&mt('in grading queue').'</option> - <option value="graded" '. ($saveSub eq 'graded' ? 'selected="selected"' : '').'>'.&mt('with ungraded submissions').'</option> - <option value="incorrect" '.($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option> + <option value="yes" '. ($saveSub eq 'yes' ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option> + <option value="queued" '. ($saveSub eq 'queued' ? 'selected="selected"' : '').'>'.&mt('in grading queue').'</option> + <option value="graded" '. ($saveSub eq 'graded' ? 'selected="selected"' : '').'>'.&mt('with ungraded submissions').'</option> + <option value="incorrect" '.($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option> <option value="all" '. ($saveSub eq 'all' ? 'selected="selected"' : '').'>'.&mt('with any status').'</option> </select> </div> @@ -8470,17 +8514,17 @@ GRADINGMENUJS <input type="radio" name="radioChoice" value="submission" '. ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '. &mt('Select individual students to grade and view submissions.').' - </label> + </label> </div> <div class="LC_grade_select_mode_type"> - <label> + <label> <input type="radio" name="radioChoice" value="viewgrades" '. ($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '. &mt('Grade all selected students in a grading table.').' </label> </div> <div class="LC_grade_select_mode_type"> - <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next').' →" /> + <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next->').'" /> </div> </div> </div> @@ -8494,12 +8538,12 @@ GRADINGMENUJS <div class="LC_grade_select_mode_type"> <label> <input type="radio" name="radioChoice" value="pickStudentPage" '. - ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '. + ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '. &mt('The <b>complete</b> page/sequence/folder: For one student').' </label> </div> <div class="LC_grade_select_mode_type"> - <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next').' →" /> + <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next->').'" /> </div> </div> </div> @@ -8604,7 +8648,7 @@ sub process_clicker { $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n"; $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n"; $result.=' <b>'.&mt('Specify a file containing the clicker information for this resource.'). - '.</b></td></tr>'."\n"; + '</b></td></tr>'."\n"; $result.='<tr bgcolor=#ffffe6><td>'."\n"; # Attempt to restore parameters from last session, set defaults if not present my %Saveable_Parameters=&clicker_grading_parameters(); @@ -8618,7 +8662,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"'; } } @@ -8682,11 +8726,11 @@ function sanitycheck() { <input type="hidden" name="saveState" value="$env{'form.saveState'}" /> <input type="file" name="upfile" size="50" /> <br /><label>$type: $selectform</label> -<br /><label><input type="radio" name="gradingmechanism" value="attendance" $checked{'attendance'} onClick="sanitycheck()" />$attendance </label> -<br /><label><input type="radio" name="gradingmechanism" value="personnel" $checked{'personnel'} onClick="sanitycheck()" />$personnel</label> -<br /><label><input type="radio" name="gradingmechanism" value="specific" $checked{'specific'} onClick="sanitycheck()" />$specific </label> +<br /><label><input type="radio" name="gradingmechanism" value="attendance"$checked{'attendance'} onClick="sanitycheck()" />$attendance </label> +<br /><label><input type="radio" name="gradingmechanism" value="personnel"$checked{'personnel'} onClick="sanitycheck()" />$personnel</label> +<br /><label><input type="radio" name="gradingmechanism" value="specific"$checked{'specific'} onClick="sanitycheck()" />$specific </label> <input type="text" name="specificid" value="$env{'form.specificid'}" size="20" /> -<br /><label><input type="radio" name="gradingmechanism" value="given" $checked{'given'} onClick="sanitycheck()" />$given </label> +<br /><label><input type="radio" name="gradingmechanism" value="given"$checked{'given'} onClick="sanitycheck()" />$given </label> <br /> <input type="text" name="givenanswer" size="50" /> <input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" /> @@ -9065,7 +9109,9 @@ sub handler { } $ssi_error = 0; - $request->print(&Apache::loncommon::start_page('Grading')); + my $brcrum = [{href=>"/adm/grades",text=>"Grading"}]; + $request->print(&Apache::loncommon::start_page('Grading',undef, + {'bread_crumbs' => $brcrum})); if ($symb eq '' && $command eq '') { if ($env{'user.adv'}) { if (($env{'form.codeone'}) && ($env{'form.codetwo'}) && @@ -9163,7 +9209,7 @@ sub handler { } elsif ($command eq 'checksubmissions' && $perm{'vgr'}) { $request->print(&checkscantron_results($request)); } elsif ($command) { - $request->print("Access Denied ($command)"); + $request->print('<p class="LC_error">'.&mt('Access Denied ([_1])',$command).'</p>'); } } if ($ssi_error) { @@ -9177,3 +9223,162 @@ sub handler { 1; __END__; + + +=head1 NAME + +Apache::grades + +=head1 SYNOPSIS + +Handles the viewing of grades. + +This is part of the LearningOnline Network with CAPA project +described at http://www.lon-capa.org. + +=head1 OVERVIEW + +Do an ssi with retries: +While I'd love to factor out this with the vesrion in lonprintout, +that would either require a data coupling between modules, which I refuse to perpetuate (there's quite enough of that already), or would require the invention of another infrastructure +I'm not quite ready to invent (e.g. an ssi_with_retry object). + +At least the logic that drives this has been pulled out into loncommon. + + + +ssi_with_retries - Does the server side include of a resource. + if the ssi call returns an error we'll retry it up to + the number of times requested by the caller. + If we still have a proble, no text is appended to the + output and we set some global variables. + to indicate to the caller an SSI error occurred. + All of this is supposed to deal with the issues described + in LonCAPA BZ 5631 see: + http://bugs.lon-capa.org/show_bug.cgi?id=5631 + by informing the user that this happened. + +Parameters: + resource - The resource to include. This is passed directly, without + interpretation to lonnet::ssi. + form - The form hash parameters that guide the interpretation of the resource + + retries - Number of retries allowed before giving up completely. +Returns: + On success, returns the rendered resource identified by the resource parameter. +Side Effects: + The following global variables can be set: + ssi_error - If an unrecoverable error occurred this becomes true. + It is up to the caller to initialize this to false + if desired. + ssi_error_resource - If an unrecoverable error occurred, this is the value + of the resource that could not be rendered by the ssi + call. + ssi_error_message - The error string fetched from the ssi response + in the event of an error. + + +=head1 HANDLER SUBROUTINE + +ssi_with_retries() + +=head1 SUBROUTINES + +=over + +=item scantron_get_correction() : + + Builds the interface screen to interact with the operator to fix a + specific error condition in a specific scanline + + Arguments: + $r - Apache request object + $i - number of the current scanline + $scan_record - hash ref as returned from &scantron_parse_scanline() + $scan_config - hash ref as returned from &get_scantron_config() + $line - full contents of the current scanline + $error - error condition, valid values are + 'incorrectCODE', 'duplicateCODE', + 'doublebubble', 'missingbubble', + 'duplicateID', 'incorrectID' + $arg - extra information needed + For errors: + - duplicateID - paper number that this studentID was seen before on + - duplicateCODE - array ref of the paper numbers this CODE was + seen on before + - incorrectCODE - current incorrect CODE + - doublebubble - array ref of the bubble lines that have double + bubble errors + - missingbubble - array ref of the bubble lines that have missing + bubble errors + +=item scantron_get_maxbubble() : + + Returns the maximum number of bubble lines that are expected to + occur. Does this by walking the selected sequence rendering the + resource and then checking &Apache::lonxml::get_problem_counter() + for what the current value of the problem counter is. + + Caches the results to $env{'form.scantron_maxbubble'}, + $env{'form.scantron.bubble_lines.n'}, + $env{'form.scantron.first_bubble_line.n'} and + $env{"form.scantron.sub_bubblelines.n"} + which are the total number of bubble, lines, the number of bubble + lines for response n and number of the first bubble line for response n, + and a comma separated list of numbers of bubble lines for sub-questions + (for optionresponse, matchresponse, and rankresponse items), for response n. + + +=item scantron_validate_missingbubbles() : + + Validates all scanlines in the selected file to not have any + answers that don't have bubbles that have not been verified + to be bubble free. + +=item scantron_process_students() : + + Routine that does the actual grading of the bubble sheet information. + + The parsed scanline hash is added to %env + + Then foreach unskipped scanline it does an &Apache::lonnet::ssi() + foreach resource , with the form data of + + 'submitted' =>'scantron' + 'grade_target' =>'grade', + 'grade_username'=> username of student + 'grade_domain' => domain of student + 'grade_courseid'=> of course + 'grade_symb' => symb of resource to grade + + This triggers a grading pass. The problem grading code takes care + of converting the bubbled letter information (now in %env) into a + valid submission. + +=item scantron_upload_scantron_data() : + + Creates the screen for adding a new bubble sheet data file to a course. + +=item scantron_upload_scantron_data_save() : + + Adds a provided bubble information data file to the course if user + has the correct privileges to do so. + +=item valid_file() : + + Validates that the requested bubble data file exists in the course. + +=item scantron_download_scantron_data() : + + Shows a list of the three internal files (original, corrected, + skipped) for a specific bubble sheet data file that exists in the + course. + +=item scantron_validate_ID() : + + Validates all scanlines in the selected file to not have any + invalid or underspecified student/employee IDs + +=back + +=cut