--- loncom/homework/grades.pm 2009/06/17 18:41:31 1.528.2.14 +++ loncom/homework/grades.pm 2008/11/11 16:40:47 1.529 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.528.2.14 2009/06/17 18:41:31 raeburn Exp $ +# $Id: grades.pm,v 1.529 2008/11/11 16:40:47 jms Exp $ # # Copyright Michigan State University Board of Trustees # @@ -26,6 +26,183 @@ # http://www.lon-capa.org/ # +=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 OTHER 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 IDs + +=back + +=cut + package Apache::grades; use strict; use Apache::style; @@ -58,46 +235,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); @@ -255,9 +392,9 @@ sub showResourceInfo { $partsseen{$partID}=1; } 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>'; + $result.='<td>'.&mt('<b>Part: </b>[_1]',$display_part).' <span class="LC_internal_info">'. + $resID.'</span></td>'. + '<td>'.&mt('<b>Type: </b>[_1]',$responsetype).'</td></tr>'; # '<td>'.&mt('<b>Handgrade: </b>[_1]',$handgrade).'</td></tr>'; } } @@ -306,14 +443,11 @@ sub reset_caches { sub get_radiobutton_correct_foil { my ($partid,$respid,$symb,$uname,$udom)=@_; 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 (@{&get_order($partid,$respid,$symb,$uname,$udom)}) { + if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') { + return $foil; + } + } } } @@ -740,7 +874,7 @@ sub verifyreceipt { my $title.= '<h3><span class="LC_info">'. - &mt('Verifying Receipt No. [_1]',$receipt). + &mt('Verifying Submission Receipt [_1]',$receipt). '</span></h3>'."\n". '<h4>'.&mt('<b>Resource: </b>[_1]',$env{'form.probTitle'}). '</h4>'."\n"; @@ -825,15 +959,17 @@ sub listStudents { &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") + &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.', - ); + my %lt = ( '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.", + ); + %lt = &Apache::lonlocal::texthash(%lt); $request->print(<<LISTJAVASCRIPT); <script type="text/javascript" language="javascript"> function checkSelect(checkBox) { @@ -876,15 +1012,17 @@ LISTJAVASCRIPT "\n".$table; $gradeTable .= - ' <b>'.&mt('View Problem Text').': </b>'. + ' '. + &mt('<b>View Problem Text: </b>[_1]', '<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"; + '<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label>').'<br />'."\n"; $gradeTable .= - ' <b>'.&mt('View Answer').': </b>'. + ' '. + &mt('<b>View Answer: </b>[_1]', '<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"; + '<label><input type="radio" name="vAns" value="all" checked="checked" /> '.&mt('all students').' </label>').'<br />'."\n"; my $submission_options; if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) { @@ -900,16 +1038,18 @@ LISTJAVASCRIPT '<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"; + ' '. + &mt('<b>Submissions: </b>[_1]',$submission_options).'<br />'."\n"; $gradeTable .= - ' <b>'.&mt('Grading Increments').': </b>'. + ' '. + &mt('<b>Grading Increments:</b> [_1]', '<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>'; + '</select>'); $gradeTable .= &build_section_inputs(). @@ -928,14 +1068,15 @@ LISTJAVASCRIPT &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);')).'<br />'; } - $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". + $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"; # checkall buttons $gradeTable.=&check_script('gradesub', 'stuinfo'); $gradeTable.='<input type="button" '."\n". 'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n". - 'value="'.&mt('Next').' →" /> <br />'."\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); @@ -1055,7 +1196,7 @@ LISTJAVASCRIPT $gradeTable.=&Apache::loncommon::end_data_table()."\n". '<input type="button" '. 'onClick="javascript:checkSelect(this.form.stuinfo);" '. - 'value="'.&mt('Next').' →" /></form>'."\n"; + 'value="'.&mt('Next->').'" /></form>'."\n"; if ($ctr == 0) { my $num_students=(scalar(keys(%$fullname))); if ($num_students eq 0) { @@ -1151,7 +1292,6 @@ 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 = '); $request->print(<<SUBJAVASCRIPT); <script type="text/javascript" language="javascript"> function updateRadio(formname,id,weight) { @@ -1162,7 +1302,7 @@ sub sub_page_js { gradeBox.value = pts; var resetbox = false; if (isNaN(pts) || pts < 0) { - alert("$alertmsg"+pts); + alert("A number equal or greater than 0 is expected. Entered value = "+pts); for (var i=0; i<radioButton.length; i++) { if (radioButton[i].checked) { gradeBox.value = i; @@ -1407,7 +1547,6 @@ INNERJS my $docopen=&Apache::lonhtmlcommon::javascript_docopen(); $docopen=~s/^document\.//; - my $alertmsg = &mt('Please select a word or group of words from document and then click this link.'); $request->print(<<SUBJAVASCRIPT); <script type="text/javascript" language="javascript"> @@ -1440,7 +1579,7 @@ INNERJS else return; var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," "); if (cleantxt=="") { - alert("$alertmsg"); + alert("Please select a word or group of words from document and then click this link."); return; } var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt); @@ -1679,7 +1818,7 @@ sub gradeBox { my $radio.='<table border="0"><tr>'."\n"; # display radio buttons in a nice table 10 across while ($thisweight<=$wgt) { - $radio.= '<td><span class="LC_nobreak"><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '. + $radio.= '<td><span style="white-space: nowrap;"><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '. 'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','. $thisweight.')" value="'.$thisweight.'" '. ($score eq $thisweight ? 'checked="checked"':'').' /> '.$thisweight."</label></span></td>\n"; @@ -1695,7 +1834,7 @@ sub gradeBox { $wgt.')" /></td>'."\n"; $line.='<td>/'.$wgt.' '.$wgtmsg. ($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? ' '.$checkIcon : ''). - ' </td><td><b>'.&mt('Grade Status').':</b>'."\n"; + ' </td><td>'."\n"; $line.='<select name="GD_SEL'.$counter.'_'.$partid.'" '. 'onChange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n"; if ($$record{'resource.'.$partid.'.solved'} eq 'excused') { @@ -1709,7 +1848,8 @@ sub gradeBox { $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>'; + &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.='</tr></table>'."\n"; $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n". @@ -2091,7 +2231,7 @@ KEYWORDS $lastsubonly.="\n".'<div class="LC_grade_submission_part"><b>Part:</b> '. $display_part.' <span class="LC_internal_info">( ID '.$respid. ' )</span> '. - '<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br /><br /></div>'; + '<span class="LC_warning">'.&mt('Nothing submitted - no attempts').'</span><br /><br /></div>'; next; } foreach my $submission (@$string) { @@ -2132,7 +2272,7 @@ KEYWORDS ' )</span> '; my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record); if (@$files) { - $lastsubonly.='<br /><span class="LC_warning">'.&mt('Like all files provided by users, this file may contain viruses').'</span><br />'; + $lastsubonly.='<br /><span class="LC_warning">'.&mt('Like all files provided by users, this file may contain virusses').'</span><br />'; my $file_counter = 0; foreach my $file (@$files) { $file_counter++; @@ -2143,7 +2283,7 @@ KEYWORDS } $lastsubonly.='<b>'.&mt('Submitted Answer:').' </b>'. &cleanRecord($subval,$responsetype,$symb,$partid, - $respid,\%record,$order,undef,$uname,$udom); + $respid,\%record,$order); if ($similar) {$lastsubonly.="<br /><br />$similar\n";} $lastsubonly.='</div>'; } @@ -2272,7 +2412,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('[_1]student(s)',$ntstu); $endform.=' <input type="button" value="'.&mt('Previous').'" '. 'onClick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> '."\n". '<input type="button" value="'.&mt('Next').'" '. @@ -2355,7 +2495,7 @@ sub get_last_submission { $$returnhash{$version.':keys'}))) { $lasthash{$key}=$$returnhash{$version.':'.$key}; $timestamp = - &Apache::lonlocal::locallocaltime($$returnhash{$version.':timestamp'}); + scalar(localtime($$returnhash{$version.':timestamp'})); } } foreach my $key (keys(%lasthash)) { @@ -2793,10 +2933,8 @@ sub handback_files { $newflg.'_'.$part_resp.'_returndoc'.$file_counter, $save_file_name); if ($result !~ m|^/uploaded/|) { - $request->print('<br /><span class="LC_error">'. - &mt('An error occurred ([_1]) while trying to upload [_2].', - $result,$newflg.'_'.$part_resp.'_returndoc'.$file_counter). - '</span>'); + $request->print('<span class="LC_error">An error occurred ('.$result. + ') while trying to upload '.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'</span><br />'); } else { # mark the file as read only my @files = ($save_file_name); @@ -3015,7 +3153,6 @@ sub file_name_version_ext { sub viewgrades_js { my ($request) = shift; - my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = '); $request->print(<<VIEWJAVASCRIPT); <script type="text/javascript" language="javascript"> function writePoint(partid,weight,point) { @@ -3024,7 +3161,7 @@ sub viewgrades_js { if (point == "textval") { point = document.classgrade["TEXTVAL_"+partid].value; if (isNaN(point) || parseFloat(point) < 0) { - alert("$alertmsg"+parseFloat(point)); + alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point)); var resetbox = false; for (var i=0; i<radioButton.length; i++) { if (radioButton[i].checked) { @@ -3122,7 +3259,7 @@ sub viewgrades_js { var weight = document.classgrade["weight_"+partid].value; if (isNaN(point) || parseFloat(point) < 0) { - alert("$alertmsg"+parseFloat(point)); + alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point)); textbox.value = ""; return; } @@ -3211,19 +3348,19 @@ sub viewgrades { '<input type="hidden" name="Status" value="'.$env{'stu_status'}.'" />'."\n". '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n"; - my ($common_header,$specific_header); + my $sectionClass; + my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section')); if ($env{'form.section'} eq 'all') { - $common_header = &mt('Assign Common Grade to Class'); - $specific_header = &mt('Assign Grade to Specific Students in Class'); + $sectionClass='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'); + $sectionClass='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); + $sectionClass='Students in Section(s) [_1]'; } - $result.= '<h3>'.$common_header.'</h3>'.&Apache::loncommon::start_data_table(); + $result.= + '<h3>'. + &mt("Assign Common Grade To $sectionClass",$section_display).'</h3>'; + $result.= &Apache::loncommon::start_data_table(); #radio buttons/text box for assigning points for a section or class. #handles different parts of a problem my ($partlist,$handgrade,$responseType) = &response_type($symb); @@ -3254,8 +3391,8 @@ sub viewgrades { my $line = '<input type="text" name="TEXTVAL_'. $partid.'" size="4" '.'onChange="javascript:writePoint(\''. $partid.'\','.$weight{$partid}.',\'textval\')" /> /'. - $weight{$partid}.' '.&mt('(problem weight)').'</td>'."\n"; - $line.= '<td><b>'.&mt('Grade Status').':</b><select name="SELVAL_'.$partid.'"'. + $weight{$partid}.' (problem weight)</td>'."\n"; + $line.= '<td><select name="SELVAL_'.$partid.'"'. 'onChange="javascript:writeRadText(\''.$partid.'\','. $weight{$partid}.')"> '. '<option selected="selected"> </option>'. @@ -3270,7 +3407,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>'. + &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). &Apache::loncommon::end_data_table_row()."\n"; $ctsparts++; } @@ -3281,32 +3418,32 @@ sub viewgrades { #table listing all the students in a section/class #header of table - $result.= '<h3>'.$specific_header.'</h3>'. - &Apache::loncommon::start_data_table(). - &Apache::loncommon::start_data_table_header_row(). - '<th>'.&mt('No.').'</th>'. - '<th>'.&nameUserString('header')."</th>\n"; + $result.= '<h3>'.&mt('Assign Grade to Specific Students in '.$sectionClass, + $section_display).'</h3>'; + $result.= &Apache::loncommon::start_data_table(). + &Apache::loncommon::start_data_table_header_row(). + '<th>'.&mt('No.').'</th>'. + '<th>'.&nameUserString('header')."</th>\n"; my (@parts) = sort(&getpartlist($symb)); my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb); 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 + $display =~ s|^Number of Attempts|Tries<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}; @@ -3463,11 +3600,10 @@ sub editgrades { if ($part !~ m/^\Q$partid\E/) { next;} if ($type eq 'awarded' || $type eq 'solved') { next; } my $display=&Apache::lonnet::metadata($url,$stores.'.display'); - $display =~ s/\[Part: \Q$part\E\]//; - my $narrowtext = &mt('Tries'); - $display =~ s{Number of Attempts}{$narrowtext}; - $header .= '<th align="center">'.&mt('Old').' '.$display.'</th>'. - '<th align="center">'.&mt('New').' '.$display.'</th>'; + $display =~ s/\[Part: (\w)+\]//; + $display =~ s/Number of Attempts/Tries/; + $header .= '<th align="center">'.&mt('Old '.$display).'</th>'. + '<th align="center">'.&mt('New '.$display).'</th>'; $columns{$partid}+=2; } } @@ -3809,12 +3945,11 @@ ENDPICK } sub checkforfile_js { - my $alertmsg = &mt('Please use the browse button to select a file from your local directory.'); my $result =<<CSVFORMJS; <script type="text/javascript" language="javascript"> function checkUpload(formname) { if (formname.upfile.value == "") { - alert("$alertmsg"); + alert("Please use the browse button to select a file from your local directory."); return false; } formname.submit(); @@ -3834,7 +3969,7 @@ sub upcsvScores_form { $result.=$table; $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.'). + $result.=' <b>'.&mt('Specify a file containing the class scores for current resource'). '.</b></td></tr>'."\n"; $result.='<tr bgcolor=#ffffe6><td>'."\n"; my $upload=&mt("Upload Scores"); @@ -4088,13 +4223,12 @@ sub csvuploadassign { sub pickStudentPage { my ($request) = shift; - my $alertmsg = &mt('Please select the student you wish to grade.'); $request->print(<<LISTJAVASCRIPT); <script type="text/javascript" language="javascript"> function checkPickOne(formname) { if (radioSelection(formname.student) == null) { - alert("$alertmsg"); + alert("Please select the student you wish to grade."); return; } ptr = pullDownSelection(formname.selectpage); @@ -4129,7 +4263,7 @@ LISTJAVASCRIPT $ctr++; } $select.= '</select>'; - $result.=' <b>'.&mt('Problems from').":</b> $select<br />\n"; + $result.=&mt(' <b>Problems from:</b> [_1]',$select)."<br />\n"; $ctr=0; foreach (@$titles) { @@ -4144,13 +4278,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.=' '.&mt('<b>View Problems Text: </b> [_1]',$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.=' '.&mt('<b>Submission Details: </b>[_1]',$options); $result.=&build_section_inputs(); my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status')); @@ -4159,10 +4293,12 @@ 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.=' '.&mt('<b>Use CODE: [_1] </b>', + '<input type="text" name="CODE" value="" />'). + '<br />'."\n"; $result.=' <input type="button" '. - 'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' →" /><br />'."\n"; + 'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next->').'" /><br />'."\n"; $request->print($result); @@ -4338,7 +4474,7 @@ sub displayPage { # $request->print('match='.$1."<br />\n"); # } # $companswer =~ s|<table border=\"1\">|<table border=\"0\">|g; - $studentTable.=' <b>'.$title.'</b> <br /> <b>'.&mt('Correct answer').':</b><br />'.$companswer; + $studentTable.=' <b>'.$title.'</b> <br /> '.&mt('<b>Correct answer:</b><br />[_1]',$companswer); } my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname); @@ -4408,7 +4544,7 @@ sub displaySubByDates { my %orders; $mark{'correct_by_student'} = $checkIcon; if (!exists($$record{'1:timestamp'})) { - return '<br /> <span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br />'; + return '<br /> <span class="LC_warning">'.&mt('Nothing submitted - no attempts').'</span><br />'; } my $interaction; @@ -4986,11 +5122,11 @@ sub scantron_CODElist { =cut sub scantron_CODEunique { - my $result='<span class="LC_nobreak"> + my $result='<span style="white-space: nowrap;"> <label><input type="radio" name="scantron_CODEunique" value="yes" checked="checked" />'.&mt('Yes').' </label> </span> - <span class="LC_nobreak"> + <span style="white-space: nowrap;"> <label><input type="radio" name="scantron_CODEunique" value="no" />'.&mt('No').' </label> </span>'; @@ -5524,8 +5660,7 @@ sub scantron_parse_scanline { my ($line,$whichline,$scantron_config,$scan_data,$just_header)=@_; my %record; - my $lastpos = $env{'form.scantron_maxbubble'}*$$scantron_config{'Qlength'}; - my $questions=substr($line,$$scantron_config{'Qstart'}-1,$lastpos); # Answers + my $questions=substr($line,$$scantron_config{'Qstart'}-1); # Answers my $data=substr($line,0,$$scantron_config{'Qstart'}-1); # earlier stuff if (!($$scantron_config{'CODElocation'} eq 0 || $$scantron_config{'CODElocation'} eq 'none')) { @@ -6214,33 +6349,28 @@ sub scantron_validate_file { } } if (!$stop) { - 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>'. - '<input type="radio" name="verifyrecord" value="1" />'.&mt('Yes').'</label>'. - (' '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 />'. - '<input type="submit" name="submit" value="'.&mt('Start Grading').'" />'. - '<input type="hidden" name="command" value="scantron_process" />'."\n"); + my $warning=&scantron_warning_screen('Start Grading'); + $r->print(&mt('Validation process complete.').'<br /> +'.$warning.' +<input type="submit" name="submit" value="'.&mt('Start Grading').'" /> +<input type="hidden" name="command" value="scantron_process" /> +'); + } 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>"); } else { if ($validate_phases[$currentphase] eq 'doublebubble' || $validate_phases[$currentphase] eq 'missingbubbles') { - $r->print('<input type="button" name="submitbutton" value="'.&mt('Continue').' →" onclick="javascript:verify_bubble_radio(this.form)" />'); + $r->print('<input type="button" name="submitbutton" value="'.&mt('Continue ->').'" onclick="javascript:verify_bubble_radio(this.form)" />'); } else { - $r->print('<input type="submit" name="submit" value="'.&mt('Continue').' →" />'); + $r->print('<input type="submit" name="submit" value="'.&mt('Continue ->').'" />'); } $r->print(' '.&mt('using corrected info').' <br />'); $r->print("<input type='submit' value='".&mt("Skip")."' name='scantron_skip_record' />"); @@ -6618,14 +6748,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 +6814,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)=@_; @@ -6833,7 +6927,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='true' 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(" @@ -7290,25 +7384,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'}) && @@ -7338,84 +7413,35 @@ sub scantron_get_maxbubble { 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}) { - - my $lines; - - # TODO - make this a persistent hash not an array. - - # optionresponse, matchresponse and rankresponse type items - # render as separate sub-questions in exam mode. - if (($analysis->{$part_id.'.type'} eq 'optionresponse') || - ($analysis->{$part_id.'.type'} eq 'matchresponse') || - ($analysis->{$part_id.'.type'} eq 'rankresponse')) { - my ($numbub,$numshown); - if ($analysis->{$part_id.'.type'} eq 'optionresponse') { - if (ref($analysis->{$part_id.'.options'}) eq 'ARRAY') { - $numbub = scalar(@{$analysis->{$part_id.'.options'}}); - } - } elsif ($analysis->{$part_id.'.type'} eq 'matchresponse') { - if (ref($analysis->{$part_id.'.items'}) eq 'ARRAY') { - $numbub = scalar(@{$analysis->{$part_id.'.items'}}); - } - } elsif ($analysis->{$part_id.'.type'} eq 'rankresponse') { - if (ref($analysis->{$part_id.'.foils'}) eq 'ARRAY') { - $numbub = scalar(@{$analysis->{$part_id.'.foils'}}); - } - } - if (ref($analysis->{$part_id.'.shown'}) eq 'ARRAY') { - $numshown = scalar(@{$analysis->{$part_id.'.shown'}}); - } - my $bubbles_per_line = 10; - my $inner_bubble_lines = int($numbub/$bubbles_per_line); - if (($numbub % $bubbles_per_line) != 0) { - $inner_bubble_lines++; - } - for (my $i=0; $i<$numshown; $i++) { - $subdivided_bubble_lines{$response_number} .= - $inner_bubble_lines.','; - } - $subdivided_bubble_lines{$response_number} =~ s/,$//; - $lines = $numshown * $inner_bubble_lines; - } else { - $lines = $analysis->{"$part_id.bubble_lines"}; - } + my $symb = $resource->symb(); - $first_bubble_line{$response_number} = $bubble_line; - $bubble_lines_per_response{$response_number} = $lines; - $responsetype_per_response{$response_number} = - $analysis->{$part_id.'.type'}; - $response_number++; + my (@parts,@allparts,@possible_parts); - $bubble_line += $lines; - $total_lines += $lines; - } + # Need to retrieve part IDs and response IDs because essayresponse, + # reactionresponse and organicresponse items are not included in + # $analysis{'parts'} from lonnet::ssi. + if (ref($resource->parts()) eq 'ARRAY') { + foreach my $part (@{$resource->parts()}) { + if (!&Apache::loncommon::check_if_partid_hidden($part,$symb,$udom,$uname)) { + my @resp_ids = $resource->responseIds($part); + foreach my $id (@resp_ids) { + my $part_id = $part.'.'.$id; + push(@possible_parts,$part_id); + } + } + } } - } - &Apache::lonnet::delenv('scantron.'); - - &save_bubble_lines(); - $env{'form.scantron_maxbubble'} = - $total_lines; - 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); + my (undef, $an) = + split(/_HASH_REF__/,$result, 2); + + my %analysis = &Apache::lonnet::str2hash($an); if (ref($analysis{'parts'}) eq 'ARRAY') { foreach my $part (@{$analysis{'parts'}}) { @@ -7425,19 +7451,81 @@ sub scantron_partids_tograde { } } } - } - return (\%analysis,\@parts); -} + # Add part_ids for any essayresponse, reactionresponse or + # organicresponse items. + foreach my $part_id (@possible_parts) { + if (grep(/^\Q$part_id\E$/,@parts)) { + push(@allparts,$part_id); + } else { + if (($analysis{$part_id.'.type'} eq 'essayresponse') || + ($analysis{$part_id.'.type'} eq 'reactionresponse') || + ($analysis{$part_id.'.type'} eq 'organicresponse')) { + push(@allparts,$part_id); + } + } + } -=pod + foreach my $part_id (@allparts) { + my $lines; -=item scantron_validate_missingbubbles + # TODO - make this a persistent hash not an array. - 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. + # optionresponse, matchresponse and rankresponse type items + # render as separate sub-questions in exam mode. + if (($analysis{$part_id.'.type'} eq 'optionresponse') || + ($analysis{$part_id.'.type'} eq 'matchresponse') || + ($analysis{$part_id.'.type'} eq 'rankresponse')) { + my ($numbub,$numshown); + if ($analysis{$part_id.'.type'} eq 'optionresponse') { + if (ref($analysis{$part_id.'.options'}) eq 'ARRAY') { + $numbub = scalar(@{$analysis{$part_id.'.options'}}); + } + } elsif ($analysis{$part_id.'.type'} eq 'matchresponse') { + if (ref($analysis{$part_id.'.items'}) eq 'ARRAY') { + $numbub = scalar(@{$analysis{$part_id.'.items'}}); + } + } elsif ($analysis{$part_id.'.type'} eq 'rankresponse') { + if (ref($analysis{$part_id.'.foils'}) eq 'ARRAY') { + $numbub = scalar(@{$analysis{$part_id.'.foils'}}); + } + } + if (ref($analysis{$part_id.'.shown'}) eq 'ARRAY') { + $numshown = scalar(@{$analysis{$part_id.'.shown'}}); + } + my $bubbles_per_line = 10; + my $inner_bubble_lines = int($numbub/$bubbles_per_line); + if (($numbub % $bubbles_per_line) != 0) { + $inner_bubble_lines++; + } + for (my $i=0; $i<$numshown; $i++) { + $subdivided_bubble_lines{$response_number} .= + $inner_bubble_lines.','; + } + $subdivided_bubble_lines{$response_number} =~ s/,$//; + $lines = $numshown * $inner_bubble_lines; + } else { + $lines = $analysis{"$part_id.bubble_lines"}; + } + + $first_bubble_line{$response_number} = $bubble_line; + $bubble_lines_per_response{$response_number} = $lines; + $responsetype_per_response{$response_number} = + $analysis{$part_id.'.type'}; + $response_number++; + + $bubble_line += $lines; + $total_lines += $lines; + } + + } + &Apache::lonnet::delenv('scantron\.'); + + &save_bubble_lines(); + $env{'form.scantron_maxbubble'} = + $total_lines; + return $env{'form.scantron_maxbubble'}; +} -=cut sub scantron_validate_missingbubbles { my ($r,$currentphase) = @_; @@ -7492,29 +7580,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,14 +7598,6 @@ 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); - 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; - } # $r->print("geto ".scalar(@resources)."<br />"); my $result= <<SCANTRONFORM; <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload"> @@ -7550,7 +7607,7 @@ SCANTRONFORM $r->print($result); my @delayqueue; - my (%completedstudents,,%scandata); + my %completedstudents; my $lock=&Apache::lonnet::set_lock(&mt('Grading bubblesheet exam')); my $count=&get_todo_count($scanlines,$scan_data); @@ -7559,10 +7616,9 @@ SCANTRONFORM 'inline',undef,'scantronupload'); &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state, 'Processing first student'); - $r->print('<br />'); my $start=&Time::HiRes::time(); my $i=-1; - my $started; + my ($uname,$udom,$started); &scantron_get_maxbubble(); # Need the bubble lines array to parse. @@ -7578,9 +7634,6 @@ SCANTRONFORM return ''; # Dunno why the other returns return '' rather than just returning. } - my %lettdig = &letter_to_digits(); - my $numletts = scalar(keys(%lettdig)); - while ($i<$scanlines->{'count'}) { ($uname,$udom)=('',''); $i++; @@ -7612,95 +7665,40 @@ SCANTRONFORM if (&scantron_clear_skip($scanlines,$scan_data,$i)) { &scantron_putfile($scanlines,$scan_data); } + + my $i=0; + foreach my $resource (@resources) { + $i++; + my %form=('submitted' =>'scantron', + 'grade_target' =>'grade', + 'grade_username'=>$uname, + 'grade_domain' =>$udom, + 'grade_courseid'=>$env{'request.course.id'}, + 'grade_symb' =>$resource->symb()); + if (exists($scan_record->{'scantron.CODE'}) + && + &Apache::lonnet::validCODE($scan_record->{'scantron.CODE'})) { + $form{'CODE'}=$scan_record->{'scantron.CODE'}; + } else { + $form{'CODE'}=''; + } + my $result=&ssi_with_retries($resource->src(), $ssi_retries, %form); + if ($ssi_error) { + $ssi_error = 0; # So end of handler error message does not trigger. + $r->print("</form>"); + &ssi_print_error($r); + $r->print(&show_grading_menu_form($symb)); + &Apache::lonnet::remove_lock($lock); + return ''; # Why return ''? Beats me. + } - 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) eq 'ssi_error') { - $ssi_error = 0; # So end of handler error message does not trigger. - $r->print("</form>"); - &ssi_print_error($r); - $r->print(&show_grading_menu_form($symb)); - &Apache::lonnet::remove_lock($lock); - return ''; # Why return ''? Beats me. - } - + if (&Apache::loncommon::connection_aborted($r)) { last; } + } $completedstudents{$uname}={'line'=>$line}; - if ($env{'form.verifyrecord'}) { - my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'}; - my $studentdata = substr($line,$scantron_config{'Qstart'}-1,$lastpos); - chomp($studentdata); - $studentdata =~ s/\r$//; - my $studentrecord = ''; - my $counter = -1; - foreach my $resource (@resources) { - ($counter,my $recording) = - &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'}, - $counter,$studentdata,\%partids_by_symb, - \%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') { - $ssi_error = 0; # So end of handler error message does not trigger. - $r->print("</form>"); - &ssi_print_error($r); - $r->print(&show_grading_menu_form($symb)); - &Apache::lonnet::remove_lock($lock); - delete($completedstudents{$uname}); - return ''; - } - $counter = -1; - $studentrecord = ''; - foreach my $resource (@resources) { - ($counter,my $recording) = - &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'}, - $counter,$studentdata,\%partids_by_symb, - \%scantron_config,\%lettdig,$numletts); - $studentrecord .= $recording; - } - if ($studentrecord ne $studentdata) { - $r->print('<p><span class="LC_error">'); - if ($scancode eq '') { - $r->print(&mt('Mismatch grading bubble sheet for user: [_1] with ID: [_2].', - $uname.':'.$udom,$scan_record->{'scantron.ID'})); - } else { - $r->print(&mt('Mismatch grading bubble sheet for user: [_1] with ID: [_2] and CODE: [_3].', - $uname.':'.$udom,$scan_record->{'scantron.ID'},$scancode)); - } - $r->print('</span><br />'.&Apache::loncommon::start_data_table()."\n". - &Apache::loncommon::start_data_table_header_row()."\n". - '<th>'.&mt('Source').'</th><th>'.&mt('Bubbled responses').'</th>'. - &Apache::loncommon::end_data_table_header_row()."\n". - &Apache::loncommon::start_data_table_row(). - '<td>'.&mt('Bubble Sheet').'</td>'. - '<td><span class="LC_nobreak">'.$studentdata.'</span></td>'. - &Apache::loncommon::end_data_table_row(). - &Apache::loncommon::start_data_table_row(). - '<td>Stored submissions</td>'. - '<td><span class="LC_nobreak">'.$studentrecord.'</span></td>'."\n". - &Apache::loncommon::end_data_table_row(). - &Apache::loncommon::end_data_table().'</p>'); - } else { - $r->print('<br /><span class="LC_warning">'. - &mt('A second grading pass was needed for user: [_1] with ID: [_2], because a mismatch was seen on the first pass.',$uname.':'.$udom,$scan_record->{'scantron.ID'}).'<br />'. - &mt("As a consequence, this user's submission history records two tries."). - '</span><br />'); - } - } - } if (&Apache::loncommon::connection_aborted($r)) { last; } } continue { &Apache::lonxml::clear_problem_counter(); - &Apache::lonnet::delenv('scantron.'); + &Apache::lonnet::delenv('scantron\.'); } &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); &Apache::lonnet::remove_lock($lock); @@ -7712,31 +7710,6 @@ 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)); - } - return; -} - -=pod - -=item scantron_upload_scantron_data - - Creates the screen for adding a new bubble sheet data file to a course. - -=cut - sub scantron_upload_scantron_data { my ($r)=@_; $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'})); @@ -7750,7 +7723,7 @@ sub scantron_upload_scantron_data { <script type="text/javascript" language="javascript"> function checkUpload(formname) { if (formname.upfile.value == "") { - alert("'.&mt('Please use the browse button to select a file from your local directory.').'"); + alert("Please use the browse button to select a file from your local directory."); return false; } formname.submit(); @@ -7777,14 +7750,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)=@_; @@ -7846,14 +7811,6 @@ sub scantron_upload_scantron_data_save { return ''; } -=pod - -=item valid_file - - Validates that the requested bubble data file exists in the course. - -=cut - sub valid_file { my ($requested_file)=@_; foreach my $filename (sort(&scantron_filenames())) { @@ -7862,16 +7819,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)); @@ -7917,7 +7864,18 @@ sub checkscantron_results { if (!$symb) {return '';} my $grading_menu_button=&show_grading_menu_form($symb); my $cid = $env{'request.course.id'}; - my %lettdig = &letter_to_digits(); + my %lettdig = ( + A => 1, + B => 2, + C => 3, + D => 4, + E => 5, + F => 6, + G => 7, + H => 8, + I => 9, + J => 0, + ); my $numletts = scalar(keys(%lettdig)); my $cnum = $env{'course.'.$cid.'.num'}; my $cdom = $env{'course.'.$cid.'.domain'}; @@ -7931,13 +7889,6 @@ sub checkscantron_results { 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 (%scandata,%lastname,%bylast); $r->print(' <form method="post" enctype="multipart/form-data" action="/adm/grades" name="checkscantron">'."\n"); @@ -7949,7 +7900,7 @@ sub checkscantron_results { my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron/Submissions Comparison Status', 'Progress of Scantron Data/Submission Records Comparison',$count, 'inline',undef,'checkscantron'); - my ($username,$domain,$started); + my ($username,$domain,$uname,$started); &Apache::grades::scantron_get_maxbubble(); # Need the bubble lines array to parse. @@ -7991,12 +7942,126 @@ sub checkscantron_results { $scandata{$pid} =~ s/\r$//; ($username,$domain)=split(/:/,$uname); my $counter = -1; + my (%expected,%startpos); foreach my $resource (@resources) { - ($counter,my $recording) = - &verify_scantron_grading($resource,$domain,$username,$cid,$counter, - $scandata{$pid},\%partids_by_symb, - \%scantron_config,\%lettdig,$numletts); - $record{$pid} .= $recording; + next if (!$resource->is_problem()); + my $symb = $resource->symb(); + my $partsref = $resource->parts(); + my @parts; + my @part_ids = (); + if (ref($partsref) eq 'ARRAY') { + @parts = @{$partsref}; + foreach my $part (@parts) { + my @resp_ids = $resource->responseIds($part); + foreach my $resp (@resp_ids) { + $counter ++; + my $part_id = $part.'.'.$resp; + $expected{$part_id} = 0; + push(@part_ids,$part_id); + if ($env{"form.scantron.sub_bubblelines.$counter"}) { + my @sub_lines = split(/,/,$env{"form.scantron.sub_bubblelines.$counter"}); + foreach my $item (@sub_lines) { + $expected{$part_id} += $item; + } + } else { + $expected{$part_id} = $env{"form.scantron.bubblelines.$counter"}; + } + $startpos{$part_id} = $env{"form.scantron.first_bubble_line.$counter"}; + } + } + } + if ($symb) { + my %recorded; + my (%returnhash) = + &Apache::lonnet::restore($symb,$cid,$domain,$username); + if ($returnhash{'version'}) { + my %lasthash=(); + my $version; + for ($version=1;$version<=$returnhash{'version'};$version++) { + foreach my $key (sort(split(/\:/,$returnhash{$version.':keys'}))) { + $lasthash{$key}=$returnhash{$version.':'.$key}; + } + } + foreach my $key (keys(%lasthash)) { + if ($key =~ /\.scantron$/) { + my $value = &unescape($lasthash{$key}); + my ($part_id) = ($key =~ /^resource\.(.+)\.scantron$/); + if ($value eq '') { + for (my $i=0; $i<$expected{$part_id}; $i++) { + for (my $j=0; $j<$scantron_config{'length'}; $j++) { + $recorded{$part_id} .= $; + } + } + } else { + my @tocheck; + my @items = split(//,$value); + if (($scantron_config{'Qon'} eq 'letter') || + ($scantron_config{'Qon'} eq 'number')) { + if (@items < $expected{$part_id}) { + my $fragment = substr($scandata{$pid},$startpos{$part_id},$expected{$part_id}); + my @singles = split(//,$fragment); + foreach my $pos (@singles) { + if ($pos eq ' ') { + push(@tocheck,$pos); + } else { + my $next = shift(@items); + push(@tocheck,$next); + } + } + } else { + @tocheck = @items; + } + foreach my $letter (@tocheck) { + if ($scantron_config{'Qon'} eq 'letter') { + if ($letter !~ /^[A-J]$/) { + $letter = $scantron_config{'Qoff'}; + } + $recorded{$part_id} .= $letter; + } elsif ($scantron_config{'Qon'} eq 'number') { + my $digit; + if ($letter !~ /^[A-J]$/) { + $digit = $scantron_config{'Qoff'}; + } else { + $digit = $lettdig{$letter}; + } + $recorded{$part_id} .= $digit; + } + } + } else { + @tocheck = @items; + for (my $i=0; $i<$expected{$part_id}; $i++) { + my $curr_sub = shift(@tocheck); + my $digit; + if ($curr_sub =~ /^[A-J]$/) { + $digit = $lettdig{$curr_sub}-1; + } + if ($curr_sub eq 'J') { + $digit += scalar($numletts); + } + for (my $j=0; $j<$scantron_config{'Qlength'}; $j++) { + if ($j == $digit) { + $recorded{$part_id} .= $scantron_config{'Qon'}; + } else { + $recorded{$part_id} .= $scantron_config{'Qoff'}; + } + } + } + } + } + } + } + } + foreach my $part_id (@part_ids) { + if ($recorded{$part_id} eq '') { + for (my $i=0; $i<$expected{$part_id}; $i++) { + for (my $j=0; $j<$scantron_config{'Qlength'}; $j++) { + $recorded{$part_id} .= $scantron_config{'Qoff'}; + } + } + } + $record{$pid} .= $recorded{$part_id}; + } + } } } &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); @@ -8058,143 +8123,6 @@ sub checkscantron_results { return; } -sub verify_scantron_grading { - my ($resource,$domain,$username,$cid,$counter,$scandata,$partids_by_symb, - $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}}) { - $counter ++; - $expected{$part_id} = 0; - if ($env{"form.scantron.sub_bubblelines.$counter"}) { - my @sub_lines = split(/,/,$env{"form.scantron.sub_bubblelines.$counter"}); - foreach my $item (@sub_lines) { - $expected{$part_id} += $item; - } - } else { - $expected{$part_id} = $env{"form.scantron.bubblelines.$counter"}; - } - $startpos{$part_id} = $env{"form.scantron.first_bubble_line.$counter"}; - } - if ($symb) { - my %recorded; - my (%returnhash) = &Apache::lonnet::restore($symb,$cid,$domain,$username); - if ($returnhash{'version'}) { - my %lasthash=(); - my $version; - for ($version=1;$version<=$returnhash{'version'};$version++) { - foreach my $key (sort(split(/\:/,$returnhash{$version.':keys'}))) { - $lasthash{$key}=$returnhash{$version.':'.$key}; - } - } - foreach my $key (keys(%lasthash)) { - if ($key =~ /\.scantron$/) { - my $value = &unescape($lasthash{$key}); - my ($part_id) = ($key =~ /^resource\.(.+)\.scantron$/); - if ($value eq '') { - for (my $i=0; $i<$expected{$part_id}; $i++) { - for (my $j=0; $j<$scantron_config->{'length'}; $j++) { - $recorded{$part_id} .= $scantron_config->{'Qoff'}; - } - } - } else { - my @tocheck; - my @items = split(//,$value); - if (($scantron_config->{'Qon'} eq 'letter') || - ($scantron_config->{'Qon'} eq 'number')) { - if (@items < $expected{$part_id}) { - my $fragment = substr($scandata,$startpos{$part_id},$expected{$part_id}); - my @singles = split(//,$fragment); - foreach my $pos (@singles) { - if ($pos eq ' ') { - push(@tocheck,$pos); - } else { - my $next = shift(@items); - push(@tocheck,$next); - } - } - } else { - @tocheck = @items; - } - foreach my $letter (@tocheck) { - if ($scantron_config->{'Qon'} eq 'letter') { - if ($letter !~ /^[A-J]$/) { - $letter = $scantron_config->{'Qoff'}; - } - $recorded{$part_id} .= $letter; - } elsif ($scantron_config->{'Qon'} eq 'number') { - my $digit; - if ($letter !~ /^[A-J]$/) { - $digit = $scantron_config->{'Qoff'}; - } else { - $digit = $lettdig->{$letter}; - } - $recorded{$part_id} .= $digit; - } - } - } else { - @tocheck = @items; - for (my $i=0; $i<$expected{$part_id}; $i++) { - my $curr_sub = shift(@tocheck); - my $digit; - if ($curr_sub =~ /^[A-J]$/) { - $digit = $lettdig->{$curr_sub}-1; - } - if ($curr_sub eq 'J') { - $digit += scalar($numletts); - } - for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) { - if ($j == $digit) { - $recorded{$part_id} .= $scantron_config->{'Qon'}; - } else { - $recorded{$part_id} .= $scantron_config->{'Qoff'}; - } - } - } - } - } - } - } - } - foreach my $part_id (@{$partids_by_symb->{$symb}}) { - if ($recorded{$part_id} eq '') { - for (my $i=0; $i<$expected{$part_id}; $i++) { - for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) { - $recorded{$part_id} .= $scantron_config->{'Qoff'}; - } - } - } - $record .= $recorded{$part_id}; - } - } - return ($counter,$record); -} - -sub letter_to_digits { - my %lettdig = ( - A => 1, - B => 2, - C => 3, - D => 4, - E => 5, - F => 6, - G => 7, - H => 8, - I => 9, - J => 0, - ); - return %lettdig; -} - -=pod - -=back - -=cut #-------- end of section for handling grading scantron forms ------- # @@ -8303,7 +8231,6 @@ sub grading_menu { "\n"; } $Str .="</form>\n"; - my $receiptalert = &mt("Please enter a receipt number given by a student in the receipt box."); $request->print(<<GRADINGMENUJS); <script type="text/javascript" language="javascript"> function checkChoice(formname,val,cmdx) { @@ -8331,7 +8258,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("Please enter a receipt number given by a student in the receipt box."); formname.receipt.value = ""; formname.receipt.focus(); return false; @@ -8352,7 +8279,6 @@ sub submit_options { if (!$symb) {return '';} my $probTitle = &Apache::lonnet::gettitle($symb); - my $receiptalert = &mt("Please enter a receipt number given by a student in the receipt box."); $request->print(<<GRADINGMENUJS); <script type="text/javascript" language="javascript"> function checkChoice(formname,val,cmdx) { @@ -8380,7 +8306,7 @@ sub submit_options { if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;} if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;} if (checkOpt) { - alert("$receiptalert"); + alert("Please enter a receipt number given by a student in the receipt box."); formname.receipt.value = ""; formname.receipt.focus(); return false; @@ -8480,7 +8406,7 @@ GRADINGMENUJS </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> @@ -8499,7 +8425,7 @@ GRADINGMENUJS </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> @@ -8603,7 +8529,7 @@ sub process_clicker { $result.=$table; $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.'). + $result.=' <b>'.&mt('Specify a file containing the clicker information for this resource'). '.</b></td></tr>'."\n"; $result.='<tr bgcolor=#ffffe6><td>'."\n"; # Attempt to restore parameters from last session, set defaults if not present @@ -9039,7 +8965,7 @@ ENDHEADER } } # We are done - $result.='<br />'.&mt('Successfully stored grades for [quant,_1,student].',$storecount). + $result.='<br />'.&mt('Successfully stored grades for [_1] student(s).',$storecount). '</td></tr></table>'."\n". '</td></tr></table><br /><br />'."\n"; return $result.&show_grading_menu_form($symb);