--- loncom/homework/grades.pm 2009/03/20 19:15:29 1.528.2.11 +++ loncom/homework/grades.pm 2009/04/15 17:02:35 1.561 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.528.2.11 2009/03/20 19:15:29 raeburn Exp $ +# $Id: grades.pm,v 1.561 2009/04/15 17:02:35 bisitz 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.=''.&mt('Part').': '.$display_part. - ' '.$resID.''. - ''.&mt('Type').': '.$responsetype.''; + ' '.$resID.''. + ''.&mt('Type').': '.$responsetype.''; # ''.&mt('Handgrade: [_1]',$handgrade).''; } } @@ -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.= '

'. - &mt('Verifying Receipt No. [_1]',$receipt). + &mt('Verifying Receipt No. [_1]',$receipt). '

'."\n". '

'.&mt('Resource: [_1]',$env{'form.probTitle'}). '

'."\n"; @@ -824,16 +838,16 @@ sub listStudents { $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'}; - my $result='

 '. - &mt("$viewgrade Submissions for a Student or a Group of Students") + my $result='

 ' + .&mt("$viewgrade Submissions for a Student or a Group of Students") .'

'; 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(< function checkSelect(checkBox) { @@ -875,16 +889,17 @@ LISTJAVASCRIPT my $gradeTable='
'. "\n".$table; - $gradeTable .= - ' '.&mt('View Problem Text').': '. - ''."\n". - ''."\n". - '
'."\n"; - $gradeTable .= - ' '.&mt('View Answer').': '. - ''."\n". - ''."\n". - '
'."\n"; + $gradeTable .= &Apache::lonhtmlcommon::start_pick_box(); + $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Problem Text')) + .''."\n" + .''."\n" + .'
'."\n" + .&Apache::lonhtmlcommon::row_closure(); + $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Answer')) + .''."\n" + .''."\n" + .'
'."\n" + .&Apache::lonhtmlcommon::row_closure(); my $submission_options; if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) { @@ -899,19 +914,20 @@ LISTJAVASCRIPT ''."\n". ''."\n". ''; - $gradeTable .= - ' '.&mt('Submissions').': '.$submission_options.'
'."\n"; + $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Submissions')) + .$submission_options + .&Apache::lonhtmlcommon::row_closure(); + + $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Grading Increments')) + .'' + .&Apache::lonhtmlcommon::row_closure(); $gradeTable .= - ' '.&mt('Grading Increments').': '. - ''; - - $gradeTable .= &build_section_inputs(). ''."\n". '
'."\n". @@ -922,14 +938,23 @@ LISTJAVASCRIPT ''."\n"; if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) { - $gradeTable.=''."\n"; + $gradeTable .= ''."\n"; } else { - $gradeTable.=&mt('Student Status: [_1]', - &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);')).'
'; + $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.").'
'."\n". - ''."\n"; + $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Check For Plagiarism')) + .'' + .&Apache::lonhtmlcommon::row_closure(1) + .&Apache::lonhtmlcommon::end_pick_box(); + + $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.")."\n" + .'' + .'

'; # checkall buttons $gradeTable.=&check_script('gradesub', 'stuinfo'); @@ -937,7 +962,6 @@ LISTJAVASCRIPT 'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n". 'value="'.&mt('Next').' →" />
'."\n"; $gradeTable.=&check_buttons(); - $gradeTable.=''; my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup); $gradeTable.= &Apache::loncommon::start_data_table(). &Apache::loncommon::start_data_table_header_row(); @@ -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(< function updateRadio(formname,id,weight) { @@ -1708,8 +1732,9 @@ sub gradeBox { $line.=''."\n"; + #&mt('Part:[_1]Points:[_2]or[_3]',$display_part,$radio,$line); $result .= - ''.&mt('Part').':'.$display_part.''.&mt('Points').':'.$radio.''.&mt('or').''.$line.''; + ''.&mt('Part').':'.$display_part.''.&mt('Points').':'.$radio.''.&mt('or').''.$line.''. $result.=''."\n"; $result.=''."\n". @@ -2369,7 +2394,7 @@ sub get_last_submission { } if (!@string) { $string[0] = - 'Nothing submitted - no attempts.'; + ''.&mt('Nothing submitted - no attempts.').''; } return (\@string,\$timestamp); } @@ -3213,15 +3238,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.= '

'.$common_header.'

'.&Apache::loncommon::start_data_table(); #radio buttons/text box for assigning points for a section or class. @@ -3270,7 +3295,7 @@ sub viewgrades { $result.= &Apache::loncommon::start_data_table_row()."\n". - ''.&mt('Part').':'.$display_part.''.&mt('Points').':'.$radio.''.&mt('or').''.$line.''. + ''.&mt('Part').':'.$display_part.''.&mt('Points').':'.$radio.''.&mt('or').''.$line.''. &Apache::loncommon::end_data_table_row()."\n"; $ctsparts++; } @@ -3291,22 +3316,22 @@ sub viewgrades { my @partids = (); foreach my $part (@parts) { my $display=&Apache::lonnet::metadata($url,$part.'.display'); - my $narrowtext = &mt('Tries').'
'; - $display =~ s{^Number of Attempts}{$narrowtext}; # makes the column narrower + my $narrowtext = &mt('Tries'); + $display =~ s|^Number of Attempts|$narrowtext
|; # 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.=''. - &mt('Score Part: [_1] (weight = [_2])', - $display_part.'
',$weight{$partid}).''."\n"; + &mt('Score Part: [_1]
(weight = [_2])', + $display_part,$weight{$partid}).''."\n"; next; } else { if ($display =~ /Problem Status/) { - my $grade_status_mt = &mt('Grade Status').'
'; - $display =~ s{Problem Status}{$grade_status_mt}; + my $grade_status_mt = &mt('Grade Status'); + $display =~ s{Problem Status}{$grade_status_mt
}; } my $part_mt = &mt('Part:'); $display =~s{\[Part: \Q$partid\E\]}{$part_mt $display_part}; @@ -3465,9 +3490,9 @@ 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}; - $header .= ''.&mt('Old').' '.$display).''. - ''.&mt('New').' '.$display).''; + $display =~ s/Number of Attempts/$narrowtext/; + $header .= ''.&mt('Old').' '.$display.''. + ''.&mt('New').' '.$display.''; $columns{$partid}+=2; } } @@ -3779,7 +3804,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 +3860,7 @@ sub upcsvScores_form { $result.='
'."\n"; $result.=''."\n"; + ''."\n"; $result.='
'."\n"; $result.=' '.&mt('Specify a file containing the class scores for current resource.'). - '.
'."\n"; my $upload=&mt("Upload Scores"); my $upfile_select=&Apache::loncommon::upfile_select_html(); @@ -4129,7 +4154,7 @@ LISTJAVASCRIPT $ctr++; } $select.= ''; - $result.=' '.&mt('Problems from').": $select
\n"; + $result.=' '.&mt('Problems from').': '.$select."
\n"; $ctr=0; foreach (@$titles) { @@ -4144,13 +4169,13 @@ LISTJAVASCRIPT my $options = ''."\n". ''."
\n"; - $result.=' '.&mt('View Problem Text').": $options"; + $result.=' '.&mt('View Problem Text').': '.$options; $options = ''."\n". ''."\n". ''."\n"; - $result.=' >b>'.&mt('Submissions').": $options"; + $result.=' '.&mt('Submissions').': '.$options; $result.=&build_section_inputs(); my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status')); @@ -4159,7 +4184,7 @@ LISTJAVASCRIPT ''."\n". ''."
\n"; - $result.=' '.&mt('Use CODE').':
'."\n"; + $result.=' '.&mt('Use CODE').':
'."\n"; $result.=' 
'."\n"; @@ -4201,7 +4226,7 @@ LISTJAVASCRIPT } $studentTable.=&Apache::loncommon::end_data_table()."\n"; $studentTable.=''."\n"; + 'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' →" />'."\n"; $studentTable.=&show_grading_menu_form($symb); $request->print($studentTable); @@ -4338,7 +4363,7 @@ sub displayPage { # $request->print('match='.$1."
\n"); # } # $companswer =~ s||
|g; - $studentTable.=' '.$title.' 
 '.&mt('Correct answer').':
'.$companswer); + $studentTable.=' '.$title.' 
 '.&mt('Correct answer').':
'.$companswer; } my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname); @@ -4692,10 +4717,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 +4790,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 +4831,6 @@ sub restore_bubble_lines { $env{"form.scantron.responsetype.$line"}; $line++; } - } # Given the parsed scanline, get the response for @@ -4815,7 +4839,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}; @@ -5171,6 +5194,10 @@ sub scantron_selectphase { ''."\n". &Apache::loncommon::end_data_table_row()."\n". &Apache::loncommon::start_data_table_row()."\n". + ''."\n". + ''. + &Apache::loncommon::end_data_table_row()."\n". + &Apache::loncommon::start_data_table_row()."\n". '
'.$format_selector.' '.&mt('Options').' '."\n". ''."\n". ''."\n". @@ -5214,8 +5241,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 number 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 +5302,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 +5341,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 number 'CODE' -> correct the CODE 'answer' -> fixup the submitted answers) @@ -5488,7 +5515,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 +6241,8 @@ sub scantron_validate_file { } } if (!$stop) { - my $warning=&scantron_warning_screen('Start Grading'); - $r->print(&mt('Validation process complete.').'
'. + my $warning=&scantron_warning_screen('Start Grading'); + $r->print(&mt('Validation process complete.').'
'. $warning. &mt('Perform verification for each student after storage of submissions?'). ' 
'. - &mt('Grading will take longer if you use verification.').'
'. &mt("Alternatively, the 'Review scantron data' utility (see grading menu) can be used for all students after grading is complete.").'

'. + &mt('Grading will take longer if you use verification.').'
'. + &mt("Alternatively, the 'Review scantron data' utility (see grading menu) can be used for all students after grading is complete.").'

'. ''. ''."\n"); } else { - $r->print(''); - $r->print(""); + $r->print(''); + $r->print(""); } if ($stop) { if ($validate_phases[$currentphase] eq 'sequence') { - $r->print(''); + $r->print(''); $r->print(' '.&mt('this error').'
'); $r->print("

".&mt("Or click the 'Grading Menu' button to start over.")."

"); @@ -6618,14 +6646,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 +6712,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 +6825,7 @@ ENDSCRIPT ".&mt("[_1]Select[_2] a CODE from the list of all CODEs and use it.", "","")." - ".&mt('Selected CODE is [_1]','')); + ".&mt("Selected CODE is [_1]",'')); $r->print("\n
"); } $r->print(" @@ -7290,25 +7282,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 +7299,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 +7374,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 +7427,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 +7445,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)."
"); + + my ($uname,$udom); my $result= < @@ -7550,7 +7471,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 +7486,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 +7526,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 +7555,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(""); &ssi_print_error($r); @@ -7640,16 +7573,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(""); &ssi_print_error($r); @@ -7661,9 +7595,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 +7632,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,30 +7647,57 @@ 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)=@_; @@ -7750,7 +7712,7 @@ sub scantron_upload_scantron_data {