--- loncom/homework/grades.pm 2007/08/22 18:27:03 1.433 +++ loncom/homework/grades.pm 2007/10/17 10:48:20 1.461 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.433 2007/08/22 18:27:03 banghart Exp $ +# $Id: grades.pm,v 1.461 2007/10/17 10:48:20 foxr Exp $ # # Copyright Michigan State University Board of Trustees # @@ -35,6 +35,7 @@ use Apache::loncommon; use Apache::lonhtmlcommon; use Apache::lonnavmaps; use Apache::lonhomework; +use Apache::lonpickcode; use Apache::loncoursedata; use Apache::lonmsg(); use Apache::Constants qw(:common); @@ -45,35 +46,104 @@ use LONCAPA; use POSIX qw(floor); + my %perm=(); +my %bubble_lines_per_response = (); # no. bubble lines for each response. + # index is "symb.part_id" + +my %first_bubble_line = (); # First bubble line no. for each bubble. + +# Save and restore the bubble lines array to the form env. + + +sub save_bubble_lines { + foreach my $line (keys(%bubble_lines_per_response)) { + $env{"form.scantron.bubblelines.$line"} = $bubble_lines_per_response{$line}; + $env{"form.scantron.first_bubble_line.$line"} = + $first_bubble_line{$line}; + } +} + + +sub restore_bubble_lines { + my $line = 0; + %bubble_lines_per_response = (); + while ($env{"form.scantron.bubblelines.$line"}) { + my $value = $env{"form.scantron.bubblelines.$line"}; + $bubble_lines_per_response{$line} = $value; + $first_bubble_line{$line} = + $env{"form.scantron.first_bubble_line.$line"}; + $line++; + } + +} + +# Given the parsed scanline, get the response for +# 'answer' number n: + +sub get_response_bubbles { + my ($parsed_line, $response) = @_; + + + my $bubble_line = $first_bubble_line{$response-1} +1; + my $bubble_lines= $bubble_lines_per_response{$response-1}; + + my $selected = ""; + + for (my $bline = 0; $bline < $bubble_lines; $bline++) { + $selected .= $$parsed_line{"scantron.$bubble_line.answer"}.":"; + $bubble_line++; + } + return $selected; +} + # ----- These first few routines are general use routines.---- + +# Return the number of occurences of a pattern in a string. + +sub occurence_count { + my ($string, $pattern) = @_; + + my @matches = ($string =~ /$pattern/g); + + return scalar(@matches); +} + + +# Take a string known to have digits and convert all the +# digits into letters in the range J,A..I. + +sub digits_to_letters { + my ($input) = @_; + + my @alphabet = ('J', 'A'..'I'); + + my @input = split(//, $input); + my $output =''; + for (my $i = 0; $i < scalar(@input); $i++) { + if ($input[$i] =~ /\d/) { + $output .= $alphabet[$input[$i]]; + } else { + $output .= $input[$i]; + } + } + return $output; +} + # # --- Retrieve the parts from the metadata file.--- sub getpartlist { my ($symb) = @_; - my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb); - my $partorder = &Apache::lonnet::metadata($url, 'partorder'); - my @parts; - if ($partorder) { - for my $part (split (/,/,$partorder)) { - if (!&Apache::loncommon::check_if_partid_hidden($part,$symb)) { - push(@parts, $part); - } - } - } else { - my $metadata = &Apache::lonnet::metadata($url, 'packages'); - foreach (split(/\,/,$metadata)) { - if ($_ =~ /^part_(.*)$/) { - if (!&Apache::loncommon::check_if_partid_hidden($1,$symb)) { - push(@parts, $1); - } - } - } - } + + my $navmap = Apache::lonnavmaps::navmap->new(); + my $res = $navmap->getBySymb($symb); + my $partlist = $res->parts(); + my $url = $res->src(); + my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys')); + my @stores; - foreach my $part (@parts) { - my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys')); + foreach my $part (@{ $partlist }) { foreach my $key (@metakeys) { if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); } } @@ -194,22 +264,54 @@ sub showResourceInfo { return $result,$responseType,$hdgrade,$partlist,$handgrade; } +sub reset_caches { + &reset_analyze_cache(); + &reset_perm(); +} -sub get_order { - my ($partid,$respid,$symb,$uname,$udom)=@_; - my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb); - $url=&Apache::lonnet::clutter($url); - my $subresult=&Apache::lonnet::ssi($url, - ('grade_target' => 'analyze'), - ('grade_domain' => $udom), - ('grade_symb' => $symb), - ('grade_courseid' => - $env{'request.course.id'}), - ('grade_username' => $uname)); - (undef,$subresult)=split(/_HASH_REF__/,$subresult,2); - my %analyze=&Apache::lonnet::str2hash($subresult); - return ($analyze{"$partid.$respid.shown"}); +{ + my %analyze_cache; + + sub reset_analyze_cache { + undef(%analyze_cache); + } + + sub get_analyze { + my ($symb,$uname,$udom)=@_; + my $key = "$symb\0$uname\0$udom"; + return $analyze_cache{$key} if (exists($analyze_cache{$key})); + + my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb); + $url=&Apache::lonnet::clutter($url); + my $subresult=&Apache::lonnet::ssi($url, + ('grade_target' => 'analyze'), + ('grade_domain' => $udom), + ('grade_symb' => $symb), + ('grade_courseid' => + $env{'request.course.id'}), + ('grade_username' => $uname)); + (undef,$subresult)=split(/_HASH_REF__/,$subresult,2); + my %analyze=&Apache::lonnet::str2hash($subresult); + return $analyze_cache{$key} = \%analyze; + } + + sub get_order { + my ($partid,$respid,$symb,$uname,$udom)=@_; + my $analyze = &get_analyze($symb,$uname,$udom); + return $analyze->{"$partid.$respid.shown"}; + } + + sub get_radiobutton_correct_foil { + my ($partid,$respid,$symb,$uname,$udom)=@_; + my $analyze = &get_analyze($symb,$uname,$udom); + foreach my $foil (@{&get_order($partid,$respid,$symb,$uname,$udom)}) { + if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') { + return $foil; + } + } + } } + #--- Clean response type for display #--- Currently filters option/rank/radiobutton/match/essay/Task # response types only. @@ -258,11 +360,11 @@ sub cleanRecord { } elsif ($response eq 'radiobutton') { my %answer=&Apache::lonnet::str2hash($answer); my ($toprow,$bottomrow); - my $correct=($order->[0])+1; - for (my $i=1;$i<=$#$order;$i++) { - my $foil=$order->[$i]; + my $correct = + &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom); + foreach my $foil (@$order) { if (exists($answer{$foil})) { - if ($i == $correct) { + if ($foil eq $correct) { $toprow.='<td><b>true</b></td>'; } else { $toprow.='<td><i>true</i></td>'; @@ -326,7 +428,10 @@ sub cleanRecord { $result.='</ul>'; return $result; } - + } elsif ( $response =~ m/(?:numerical|formula)/) { + $answer = + &Apache::loncommon::format_previous_attempt_value('submission', + $answer); } return $answer; } @@ -370,8 +475,10 @@ COMMONJSFUNCTIONS #--- Dumps the class list with usernames,list of sections, #--- section, ids and fullnames for each user. sub getclasslist { - my ($getsec,$filterlist) = @_; + my ($getsec,$filterlist,$getgroup) = @_; my @getsec; + my @getgroup; + my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status')); if (!ref($getsec)) { if ($getsec ne '' && $getsec ne 'all') { @getsec=($getsec); @@ -380,10 +487,19 @@ sub getclasslist { @getsec=@{$getsec}; } if (grep(/^all$/,@getsec)) { undef(@getsec); } + if (!ref($getgroup)) { + if ($getgroup ne '' && $getgroup ne 'all') { + @getgroup=($getgroup); + } + } else { + @getgroup=@{$getgroup}; + } + if (grep(/^all$/,@getgroup)) { undef(@getgroup); } - my $classlist=&Apache::loncoursedata::get_classlist(); + my ($classlist,$keylist)=&Apache::loncoursedata::get_classlist(); # Bail out if we were unable to get the classlist return if (! defined($classlist)); + &Apache::loncoursedata::get_group_memberships($classlist,$keylist); # my %sections; my %fullnames; @@ -400,18 +516,40 @@ sub getclasslist { $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()]; my $status = $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()]; + my $group = + $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()]; # filter students according to status selected - if ($filterlist && $env{'form.Status'} ne 'Any') { - if ($env{'form.Status'} ne $status) { - delete ($classlist->{$student}); + if ($filterlist && (!($stu_status =~ /Any/))) { + if (!($stu_status =~ $status)) { + delete($classlist->{$student}); next; } } + # filter students according to groups selected + my @stu_groups = split(/,/,$group); + if (@getgroup) { + my $exclude = 1; + foreach my $grp (@getgroup) { + foreach my $stu_group (@stu_groups) { + if ($stu_group eq $grp) { + $exclude = 0; + } + } + if (($grp eq 'none') && !$group) { + $exclude = 0; + } + } + if ($exclude) { + delete($classlist->{$student}); + } + } $section = ($section ne '' ? $section : 'none'); if (&canview($section)) { if (!@getsec || grep(/^\Q$section\E$/,@getsec)) { $sections{$section}++; - $fullnames{$student}=$fullname; + if ($classlist->{$student}) { + $fullnames{$student}=$fullname; + } } else { delete($classlist->{$student}); } @@ -484,6 +622,7 @@ sub student_gradeStatus { # Shows a student's view of problem and submission sub jscriptNform { my ($symb) = @_; + my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status')); my $jscript='<script type="text/javascript" language="javascript">'."\n". ' function viewOneStudent(user,domain) {'."\n". ' document.onestudent.student.value = user;'."\n". @@ -495,7 +634,7 @@ sub jscriptNform { '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n". '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n". '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n". - '<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n". + '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n". '<input type="hidden" name="command" value="submission" />'."\n". '<input type="hidden" name="student" value="" />'."\n". '<input type="hidden" name="userdom" value="" />'."\n". @@ -503,6 +642,8 @@ sub jscriptNform { return $jscript; } + + # Given the score (as a number [0-1] and the weight) what is the final # point value? This function will round to the nearest tenth, third, # or quarter if one of those is within the tolerance of .00001. @@ -659,8 +800,8 @@ sub listStudents { my $cdom = $env{"course.$env{'request.course.id'}.domain"}; my $cnum = $env{"course.$env{'request.course.id'}.num"}; my $getsec = $env{'form.section'} eq '' ? 'all' : $env{'form.section'}; + my $getgroup = $env{'form.group'} eq '' ? 'all' : $env{'form.group'}; my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'}; - my $viewgrade = $env{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View'; $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'}; @@ -720,8 +861,8 @@ LISTJAVASCRIPT if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) { $gradeTable.='<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> essay part only </label>'."\n"; } - - my $saveStatus = $env{'form.Status'} eq '' ? 'Active' : $env{'form.Status'}; + my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status')); + my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status; $env{'form.Status'} = $saveStatus; $gradeTable.='<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> last submission only </label>'."\n". '<label><input type="radio" name="lastSub" value="last" /> last submission & parts info </label>'."\n". @@ -743,7 +884,7 @@ LISTJAVASCRIPT '<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n"; if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) { - $gradeTable.='<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n"; + $gradeTable.='<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n"; } else { $gradeTable.='<b>Student Status:</b> '. &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);').'<br />'; @@ -760,7 +901,7 @@ LISTJAVASCRIPT 'value="Next->" /> <br />'."\n"; $gradeTable.=&check_buttons(); $gradeTable.='<label><input type="checkbox" name="checkPlag" checked="checked" />Check For Plagiarism</label>'; - my ($classlist, undef, $fullname) = &getclasslist($getsec,'1'); + my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup); $gradeTable.='<table border="0"><tr><td bgcolor="#777777">'. '<table border="0"><tr bgcolor="#e6ffff">'; my $loop = 0; @@ -835,7 +976,7 @@ LISTJAVASCRIPT $ctr++; my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()]; - + my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()]; if ( $perm{'vgr'} eq 'F' ) { $gradeTable.='<tr bgcolor="#ffffe6">' if ($ctr%2 ==1); $gradeTable.='<td align="right">'.$ctr.' </td>'. @@ -843,7 +984,7 @@ LISTJAVASCRIPT $student.':'.$$fullname{$student}.':::SECTION'.$section. ') " /> </label></td>'."\n".'<td>'. &nameUserString(undef,$$fullname{$student},$uname,$udom). - ' '.$section.'</td>'."\n"; + ' '.$section.'/'.$group.'</td>'."\n"; if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') { foreach (sort keys(%status)) { @@ -1665,7 +1806,7 @@ sub build_section_inputs { $section_inputs .= '<input type="hidden" name="section" value="all" />'."\n"; } else { my @sections = &Apache::loncommon::get_env_multiple('form.section'); - foreach my $section(@sections) { + foreach my $section (@sections) { $section_inputs .= '<input type="hidden" name="section" value="'.$section.'" />'."\n"; } } @@ -1675,12 +1816,10 @@ sub build_section_inputs { # --------------------------- show submissions of a student, option to grade sub submission { my ($request,$counter,$total) = @_; - my ($uname,$udom) = ($env{'form.student'},$env{'form.userdom'}); $udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student? my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'}); $env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq ''; - my $symb = &get_symb($request); if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; } @@ -1733,7 +1872,7 @@ sub submission { &Apache::lonxml::clear_problem_counter(); $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode)); } - + # kwclr is the only variable that is guaranteed to be non blank # if this subroutine has been called once. my %keyhash = (); @@ -1752,10 +1891,11 @@ sub submission { $env{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0'; } my $overRideScore = $env{'form.overRideScore'} eq '' ? 'no' : $env{'form.overRideScore'}; + my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status')); $request->print('<form action="/adm/grades" method="post" name="SCORE" enctype="multipart/form-data">'."\n". '<input type="hidden" name="command" value="handgrade" />'."\n". '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n". - '<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n". + '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n". '<input type="hidden" name="overRideScore" value="'.$overRideScore.'" />'."\n". '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n". '<input type="hidden" name="refresh" value="off" />'."\n". @@ -1818,8 +1958,13 @@ KEYWORDS } } +# This is where output for one specific student would start + my $bgcolor='#DDEEDD'; + if (int($counter/2) eq $counter) { $bgcolor='#DDDDEE'; } + $request->print("\n\n". + '<p><table border="2"><tr><th bgcolor="'.$bgcolor.'">'.$env{'form.fullname'}.'</th></tr><tr><td bgcolor="'.$bgcolor.'">'); + if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') { - $request->print('<br /><br /><br />') if ($counter > 0); my $mode; if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') { $mode='both'; @@ -2089,6 +2234,11 @@ KEYWORDS } $request->print($result.'</td></tr></table></td></tr></table>'."\n"); +# Done with printing info for one student + + $request->print('</td></tr></table></p>'); + + # print end of form if ($counter == $total) { my $endform='<table border="0"><tr><td>'."\n"; @@ -2751,7 +2901,6 @@ sub version_selected_portfile { my $new_answer; $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stu_name/portfolio$directory$file_name"); if($env{'form.copy'} eq '-1') { - &Apache::lonnet::logthis('problem getting file '.$file_name); $new_answer = 'problem getting file'; } else { $new_answer = $answer_name.'.'.$version.'.'.$answer_ext; @@ -2972,12 +3121,13 @@ sub viewgrades { $result.=&jscriptNform($symb); #beginning of class grading form + my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status')); $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n". '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n". '<input type="hidden" name="command" value="editgrades" />'."\n". &build_section_inputs(). '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n". - '<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n". + '<input type="hidden" name="Status" value="'.$env{'stu_status'}.'" />'."\n". '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n"; my $sectionClass; @@ -3096,9 +3246,10 @@ sub viewgrades { if (scalar(%$fullname) eq 0) { my $colspan=3+scalar(@parts); my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section')); + my $stu_status = join(' or ',&Apache::loncommon::get_env_multiple('form.Status')); $result='<span class="LC_warning">'. &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade', - $section_display, $env{'form.Status'}). + $section_display, $stu_status). '</span>'; } $result.=&show_grading_menu_form($symb); @@ -3377,7 +3528,7 @@ sub split_part_type { my ($partstr) = @_; my ($temp,@allparts)=split(/_/,$partstr); my $type=pop(@allparts); - my $part=join('.',@allparts); + my $part=join('_',@allparts); return ($part,$type); } @@ -3783,7 +3934,6 @@ sub csvuploadassign { } if (! %grades) { push(@skipped,"$username:$domain no data to save"); } $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}"; -# &Apache::lonnet::logthis(" storing ".(join('-',%grades))); my $result=&Apache::lonnet::cstore(\%grades,$symb, $env{'request.course.id'}, $domain,$username); @@ -3882,7 +4032,8 @@ LISTJAVASCRIPT '<label><input type="radio" name="lastSub" value="all" /> all details</label>'."\n"; $result.=&build_section_inputs(); - $result.='<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n". + my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status')); + $result.='<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n". '<input type="hidden" name="command" value="displayPage" />'."\n". '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n". '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n"; @@ -4135,7 +4286,6 @@ sub displaySubByDates { my $where = ($isTask ? "$version:resource.$interaction" : "$version:resource"); - #&Apache::lonnet::logthis(" got $where"); $studentTable.='<tr bgcolor="#ffffff" valign="top"><td>'.$timestamp.'</td>'; if ($isCODE) { $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>'; @@ -4155,7 +4305,6 @@ sub displaySubByDates { my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/) : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/)); - #&Apache::lonnet::logthis("match $matchKey $responseId (".$$record{$version.':'.$matchKey}); $displaySub[0].='<b>Part:</b> '.$display_part.' '; $displaySub[0].='<span class="LC_internal_info">(ID '. $responseId.')</span> <b>'; @@ -4396,7 +4545,7 @@ one of the predefined configurations for like. Next each scanline is checked for any errors of either 'missing -bubbles' (it's an error because it may have been missed scanned +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 @@ -4407,7 +4556,7 @@ username:domain. During the validation phase the instructor can choose to skip scanlines. -After the validation phase, there is now 3 bubble sheet files +After the validation phase, there are now 3 bubble sheet files scantron_original_filename (unmodified original file) scantron_corrected_filename (file where the corrected information has replaced the original information) @@ -4424,10 +4573,7 @@ the homework problem. =over 4 -=cut - -=pod =item defaultFormData @@ -4440,12 +4586,12 @@ the homework problem. sub defaultFormData { my ($symb)=@_; - return ' - <input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n". + return '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n". '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n". '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n"; } + =pod =item getSequenceDropDown @@ -4747,11 +4893,10 @@ SCANTRONFORM </tr> SCANTRONFORM - $r->print(<<SCANTRONFORM); - </table> -$grading_menu_button -SCANTRONFORM - + $r->print('<tr><td bgcolor="#777777">'); + &Apache::lonpickcode::code_list($r,2); + $r->print('</td></tr></table>'); + $r->print($grading_menu_button); return } @@ -5035,13 +5180,18 @@ sub scan_data { if just_header was not true these key may also exist - missingerror - a list of bubbled line numbers that had a blank bubble - that is considered an error (if the operator had already - okayed a blank bubble line as really being blank then - that bubble line number won't appear here. - doubleerror - a list of bubbled line numbers that had more than one - bubble filled in and has not been corrected by the - operator + missingerror - a list of bubble ranges that are considered to be answers + to a single question that don't have any bubbles filled in. + Of the form questionnumber:firstbubblenumber:count. + doubleerror - a list of bubble ranges that are considered to be answers + to a single question that have more than one bubble filled in. + Of the form questionnumber::firstbubblenumber:count + + In the above, count is the number of bubble responses in the + input line needed to represent the possible answers to the question. + e.g. a radioresponse with 15 choices in an answer sheet with 10 choices + per line would have count = 2. + maxquest - the number of the last bubble line that was parsed (<number> starts at 1) @@ -5092,66 +5242,159 @@ sub scantron_parse_scanline { my @alphabet=('A'..'Z'); my $questnum=0; + my $ansnum =1; # Multiple 'answer lines'/question. + while ($questions) { + my $answers_needed = $bubble_lines_per_response{$questnum}; + my $answer_length = $$scantron_config{'Qlength'} * $answers_needed; + + + $questnum++; - my $currentquest=substr($questions,0,$$scantron_config{'Qlength'}); - substr($questions,0,$$scantron_config{'Qlength'})=''; - if (length($currentquest) < $$scantron_config{'Qlength'}) { next; } + my $currentquest = substr($questions,0,$answer_length); + $questions = substr($questions,0,$answer_length)=''; + if (length($currentquest) < $answer_length) { next; } + + # Qon letter implies for each slot in currentquest we have: + # ? or * for doubles a letter in A-Z for a bubble and + # about anything else (esp. a value of Qoff for missing + # bubbles. + + if ($$scantron_config{'Qon'} eq 'letter') { - if ($currentquest eq '?' - || $currentquest eq '*') { + + if ($currentquest =~ /\?/ + || $currentquest =~ /\*/ + || (&occurence_count($currentquest, "[A-Z]") > 1)) { push(@{$record{'scantron.doubleerror'}},$questnum); - $record{"scantron.$questnum.answer"}=''; + for (my $ans = 0; $ans < $answers_needed; $ans++) { + my $bubble = substr($currentquest, $ans, 1); + if ($bubble =~ /[A-Z]/ ) { + $record{"scantron.$ansnum.answer"} = $bubble; + } else { + $record{"scantron.$ansnum.answer"}=''; + } + $ansnum++; + } + } elsif (!defined($currentquest) - || $currentquest eq $$scantron_config{'Qoff'} - || $currentquest !~ /^[A-Z]$/) { - $record{"scantron.$questnum.answer"}=''; + || (&occurence_count($currentquest, $$scantron_config{'Qoff'}) == length($currentquest)) + || (&occurence_count($currentquest, "[A-Z]") == 0)) { + for (my $ans = 0; $ans < $answers_needed; $ans++ ) { + $record{"scantron.$ansnum.answer"}=''; + $ansnum++; + + } if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) { push(@{$record{"scantron.missingerror"}},$questnum); + $ansnum += $answers_needed; } + } else { - $record{"scantron.$questnum.answer"}=$currentquest; + for (my $ans = 0; $ans < $answers_needed; $ans++) { + $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1); + $ansnum++; + } } + + # Qon 'number' implies each slot gives a digit that indexes the + # the bubbles filled or Qoff or a non number for unbubbled lines. + # and *? for double bubbles on a line. + # these answers are also stored as letters. + } elsif ($$scantron_config{'Qon'} eq 'number') { - if ($currentquest eq '?' - || $currentquest eq '*') { + if ($currentquest =~ /\?/ + || $currentquest =~ /\*/ + || (&occurence_count($currentquest, '\d') > 1)) { push(@{$record{'scantron.doubleerror'}},$questnum); - $record{"scantron.$questnum.answer"}=''; + for (my $ans = 0; $ans < $answers_needed; $ans++) { + my $bubble = substr($currentquest, $ans, 1); + if ($bubble =~ /\d/) { + $record{"scantron.$ansnum.answer"} = $alphabet[$bubble]; + } else { + $record{"scantron.$ansnum.answer"}=' '; + } + $ansnum++; + } + } elsif (!defined($currentquest) - || $currentquest eq $$scantron_config{'Qoff'} - || $currentquest !~ /^\d$/) { - $record{"scantron.$questnum.answer"}=''; + || (&occurence_count($currentquest,$$scantron_config{'Qoff'}) == length($currentquest)) + || (&occurence_count($currentquest, '\d') == 0)) { + for (my $ans = 0; $ans < $answers_needed; $ans++ ) { + $record{"scantron.$ansnum.answer"}=''; + $ansnum++; + + } if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) { push(@{$record{"scantron.missingerror"}},$questnum); + $ansnum += $answers_needed; } + } else { - # wrap zero back to J - if ($currentquest eq '0') { - $record{"scantron.$questnum.answer"}= - $alphabet[9]; - } else { - $record{"scantron.$questnum.answer"}= - $alphabet[$currentquest-1]; + $currentquest = &digits_to_letters($currentquest); + for (my $ans =0; $ans < $answers_needed; $ans++) { + $record{"scantron.$ansnum.answer"} = substr($currentquest, $ans, 1); + $ansnum++; } } } else { + + # Otherwise there's a positional notation; + # each bubble line requires Qlength items, and there are filled in + # bubbles for each case where there 'Qon' characters. + # + my @array=split($$scantron_config{'Qon'},$currentquest,-1); - if (length($array[0]) eq $$scantron_config{'Qlength'}) { - $record{"scantron.$questnum.answer"}=''; + + # If the split only giveas us one element.. the full length of the + # answser string, no bubbles are filled in: + + if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) { + for (my $ans = 0; $ans < $answers_needed; $ans++ ) { + $record{"scantron.$ansnum.answer"}=''; + $ansnum++; + + } if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) { push(@{$record{"scantron.missingerror"}},$questnum); } - } else { - $record{"scantron.$questnum.answer"}= - $alphabet[length($array[0])]; + } elsif (scalar(@array) lt 2) { + + my $location = length($array[0]); + my $line_num = $location / $$scantron_config{'Qlength'}; + my $bubble = $alphabet[$location % $$scantron_config{'Qlength'}]; + + for (my $ans = 0; $ans < $answers_needed; $ans++) { + if ($ans eq $line_num) { + $record{"scantron.$ansnum.answer"} = $bubble; + } else { + $record{"scantron.$ansnum.answer"} = ' '; + } + $ansnum++; + } } - if (scalar(@array) gt 2) { + # If there's more than one instance of a bubble character + # That's a double bubble; with positional notation we can + # record all the bubbles filled in as well as the + # fact this response consists of multiple bubbles. + # + else { push(@{$record{'scantron.doubleerror'}},$questnum); + + my $first_answer = $ansnum; + for (my $ans =0; $ans < $answers_needed; $ans++) { + $record{"scantron.$ansnum.answer"} = ''; + $ans++; + } + my @ans=@array; my $i=length($ans[0]);shift(@ans); while ($#ans) { $i+=length($ans[0])+1; - $record{"scantron.$questnum.answer"}.=$alphabet[$i]; + my $line = $i/$$scantron_config{'Qlength'} + $first_answer; + my $bubble = $i%$$scantron_config{'Qlength'}; + + $record{"scantron.$line.answer"}.=$alphabet[$bubble]; shift(@ans); } } @@ -5510,6 +5753,16 @@ sub scantron_form_start { <input type="hidden" name="scantron_options_ignore" value="$env{'form.scantron_options_ignore'}" /> <input type="hidden" name="scantron_options_hidden" value="$env{'form.scantron_options_hidden'}" /> SCANTRONFORM + + my $line = 0; + while (defined($env{"form.scantron.bubblelines.$line"})) { + my $chunk = + '<input type="hidden" name="scantron.bubblelines.'.$line.'" value="'.$env{"form.scantron.bubblelines.$line"}.'" />'."\n"; + $chunk .= + '<input type="hidden" name="scantron.first_bubble_line.'.$line.'" value="'.$env{"form.scantron.first_bubble_line.$line"}.'" />'."\n"; + $result .= $chunk; + $line++; + } return $result; } @@ -5568,6 +5821,7 @@ sub scantron_validate_file { } my $currentphase=$env{'form.validatepass'}; + my $stop=0; while (!$stop && $currentphase < scalar(@validate_phases)) { $r->print("<p> Validating ".$validate_phases[$currentphase]."</p>"); @@ -5991,6 +6245,8 @@ sub scantron_validate_ID { #get scantron line setup my %scantron_config=&get_scantron_config($env{'form.scantron_format'}); my ($scanlines,$scan_data)=&scantron_getfile(); + + &scantron_get_maxbubble(); # parse needs the bubble_lines.. array. my %found=('ids'=>{},'usernames'=>{}); for (my $i=0;$i<=$scanlines->{'count'};$i++) { @@ -6076,7 +6332,7 @@ sub scantron_validate_ID { sub scantron_get_correction { my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_; -#FIXME in the case of a duplicated ID the previous line, probaly need +#FIXME in the case of a duplicated ID the previous line, probably need #to show both the current line and the previous one and allow skipping #the previous one or the current one @@ -6175,9 +6431,10 @@ ENDSCRIPT $r->print($message); $r->print("<p>Please indicate which bubble should be used for grading</p>"); foreach my $question (@{$arg}) { - my $selected=$$scan_record{"scantron.$question.answer"}; + my $selected = &get_response_bubbles($scan_record, $question); + my @select_array = split(/:/,$selected); &scantron_bubble_selector($r,$scan_config,$question, - split('',$selected)); + @select_array); } } elsif ($error eq 'missingbubble') { $r->print("<p>There have been <b>no</b> bubbles scanned for some question(s)</p>\n"); @@ -6187,7 +6444,7 @@ ENDSCRIPT $r->print('<input type="hidden" name="scantron_questions" value="'. join(',',@{$arg}).'" />'); foreach my $question (@{$arg}) { - my $selected=$$scan_record{"scantron.$question.answer"}; + my $selected = &get_response_bubbles($scan_record, $question); &scantron_bubble_selector($r,$scan_config,$question); } } else { @@ -6208,22 +6465,25 @@ ENDSCRIPT $r - Apache request object $scan_config - hash from &get_scantron_config() $quest - number of the bubble line to make a corrector for - $selected - array of letters of previously selected bubbles - $lines - if present, number of bubble lines to show + $lines - array of answer lines. =cut sub scantron_bubble_selector { - my ($r,$scan_config,$quest,@selected, $lines)=@_; + my ($r,$scan_config,$quest,@lines)=@_; my $max=$$scan_config{'Qlength'}; + my $scmode=$$scan_config{'Qon'}; + + my $bubble_length = scalar(@lines); + + if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; } + my $response = $quest-1; + my $lines = $bubble_lines_per_response{$response}; - if (!defined($lines)) { - $lines = 1; - } my $total_lines = $lines*2; my @alphabet=('A'..'Z'); $r->print("<table border='1'><tr><td rowspan='".$total_lines."'>$quest</td>"); @@ -6232,12 +6492,8 @@ sub scantron_bubble_selector { if ($l != 0) { $r->print('<tr>'); } - - # FIXME: This loop probably has to be considerably more clever for - # multiline bubbles: User can multibubble by having bubbles in - # several lines. User can skip lines legitimately etc. etc. - for (my $i=0;$i<$max;$i++) { + my @selected = split(//,$lines[$l]); $r->print("\n".'<td align="center">'); if ($selected[0] eq $alphabet[$i]) { $r->print('X'); @@ -6397,6 +6653,8 @@ sub scantron_validate_CODE { my %allcodes=&get_codes(); + &scantron_get_maxbubble(); # parse needs the lines per response array. + my ($scanlines,$scan_data)=&scantron_getfile(); for (my $i=0;$i<=$scanlines->{'count'};$i++) { my $line=&scantron_get_line($scanlines,$scan_data,$i); @@ -6449,6 +6707,9 @@ sub scantron_validate_doublebubble { #get scantron line setup my %scantron_config=&get_scantron_config($env{'form.scantron_format'}); my ($scanlines,$scan_data)=&scantron_getfile(); + + &scantron_get_maxbubble(); # parse needs the bubble line array. + for (my $i=0;$i<=$scanlines->{'count'};$i++) { my $line=&scantron_get_line($scanlines,$scan_data,$i); if ($line=~/^[\s\cz]*$/) { next; } @@ -6472,33 +6733,78 @@ sub scantron_validate_doublebubble { resource and then checking &Apache::lonxml::get_problem_counter() for what the current value of the problem counter is. - Caches the result to $env{'form.scantron_maxbubble'} + Caches the results to $env{'form.scantron_maxbubble'}, + $env{'form.scantron.bubble_lines.n'} and + $env{'form.scantron.first_bubble_line.n'} + which are the total number of bubble, lines, the number of bubble + lines for reponse n and number of the first bubble line for response n. =cut sub scantron_get_maxbubble { if (defined($env{'form.scantron_maxbubble'}) && $env{'form.scantron_maxbubble'}) { + &restore_bubble_lines(); return $env{'form.scantron_maxbubble'}; } - my $navmap=Apache::lonnavmaps::navmap->new(); - my (undef,undef,$sequence)= + my (undef, undef, $sequence) = &Apache::lonnet::decode_symb($env{'form.selectpage'}); + my $navmap=Apache::lonnavmaps::navmap->new(); my $map=$navmap->getResourceByUrl($sequence); my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0); &Apache::lonxml::clear_problem_counter(); + my $uname = $env{'form.student'}; + my $udom = $env{'form.userdom'}; + my $cid = $env{'request.course.id'}; + my $total_lines = 0; + %bubble_lines_per_response = (); + %first_bubble_line = (); + + + my $response_number = 0; + my $bubble_line = 0; foreach my $resource (@resources) { + my $symb = $resource->symb(); + &Apache::lonxml::clear_bubble_lines_for_part(); my $result=&Apache::lonnet::ssi($resource->src(), - ('symb' => $resource->symb())); + ('symb' => $resource->symb()), + ('grade_target' => 'analyze'), + ('grade_courseid' => $cid), + ('grade_domain' => $udom), + ('grade_username' => $uname)); + my (undef, $an) = + split(/_HASH_REF__/,$result, 2); + + my %analysis = &Apache::lonnet::str2hash($an); + + + + foreach my $part_id (@{$analysis{'parts'}}) { + + + my $lines = $analysis{"$part_id.bubble_lines"};; + + # TODO - make this a persistent hash not an array. + + + $first_bubble_line{$response_number} = $bubble_line; + $bubble_lines_per_response{$response_number} = $lines; + $response_number++; + + $bubble_line += $lines; + $total_lines += $lines; + } + } &Apache::lonnet::delenv('scantron\.'); - $env{'form.scantron_maxbubble'} = - &Apache::lonxml::get_problem_counter()-1; + &save_bubble_lines(); + $env{'form.scantron_maxbubble'} = + $total_lines; return $env{'form.scantron_maxbubble'}; } @@ -6507,7 +6813,8 @@ sub scantron_get_maxbubble { =item scantron_validate_missingbubbles Validates all scanlines in the selected file to not have any - bubble lines with missing bubbles that haven't been verified as missing. + answers that don't have bubbles that have not been verified + to be bubble free. =cut @@ -6601,6 +6908,9 @@ SCANTRONFORM my $start=&Time::HiRes::time(); my $i=-1; my ($uname,$udom,$started); + + &scantron_get_maxbubble(); # Need the bubble lines array to parse. + while ($i<$scanlines->{'count'}) { ($uname,$udom)=('',''); $i++; @@ -6651,8 +6961,6 @@ SCANTRONFORM } my $result=&Apache::lonnet::ssi($resource->src(),%form); if ($result ne '') { - &Apache::lonnet::logthis("scantron grading error -> $result"); - &Apache::lonnet::logthis("scantron grading error info name $uname domain $udom course $env{'request.course.id'} url ".$resource->src()); } if (&Apache::loncommon::connection_aborted($r)) { last; } } @@ -6877,8 +7185,132 @@ sub savedState { return \%savedState; } -#--- Displays the main menu page ------- -sub gradingmenu { +sub grading_menu { + my ($request) = @_; + my ($symb)=&get_symb($request); + if (!$symb) {return '';} + my $probTitle = &Apache::lonnet::gettitle($symb); + my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle); + + # + # Define menu data + $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb); + my ($table) = &showResourceInfo($symb,$env{'form.probTitle'}); + $request->print($table); + my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb), + 'handgrade'=>$hdgrade, + 'probTitle'=>$probTitle, + 'command'=>'submit_options', + 'saveState'=>"", + 'gradingMenu'=>1, + 'showgrading'=>"yes"); + my $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields); + my @menu = ({ url => $url, + name => &mt('Manual Grading/View Submissions'), + short_description => + &mt('Start the process of hand grading submissions.'), + }); + $fields{'command'} = 'csvform'; + $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields); + push (@menu, { url => $url, + name => &mt('Upload Scores'), + short_description => + &mt('Specify a file containing the class scores for current resource.')}); + $fields{'command'} = 'processclicker'; + $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields); + push (@menu, { url => $url, + name => &mt('Process Clicker'), + short_description => + &mt('Specify a file containing the clicker information for this resource.')}); + $fields{'command'} = 'scantron_selectphase'; + $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields); + push (@menu, { url => $url, + name => &mt('Grade/Manage Scantron Forms'), + short_description => + &mt('')}); + $fields{'command'} = 'verify'; + $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields); + push (@menu, { url => "", + name => &mt('Verify Receipt'), + short_description => + &mt('')}); + # + # Create the menu + my $Str; + # $Str .= '<h2>'.&mt('Please select a grading task').'</h2>'; + $Str .= '<form method="post" action="" name="gradingMenu">'; + $Str .= '<input type="hidden" name="command" value="" />'. + '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n". + '<input type="hidden" name="handgrade" value="'.$hdgrade.'" />'."\n". + '<input type="hidden" name="probTitle" value="'.$probTitle.'" ue="" />'."\n". + '<input type="hidden" name="saveState" value="" />'."\n". + '<input type="hidden" name="gradingMenu" value="1" />'."\n". + '<input type="hidden" name="showgrading" value="yes" />'."\n"; + + foreach my $menudata (@menu) { + if ($menudata->{'name'} ne &mt('Verify Receipt')) { + $Str .=' <h3><a '. + $menudata->{'jscript'}. + ' href="'. + $menudata->{'url'}.'" >'. + $menudata->{'name'}."</a></h3>\n"; + } else { + $Str .=' <h3><input type="button" value="Verify Receipt" '. + $menudata->{'jscript'}. + ' onClick="javascript:checkChoice(document.forms.gradingMenu,\'5\',\'verify\')" '. + ' /></h3>'; + $Str .= (' 'x8). + ' receipt: '.&Apache::lonnet::recprefix($env{'request.course.id'}). + '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />'; + } + $Str .= ' '.(' 'x8).$menudata->{'short_description'}. + "\n"; + } + $Str .="</dl>\n"; + $Str .="</form>\n"; + $request->print(<<GRADINGMENUJS); +<script type="text/javascript" language="javascript"> + function checkChoice(formname,val,cmdx) { + if (val <= 2) { + var cmd = radioSelection(formname.radioChoice); + var cmdsave = cmd; + } else { + cmd = cmdx; + cmdsave = 'submission'; + } + formname.command.value = cmd; + if (val < 5) formname.submit(); + if (val == 5) { + if (!checkReceiptNo(formname,'notOK')) { + return false; + } else { + formname.submit(); + } + } + } + + function checkReceiptNo(formname,nospace) { + var receiptNo = formname.receipt.value; + var checkOpt = false; + if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;} + if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;} + if (checkOpt) { + alert("Please enter a receipt number given by a student in the receipt box."); + formname.receipt.value = ""; + formname.receipt.focus(); + return false; + } + return true; + } +</script> +GRADINGMENUJS + &commonJSfunctions($request); + return $Str; +} + + +#--- Displays the submissions first page ------- +sub submit_options { my ($request) = @_; my ($symb)=&get_symb($request); if (!$symb) {return '';} @@ -6940,14 +7372,20 @@ GRADINGMENUJS '<input type="hidden" name="gradingMenu" value="1" />'."\n". '<input type="hidden" name="showgrading" value="yes" />'."\n"; - $result.='<table width="100%" border="0"><tr><td bgcolor=#777777>'."\n". - '<table width="100%" border="0"><tr bgcolor="#e6ffff"><td colspan="2">'."\n". + $result.='<table border="0"><tr><td bgcolor=#777777>'."\n". + '<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n". ' <b>Select a Grading/Viewing Option</b></td></tr>'."\n". '<tr bgcolor="#ffffe6" valign="top"><td>'."\n"; $result.='<table width="100%" border="0">'; + $result.='<tr bgcolor="#ffffe6" valign="top">'."\n"; + $result.='<td><b>'.&mt('Sections').'</b></td>'; + $result.='<td><b>'.&mt('Groups').'</b></td>'; + $result.='<td><b>'.&mt('Access Status').'</td>'."\n"; + $result.='<td><b>'.&mt('Submission Status').'</td>'."\n"; + $result.='</tr>'; $result.='<tr bgcolor="#ffffe6" valign="top"><td>'."\n". - ' '.&mt('Select Section').': <select name="section" multiple="multiple" size="3">'."\n"; + ' <select name="section" multiple="multiple" size="3">'."\n"; if (ref($sections)) { foreach (sort (@$sections)) { $result.='<option value="'.$_.'" '. @@ -6955,15 +7393,13 @@ GRADINGMENUJS } } $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> '; + $result.= '</td><td>'."\n"; + $result.= &Apache::lonstatistics::GroupSelect('group','multiple',3); + $result.='</td><td>'."\n"; + $result.=&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,3,undef,'mult'); - $result.=&mt('Student Status').':'.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,undef); - - $result.='</td></tr>'; - - $result.='<tr bgcolor="#ffffe6"valign="top"><td><label>'. - '<input type="radio" name="radioChoice" value="submission" '. - ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.'<b>'.&mt('Current Resource').':</b> '.&mt('For one or more students'). - '</label> <select name="submitonly">'. + $result.='</td>'; + $result.='<td><select name="submitonly" size="3">'. '<option value="yes" '. ($saveSub eq 'yes' ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>'. '<option value="queued" '. @@ -6973,54 +7409,64 @@ GRADINGMENUJS '<option value="incorrect" '. ($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>'. '<option value="all" '. - ($saveSub eq 'all' ? 'selected="selected"' : '').'>'.&mt('with any status').'</option></select></td></tr>'."\n"; + ($saveSub eq 'all' ? 'selected="selected"' : '').'>'.&mt('with any status').'</option></select></td></tr>'; - $result.='<tr bgcolor="#ffffe6"valign="top"><td>'. + $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="3"><label>'. + '<input type="radio" name="radioChoice" value="submission" '. + ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.'<b>'.&mt('Current Resource').':</b> '.&mt('For one or more students'). + '</label> </td></tr>'."\n"; + + $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="3">'. '<label><input type="radio" name="radioChoice" value="viewgrades" '. ($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '. '<b>Current Resource:</b> For all students in selected section or course</label></td></tr>'."\n"; - $result.='<tr bgcolor="#ffffe6" valign="top"><td>'. - '<label><input type="radio" name="radioChoice" value="pickStudentPage" '. + $result.='<tr bgcolor="#ffffe6"><td colspan="3"><br />'. + '<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'. + '</td></tr>'."\n"; + + + $result.='<tr bgcolor="#ffffe6" valign="top"><td colspan="3">'. + '<br /><label><input type="radio" name="radioChoice" value="pickStudentPage" '. ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '. - 'The <b>complete</b> set/page/sequence: For one student</label></td></tr>'."\n"; + 'The <b>complete</b> set/page/sequence/folder: For one student</label></td></tr>'."\n"; - $result.='<tr bgcolor="#ffffe6"><td><br />'. + $result.='<tr bgcolor="#ffffe6"><td colspan="3"><br />'. '<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'. '</td></tr></table>'."\n"; - $result.='</td><td valign="top">'; + $result.='</td>'; #<td valign="top">'; - $result.='<table width="100%" border="0">'; - $result.='<tr bgcolor="#ffffe6"><td>'. - '<input type="button" onClick="javascript:checkChoice(this.form,\'3\',\'csvform\');" value="'.&mt('Upload').'" />'. - ' '.&mt('scores from file').' </td></tr>'."\n"; - - $result.='<tr bgcolor="#ffffe6"><td>'. - '<input type="button" onClick="javascript:checkChoice(this.form,\'6\',\'processclicker\');" value="'.&mt('Process').'" />'. - ' '.&mt('clicker file').' </td></tr>'."\n"; - - $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'. - '<input type="button" onClick="javascript:checkChoice(this.form,\'4\',\'scantron_selectphase\');'. - '" value="'.&mt('Grade').'" /> scantron forms</td></tr>'."\n"; - - if ((&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) && ($symb)) { - $result.='<tr bgcolor="#ffffe6"valign="top"><td>'. - '<input type="button" onClick="javascript:checkChoice(this.form,\'5\',\'verify\');" value="'.&mt('Verify').'" />'. - ' '.&mt('receipt').': '. - &Apache::lonnet::recprefix($env{'request.course.id'}). - '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />'. - '</td></tr>'."\n"; - } - $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'. - '<input type="button" onClick="javascript:this.form.action=\'/adm/helper/resettimes.helper\';this.form.submit();'. - '" value="'.&mt('Manage').'" /> access times.</td></tr>'."\n"; - $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'. - '<input type="button" onClick="javascript:this.form.command.value=\'codelist\';this.form.action=\'/adm/pickcode\';this.form.submit();'. - '" value="'.&mt('View').'" /> saved CODEs.</td></tr>'."\n"; - - $result.='</table>'."\n". - '</td></tr></table>'."\n". +# $result.='<table width="100%" border="0">'; +# $result.='<tr bgcolor="#ffffe6"><td>'. +# '<input type="button" onClick="javascript:checkChoice(this.form,\'3\',\'csvform\');" value="'.&mt('Upload').'" />'. +# ' '.&mt('scores from file').' </td></tr>'."\n"; +# +# $result.='<tr bgcolor="#ffffe6"><td>'. +# '<input type="button" onClick="javascript:checkChoice(this.form,\'6\',\'processclicker\');" value="'.&mt('Process').'" />'. +# ' '.&mt('clicker file').' </td></tr>'."\n"; +# +# $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'. +# '<input type="button" onClick="javascript:checkChoice(this.form,\'4\',\'scantron_selectphase\');'. +# '" value="'.&mt('Grade').'" /> scantron forms</td></tr>'."\n"; +# +# if ((&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) && ($symb)) { +# $result.='<tr bgcolor="#ffffe6"valign="top"><td>'. +# '<input type="button" onClick="javascript:checkChoice(this.form,\'5\',\'verify\');" value="'.&mt('Verify').'" />'. +# ' '.&mt('receipt').': '. +# &Apache::lonnet::recprefix($env{'request.course.id'}). +# '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />'. +# '</td></tr>'."\n"; +# } +# $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'. +# '<input type="button" onClick="javascript:this.form.action=\'/adm/helper/resettimes.helper\';this.form.submit();'. +# '" value="'.&mt('Manage').'" /> access times.</td></tr>'."\n"; +# $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'. +# '<input type="button" onClick="javascript:this.form.command.value=\'codelist\';this.form.action=\'/adm/pickcode\';this.form.submit();'. +# '" value="'.&mt('View').'" /> saved CODEs.</td></tr>'."\n"; +# +# $result.='</table>'."\n".'</td>'; + $result.= '</tr></table>'."\n". '</td></tr></table></form>'."\n"; return $result; } @@ -7055,9 +7501,10 @@ sub gather_clicker_ids { # Set up a couple variables. my $username_idx = &Apache::loncoursedata::CL_SNAME(); my $domain_idx = &Apache::loncoursedata::CL_SDOM(); + my $status_idx = &Apache::loncoursedata::CL_STATUS(); foreach my $student (keys(%$classlist)) { - + if ($classlist->{$student}->[$status_idx] ne 'Active') { next; } my $username = $classlist->{$student}->[$username_idx]; my $domain = $classlist->{$student}->[$domain_idx]; my $clickers = @@ -7194,9 +7641,9 @@ function sanitycheck() { <input type="hidden" name="saveState" value="$env{'form.saveState'}" /> <input type="file" name="upfile" size="50" /> <br /><label>$type: $selectform</label> -<br /><label>$attendance: <input type="radio" name="gradingmechanism" value="attendance" $checked{'attendance'} onClick="sanitycheck()" /></label> -<br /><label>$personnel: <input type="radio" name="gradingmechanism" value="personnel" $checked{'personnel'} onClick="sanitycheck()" /></label> -<br /><label>$specific: <input type="radio" name="gradingmechanism" value="specific" $checked{'specific'} onClick="sanitycheck()" /></label> +<br /><label><input type="radio" name="gradingmechanism" value="attendance" $checked{'attendance'} onClick="sanitycheck()" />$attendance </label> +<br /><label><input type="radio" name="gradingmechanism" value="personnel" $checked{'personnel'} onClick="sanitycheck()" />$personnel</label> +<br /><label><input type="radio" name="gradingmechanism" value="specific" $checked{'specific'} onClick="sanitycheck()" />$specific </label> <input type="text" name="specificid" value="$env{'form.specificid'}" size="20" /> <input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" /> <br /><label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onChange="sanitycheck()" /></label> @@ -7299,6 +7746,8 @@ ENDHEADER } $result.='<br />'.&mt('Found [_1] question(s)',$number).'<br />'. '<input type="hidden" name="number" value="'.$number.'" />'. + &mt('Awarding [_1] percent for corrion(s)',$number).'<br />'. + '<input type="hidden" name="number" value="'.$number.'" />'. &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses', $env{'form.pcorrect'},$env{'form.pincorrect'}). '<br />'; @@ -7318,8 +7767,21 @@ ENDHEADER $result.="\n".'<input type="hidden" name="correct:'.$correct_count.':'.$correct_ids{$id}.'" value="'.$responses{$id}.'" />'; $correct_count++; } elsif ($clicker_ids{$id}) { - $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />'; - $student_count++; + if ($clicker_ids{$id}=~/\,/) { +# More than one user with the same clicker! + $result.="\n<hr />".&mt('Clicker registered more than once').": <tt>".$id."</tt><br />"; + $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'. + "<select name='multi".$id."'>"; + foreach my $reguser (sort(split(/\,/,$clicker_ids{$id}))) { + $result.="<option value='".$reguser."'>".&Apache::loncommon::plainname(split(/\:/,$reguser)).' ('.$reguser.')</option>'; + } + $result.='</select>'; + $unknown_count++; + } else { +# Good: found one and only one user with the right clicker + $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />'; + $student_count++; + } } else { $result.="\n<hr />".&mt('Unregistered Clicker')." <tt>".$id."</tt><br />"; $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'. @@ -7470,6 +7932,8 @@ ENDHEADER my $id=$1; if (($env{'form.uname'.$id}) && ($env{'form.udom'.$id})) { $user=$env{'form.uname'.$id}.':'.$env{'form.udom'.$id}; + } elsif ($env{'form.multi'.$id}) { + $user=$env{'form.multi'.$id}; } } if ($user) { @@ -7514,8 +7978,7 @@ ENDHEADER sub handler { my $request=$_[0]; - - &reset_perm(); + &reset_caches(); if ($env{'browser.mathml'}) { &Apache::loncommon::content_type($request,'text/xml'); } else { @@ -7527,9 +7990,12 @@ sub handler { my $symb=&get_symb($request,1); my @commands=&Apache::loncommon::get_env_multiple('form.command'); my $command=$commands[0]; + if ($#commands > 0) { &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands)); } + + $request->print(&Apache::loncommon::start_page('Grading')); if ($symb eq '' && $command eq '') { if ($env{'user.adv'}) { @@ -7570,7 +8036,9 @@ sub handler { } elsif ($command eq 'processGroup' && $perm{'vgr'}) { &processGroup($request); } elsif ($command eq 'gradingmenu' && $perm{'vgr'}) { - $request->print(&gradingmenu($request)); + $request->print(&grading_menu($request)); + } elsif ($command eq 'submit_options' && $perm{'vgr'}) { + $request->print(&submit_options($request)); } elsif ($command eq 'viewgrades' && $perm{'vgr'}) { $request->print(&viewgrades($request)); } elsif ($command eq 'handgrade' && $perm{'mgr'}) { @@ -7628,6 +8096,7 @@ sub handler { } } $request->print(&Apache::loncommon::end_page()); + &reset_caches(); return ''; }