--- loncom/homework/grades.pm 2007/06/15 21:21:16 1.405 +++ loncom/homework/grades.pm 2007/11/03 00:08:09 1.477 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.405 2007/06/15 21:21:16 www Exp $ +# $Id: grades.pm,v 1.477 2007/11/03 00:08:09 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -35,47 +35,115 @@ 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); use Apache::lonlocal; use Apache::lonenc; use String::Similarity; -use lib '/home/httpd/lib/perl'; use LONCAPA; use POSIX qw(floor); -my %oldessays=(); + 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); } } @@ -94,6 +162,7 @@ sub get_symb { return (); } } + &Apache::lonenc::check_decrypt(\$symb); return ($symb); } @@ -195,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. @@ -231,8 +332,8 @@ sub cleanRecord { $bottomrow.='<td>'.$grayFont.$foil.'</span> </td>'; } return '<blockquote><table border="1">'. - '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'. - '<tr valign="top"><td>'.$grayFont.'Option ID</span></td>'. + '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'. + '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'. $grayFont.$bottomrow.'</tr>'.'</table></blockquote>'; } elsif ($response eq 'match') { my %answer=&Apache::lonnet::str2hash($answer); @@ -251,31 +352,31 @@ sub cleanRecord { $bottomrow.='<td>'.$grayFont.$foil.'</span> </td>'; } return '<blockquote><table border="1">'. - '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'. - '<tr valign="top"><td>'.$grayFont.'Item ID</span></td>'. + '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'. + '<tr valign="top"><td>'.$grayFont.&mt('Item ID').'</span></td>'. $middlerow.'</tr>'. - '<tr valign="top"><td>'.$grayFont.'Option ID</span></td>'. + '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'. $bottomrow.'</tr>'.'</table></blockquote>'; } 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) { - $toprow.='<td><b>true</b></td>'; + if ($foil eq $correct) { + $toprow.='<td><b>'.&mt('true').'</b></td>'; } else { - $toprow.='<td><i>true</i></td>'; + $toprow.='<td><i>'.&mt('true').'</i></td>'; } } else { - $toprow.='<td>false</td>'; + $toprow.='<td>'.&mt('false').'</td>'; } $bottomrow.='<td>'.$grayFont.$foil.'</span> </td>'; } return '<blockquote><table border="1">'. - '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'. - '<tr valign="top"><td>'.$grayFont.'Option ID</span></td>'. + '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'. + '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'. $grayFont.$bottomrow.'</tr>'.'</table></blockquote>'; } elsif ($response eq 'essay') { if (! exists ($env{'form.'.$symb})) { @@ -327,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; } @@ -371,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); @@ -381,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; @@ -401,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}); } @@ -485,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". @@ -493,10 +631,10 @@ sub jscriptNform { ' }'."\n". '</script>'."\n"; $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\n". - '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". + '<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". @@ -504,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. @@ -538,7 +678,7 @@ sub compute_points { # sub most_similar { - my ($uname,$udom,$uessay)=@_; + my ($uname,$udom,$uessay,$old_essays)=@_; # ignore spaces and punctuation @@ -555,23 +695,22 @@ sub most_similar { my $scrsid=''; my $sessay=''; # go through all essays ... - foreach my $tkey (keys %oldessays) { - my ($tname,$tdom,$tcrsid)=split(/\./,$tkey); + foreach my $tkey (keys(%$old_essays)) { + my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey)); # ... except the same student - if (($tname ne $uname) || ($tdom ne $udom)) { - my $tessay=$oldessays{$tkey}; - $tessay=~s/\W+/ /gs; + next if (($tname eq $uname) && ($tdom eq $udom)); + my $tessay=$old_essays->{$tkey}; + $tessay=~s/\W+/ /gs; # String similarity gives up if not even limit - my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit); + my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit); # Found one - if ($tsimilar>$limit) { - $limit=$tsimilar; - $sname=$tname; - $sdom=$tdom; - $scrsid=$tcrsid; - $sessay=$oldessays{$tkey}; - } - } + if ($tsimilar>$limit) { + $limit=$tsimilar; + $sname=$tname; + $sdom=$tdom; + $scrsid=$tcrsid; + $sessay=$old_essays->{$tkey}; + } } if ($limit>0.6) { return ($sname,$sdom,$scrsid,$sessay,$limit); @@ -618,7 +757,7 @@ sub verifyreceipt { if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) { $contents.='<tr bgcolor="#ffffe6"><td> '."\n". '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom. - '\')"; TARGET=_self>'.$$fullname{$_}.'</a> </td>'."\n". + '\');" target="_self">'.$$fullname{$_}.'</a> </td>'."\n". '<td> '.$uname.' </td>'. '<td> '.$udom.' </td>'; if ($receiptparts) { @@ -661,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'}; @@ -722,12 +861,11 @@ 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". + '<label><input type="radio" name="lastSub" value="last" /> last submission & parts info </label>'."\n". '<label><input type="radio" name="lastSub" value="datesub" /> by dates and submissions </label>'."\n". '<label><input type="radio" name="lastSub" value="all" /> all details</label><br />'."\n". ' <b>Grading Increments:</b> <select name="increment">'. @@ -736,18 +874,17 @@ LISTJAVASCRIPT '<option value=".25">Quarter Points</option>'. '<option value=".1">Tenths of a Point</option>'. '</select>'. - - '<input type="hidden" name="section" value="'.$getsec.'" />'."\n". + &build_section_inputs(). '<input type="hidden" name="submitonly" value="'.$submitonly.'" />'."\n". '<input type="hidden" name="handgrade" value="'.$env{'form.handgrade'}.'" /><br />'."\n". '<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" /><br />'."\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="symb" value="'.$symb.'" />'."\n". + '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n". '<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 />'; @@ -764,28 +901,28 @@ 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'); - $gradeTable.='<table border="0"><tr><td bgcolor="#777777">'. - '<table border="0"><tr bgcolor="#e6ffff">'; + my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup); + $gradeTable.= &Apache::loncommon::start_data_table(). + &Apache::loncommon::start_data_table_header_row(); my $loop = 0; while ($loop < 2) { - $gradeTable.='<td><b> No.</b> </td><td><b> Select </b></td>'. - '<td>'.&nameUserString('header').' Section/Group</td>'; + $gradeTable.='<th>No.</th><th>Select</th>'. + '<th>'.&nameUserString('header').' '.'Section/Group</th>'; if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'queued' && $submitonly ne 'all') { foreach (sort(@$partlist)) { my $display_part=&get_display_part((split(/_/))[0],$symb); - $gradeTable.='<td><b> Part: '.$display_part. - ' Status </b></td>'; + $gradeTable.='<th>Part: '.$display_part. + ' Status</h>'; } } elsif ($submitonly eq 'queued') { - $gradeTable.='<td><b> '.&mt('Queue Status').' </b></td>'; + $gradeTable.='<th>'.&mt('Queue Status').' </th>'; } $loop++; # $gradeTable.='<td></td>' if ($loop%2 ==1); } - $gradeTable.='</tr>'."\n"; + $gradeTable.=&Apache::loncommon::end_data_table_header_row()."\n"; my $ctr = 0; foreach my $student (sort @@ -839,15 +976,17 @@ 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); + if ($ctr%2 ==1) { + $gradeTable.= &Apache::loncommon::start_data_table_row(); + } $gradeTable.='<td align="right">'.$ctr.' </td>'. '<td align="center"><label><input type=checkbox name="stuinfo" value="'. $student.':'.$$fullname{$student}.':::SECTION'.$section. ') " /> </label></td>'."\n".'<td>'. &nameUserString(undef,$$fullname{$student},$uname,$udom). - ' '.$section.'</td>'."\n"; + ' '.$section.($group ne '' ?'/'.$group:'').'</td>'."\n"; if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') { foreach (sort keys(%status)) { @@ -856,7 +995,9 @@ LISTJAVASCRIPT } } # $gradeTable.='<td></td>' if ($ctr%2 ==1); - $gradeTable.='</tr>'."\n" if ($ctr%2 ==0); + if ($ctr%2 ==0) { + $gradeTable.=&Apache::loncommon::end_data_table_row()."\n"; + } } } if ($ctr%2 ==1) { @@ -870,10 +1011,10 @@ LISTJAVASCRIPT } elsif ($submitonly eq 'queued') { $gradeTable.='<td> </td>'; } - $gradeTable.='</tr>'; + $gradeTable.=&Apache::loncommon::end_data_table_row(); } - $gradeTable.='</table></td></tr></table>'."\n". + $gradeTable.=&Apache::loncommon::end_data_table()."\n". '<input type="button" '. 'onClick="javascript:checkSelect(this.form.stuinfo);" '. 'value="Next->" /></form>'."\n"; @@ -891,7 +1032,7 @@ LISTJAVASCRIPT ' students checked for '.$submissions.')</span><br />'; } } elsif ($ctr == 1) { - $gradeTable =~ s/type=checkbox/type=checkbox checked/; + $gradeTable =~ s/type="checkbox"/type="checkbox" checked="checked"/; } $gradeTable.=&show_grading_menu_form($symb); $request->print($gradeTable); @@ -1343,43 +1484,43 @@ INNERJS pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">"); pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">"); - pDoc.write("<h3><span class=\\"LC_info\\"> Compose Message for \"+fullname+\"</span></h3><br /><br />"); + pDoc.write("<h3><span class=\\"LC_info\\"> Compose Message for \"+fullname+\"<\\/span><\\/h3><br /><br />"); pDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">"); pDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">"); - pDoc.write("<td><b>Type</b></td><td><b>Include</b></td><td><b>Message</td></tr>"); + pDoc.write("<td><b>Type<\\/b><\\/td><td><b>Include<\\/b><\\/td><td><b>Message<\\/td><\\/tr>"); } function displaySubject(msg,shwsel) { pDoc = pWin.document; pDoc.write("<tr bgcolor=\\"#ffffdd\\">"); - pDoc.write("<td>Subject</td>"); - pDoc.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"></td>"); - pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"60\\" maxlength=\\"80\\"></td></tr>"); + pDoc.write("<td>Subject<\\/td>"); + pDoc.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>"); + pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"60\\" maxlength=\\"80\\"><\\/td><\\/tr>"); } function displaySavedMsg(ctr,msg,shwsel) { pDoc = pWin.document; pDoc.write("<tr bgcolor=\\"#ffffdd\\">"); - pDoc.write("<td align=\\"center\\">"+ctr+"</td>"); - pDoc.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"></td>"); - pDoc.write("<td><textarea name=\\"msg"+ctr+"\\" cols=\\"60\\" rows=\\"3\\">"+msg+"</textarea></td></tr>"); + pDoc.write("<td align=\\"center\\">"+ctr+"<\\/td>"); + pDoc.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"><\\/td>"); + pDoc.write("<td><textarea name=\\"msg"+ctr+"\\" cols=\\"60\\" rows=\\"3\\">"+msg+"<\\/textarea><\\/td><\\/tr>"); } function newMsg(newmsg,shwsel) { pDoc = pWin.document; pDoc.write("<tr bgcolor=\\"#ffffdd\\">"); - pDoc.write("<td align=\\"center\\">New</td>"); - pDoc.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"></td>"); - pDoc.write("<td><textarea name=\\"newmsg\\" cols=\\"60\\" rows=\\"3\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" >"+newmsg+"</textarea></td></tr>"); + pDoc.write("<td align=\\"center\\">New<\\/td>"); + pDoc.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>"); + pDoc.write("<td><textarea name=\\"newmsg\\" cols=\\"60\\" rows=\\"3\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" >"+newmsg+"<\\/textarea><\\/td><\\/tr>"); } function msgTail() { pDoc = pWin.document; - pDoc.write("</table>"); - pDoc.write("</td></tr></table> "); + pDoc.write("<\\/table>"); + pDoc.write("<\\/td><\\/tr><\\/table> "); pDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\"> "); pDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />"); - pDoc.write("</form>"); + pDoc.write("<\\/form>"); pDoc.write('$end_page_msg_central'); pDoc.close(); } @@ -1428,32 +1569,32 @@ INNERJS hDoc.$docopen; hDoc.write('$start_page_highlight_central'); hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">"); - hDoc.write("<h3><span class=\\"LC_info\\"> Keyword Highlight Options</span></h3><br /><br />"); + hDoc.write("<h3><span class=\\"LC_info\\"> Keyword Highlight Options<\\/span><\\/h3><br /><br />"); hDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">"); hDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">"); - hDoc.write("<td><b>Text Color</b></td><td><b>Font Size</b></td><td><b>Font Style</td></tr>"); + hDoc.write("<td><b>Text Color<\\/b><\\/td><td><b>Font Size<\\/b><\\/td><td><b>Font Style<\\/td><\\/tr>"); } function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) { var hDoc = hwdWin.document; hDoc.write("<tr bgcolor=\\"#ffffdd\\">"); hDoc.write("<td align=\\"left\\">"); - hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+"> "+clrtxt+"</td>"); + hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+"> "+clrtxt+"<\\/td>"); hDoc.write("<td align=\\"left\\">"); - hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+"> "+sztxt+"</td>"); + hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+"> "+sztxt+"<\\/td>"); hDoc.write("<td align=\\"left\\">"); - hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+"> "+sytxt+"</td>"); - hDoc.write("</tr>"); + hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+"> "+sytxt+"<\\/td>"); + hDoc.write("<\\/tr>"); } function highlightend() { var hDoc = hwdWin.document; - hDoc.write("</table>"); - hDoc.write("</td></tr></table> "); + hDoc.write("<\\/table>"); + hDoc.write("<\\/td><\\/tr><\\/table> "); hDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:updateChoice(1)\\"> "); hDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />"); - hDoc.write("</form>"); + hDoc.write("<\\/form>"); hDoc.write('$end_page_highlight_central'); hDoc.close(); } @@ -1478,13 +1619,13 @@ sub gradeBox { '" src="'.$request->dir_config('lonIconsURL'). '/check.gif" height="16" border="0" />'; my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname); - my $wgtmsg = ($wgt > 0 ? '(problem weight)' : - '<span class="LC_info">problem weight assigned by computer</span>'); + my $wgtmsg = ($wgt > 0) ? &mt('(problem weight)') + : '<span class="LC_info">'.&mt('problem weight assigned by computer').'</span>'; $wgt = ($wgt > 0 ? $wgt : '1'); my $score = ($$record{'resource.'.$partid.'.awarded'} eq '' ? '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt)); my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n"; - my $display_part=&get_display_part($partid,$symb); + my $display_part= &get_display_part($partid,$symb); my %last_resets = &get_last_resets($symb,$env{'request.course.id'}, [$partid]); my $aggtries = $$record{'resource.'.$partid.'.tries'}; @@ -1566,7 +1707,7 @@ sub handback_box { '<span class="LC_filename">'.$file_disp.'</span>'); $result.='<input type="file" name="'.$prefix.'returndoc'.$file_counter.'" />'."\n"; $result.='<input type="hidden" name="'.$prefix.'origdoc'.$file_counter.'" value="'.$file.'" /><br />'; - $result.='(File will be uploaded when you click on Save & Next below.)<br />'; + $result.='(File will be uploaded when you click on Save & Next below.)<br />'; $file_counter++; } } @@ -1602,27 +1743,27 @@ sub show_problem { $companswer=~s|</form>||g; $companswer=~s|name="submit"|name="would_have_been_submit"|g; } - my $result.='<table border="0" width="100%"><tr><td bgcolor="#777777">'; - $result.='<table border="0" width="100%">'; - if ($viewon) { - $result.='<tr><td bgcolor="#e6ffff"><b> '; - if ($mode eq 'both' or $mode eq 'text') { - $result.='View of the problem - '; - } else { - $result.='Correct answer: '; - } - $result.=$env{'form.fullname'}.'</b></td></tr>'; - } + $rendered= + '<div class="LC_grade_show_problem_header">'. + &mt('View of the problem'). + '</div><div class="LC_grade_show_problem_problem">'. + $rendered. + '</div>'; + $companswer= + '<div class="LC_grade_show_problem_header">'. + &mt('Correct answer'). + '</div><div class="LC_grade_show_problem_problem">'. + $companswer. + '</div>'; + my $result; if ($mode eq 'both') { - $result.='<tr><td bgcolor="#ffffff">'.$rendered.'<br />'; - $result.='<b>Correct answer:</b><br />'.$companswer; + $result=$rendered.$companswer; } elsif ($mode eq 'text') { - $result.='<tr><td bgcolor="#ffffff">'.$rendered; + $result=$rendered; } elsif ($mode eq 'answer') { - $result.='<tr><td bgcolor="#ffffff">'.$companswer; + $result=$companswer; } - $result.='</td></tr></table>'; - $result.='</td></tr></table><br />'; + $result='<div class="LC_grade_show_problem">'.$result.'</div>'; return $result; } @@ -1663,15 +1804,26 @@ sub download_all_link { return } +sub build_section_inputs { + my $section_inputs; + if ($env{'form.section'} eq '') { + $section_inputs .= '<input type="hidden" name="section" value="all" />'."\n"; + } else { + my @sections = &Apache::loncommon::get_env_multiple('form.section'); + foreach my $section (@sections) { + $section_inputs .= '<input type="hidden" name="section" value="'.$section.'" />'."\n"; + } + } + return $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 ''; } @@ -1691,6 +1843,7 @@ sub submission { '" src="'.$request->dir_config('lonIconsURL'). '/check.gif" height="16" border="0" />'; + my %old_essays; # header info if ($counter == 0) { &sub_page_js($request); @@ -1703,12 +1856,6 @@ sub submission { $request->print('<h3> <span class="LC_info">Submission Record</span></h3>'."\n". '<h4> <b>Resource: </b>'.$env{'form.probTitle'}.'</h4>'."\n"); - if ($env{'form.handgrade'} eq 'no') { - my $checkMark='<br /><br /> <b>Note:</b> Part(s) graded correct by the computer is marked with a '. - $checkIcon.' symbol.'."\n"; - $request->print($checkMark); - } - # option to display problem, only once else it cause problems # with the form later since the problem has a form. if ($env{'form.vProb'} eq 'yes' or $env{'form.vAns'} eq 'yes') { @@ -1723,7 +1870,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 = (); @@ -1742,22 +1889,22 @@ 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". '<input type="hidden" name="studentNo" value="" />'."\n". '<input type="hidden" name="gradeOpt" value="" />'."\n". - '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". + '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n". '<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" />'."\n". '<input type="hidden" name="vProb" value="'.$env{'form.vProb'}.'" />'."\n". '<input type="hidden" name="vAns" value="'.$env{'form.vAns'}.'" />'."\n". '<input type="hidden" name="lastSub" value="'.$env{'form.lastSub'}.'" />'."\n". - '<input type="hidden" name="section" value="'.$env{'form.section'}.'" />'."\n". + &build_section_inputs(). '<input type="hidden" name="submitonly" value="'.$env{'form.submitonly'}.'" />'."\n". '<input type="hidden" name="handgrade" value="'.$env{'form.handgrade'}.'" />'."\n". '<input type="hidden" name="NCT"'. @@ -1793,10 +1940,10 @@ sub submission { # $request->print(<<KEYWORDS); <b>Keyword Options:</b> -<a href="javascript:keywords(document.SCORE)"; TARGET=_self>List</a> +<a href="javascript:keywords(document.SCORE);" target="_self">List</a> <a href="#" onMouseDown="javascript:getSel(); return false" CLASS="page">Paste Selection to List</a> -<a href="javascript:kwhighlight()"; TARGET=_self>Highlight Attribute</a><br /><br /> +<a href="javascript:kwhighlight();" target="_self">Highlight Attribute</a><br /><br /> KEYWORDS # # Load the other essays for similarity check @@ -1805,12 +1952,18 @@ KEYWORDS my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/); $apath=&escape($apath); $apath=~s/\W/\_/gs; - %oldessays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname); + %old_essays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname); } } +# This is where output for one specific student would start + my $add_class = ($counter%2) ? 'LC_grade_show_user_odd_row' : ''; + $request->print("\n\n". + '<div class="LC_grade_show_user '.$add_class.'">'. + '<div class="LC_grade_user_name">'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'</div>'. + '<div class="LC_grade_show_user_body">'."\n"); + 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'; @@ -1820,7 +1973,7 @@ KEYWORDS $mode='answer'; } &Apache::lonxml::clear_problem_counter(); - $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode)); + $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode,{'request.prefix' => 'ctr'.$counter})); } my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname); @@ -1828,71 +1981,31 @@ KEYWORDS # Display student info $request->print(($counter == 0 ? '' : '<br />')); - my $result='<table border="0" width="100%"><tr><td bgcolor="#777777">'."\n". - '<table border="0" width="100%"><tr bgcolor="#edffff"><td>'."\n"; - - $result.='<b>Fullname: </b>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'<br />'."\n"; + my $result='<div class="LC_grade_submissions">'; + + $result.='<div class="LC_grade_submissions_header">'; + $result.= &mt('Submissions'); $result.='<input type="hidden" name="name'.$counter. '" value="'.$env{'form.fullname'}.'" />'."\n"; + if ($env{'form.handgrade'} eq 'no') { + $result.='<span class="LC_grade_check_note">'. + &mt('Part(s) graded correct by the computer is marked with a [_1] symbol.',$checkIcon)."</span>\n"; + + } + + # If any part of the problem is an essay-response (handgraded), then check for collaborators - my @col_fullnames; - my ($classlist,$fullname); + my $fullname; + my $col_fullnames = []; if ($env{'form.handgrade'} eq 'yes') { - ($classlist,undef,$fullname) = &getclasslist('all','0'); - for (keys (%$handgrade)) { - my $ncol = &Apache::lonnet::EXT('resource.'.$_. - '.maxcollaborators', - $symb,$udom,$uname); - next if ($ncol <= 0); - s/\_/\./g; - next if ($record{'resource.'.$_.'.collaborators'} eq ''); - my @goodcollaborators = (); - my @badcollaborators = (); - foreach (split(/,?\s+/,$record{'resource.'.$_.'.collaborators'})) { - $_ =~ s/[\$\^\(\)]//g; - next if ($_ eq ''); - my ($co_name,$co_dom) = split /\@|:/,$_; - $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i); - next if ($co_name eq $uname && $co_dom eq $udom); - # Doing this grep allows 'fuzzy' specification - my @Matches = grep /^$co_name:$co_dom$/i,keys %$classlist; - if (! scalar(@Matches)) { - push @badcollaborators,$_; - } else { - push @goodcollaborators, @Matches; - } - } - if (scalar(@goodcollaborators) != 0) { - $result.='<b>Collaborators: </b>'; - foreach (@goodcollaborators) { - my ($lastname,$givenn) = split(/,/,$$fullname{$_}); - push @col_fullnames, $givenn.' '.$lastname; - $result.=$$fullname{$_}.' '; - } - $result.='<br />'."\n"; - my ($part)=split(/\./,$_); - $result.='<input type="hidden" name="collaborator'.$counter. - '" value="'.$part.':'.(join ':',@goodcollaborators).'" />'. - "\n"; - } - if (scalar(@badcollaborators) > 0) { - $result.='<table border="0"><tr bgcolor="#ffbbbb"><td>'; - $result.='This student has submitted '; - $result.=(scalar(@badcollaborators) == 1) ? 'an invalid collaborator' : 'invalid collaborators'; - $result .= ': '.join(', ',@badcollaborators); - $result .= '</td></tr></table>'; - } - if (scalar(@badcollaborators > $ncol)) { - $result .= '<table border="0"><tr bgcolor="#ffbbbb"><td>'; - $result .= 'This student has submitted too many '. - 'collaborators. Maximum is '.$ncol.'.'; - $result .= '</td></tr></table>'; - } - } + (my $sub_result,$fullname,$col_fullnames)= + &check_collaborators($symb,$uname,$udom,\%record,$handgrade, + $counter); + $result.=$sub_result; } $request->print($result."\n"); - + $request->print('</div>'."\n"); # print student answer/submission # Options are (1) Handgaded submission only # (2) Last submission, includes submission that is not handgraded @@ -1901,12 +2014,14 @@ KEYWORDS # (4) The whole record for this student if ($env{'form.lastSub'} =~ /^(lastonly|hdgrade)$/) { my ($string,$timestamp)= &get_last_submission(\%record); - my $lastsubonly=''. - ($$timestamp eq '' ? '' : '<b>Date Submitted:</b> '. - $$timestamp)."</td></tr>\n"; + + my $lastsubonly; + if ($$timestamp eq '') { - $lastsubonly.='<tr><td bgcolor="#ffffe6">'.$$string[0]; + $lastsubonly.='<div class="LC_grade_submissions_body">'.$$string[0].'</div>'; } else { + $lastsubonly = '<div class="LC_grade_submissions_body"> <b>Date Submitted:</b> '.$$timestamp."\n"; + my %seenparts; my @part_response_id = &flatten_responseType($responseType); foreach my $part (@part_response_id) { @@ -1922,33 +2037,42 @@ KEYWORDS ' <b>Collaborative submission by:</b> '. '<a href="javascript:viewSubmitter(\''. $env{"form.$uname:$udom:$partid:submitted_by"}. - '\')"; TARGET=_self>'. + '\');" target="_self">'. $$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a><br />'; $request->print($submitby); next; } my $responsetype = $responseType->{$partid}->{$respid}; if (!exists($record{"resource.$partid.$respid.submission"})) { - $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '. + $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">Nothing submitted - no attempts</span><br /><br />'; + '<span class="LC_warning">'.&mt('Nothing submitted - no attempts').'</span><br /><br /></div>'; next; } - foreach (@$string) { - my ($partid,$respid) = /^resource\.([^\.]*)\.([^\.]*)\.submission/; + foreach my $submission (@$string) { + my ($partid,$respid) = ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/); if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; } - my ($ressub,$subval) = split(/:/,$_,2); + my ($ressub,$subval) = split(/:/,$submission,2); # Similarity check my $similar=''; if($env{'form.checkPlag'}){ my ($oname,$odom,$ocrsid,$oessay,$osim)= - &most_similar($uname,$udom,$subval); + &most_similar($uname,$udom,$subval,\%old_essays); if ($osim) { $osim=int($osim*100.0); - $similar="<hr /><h3><span class=\"LC_warning\">Essay". - " is $osim% similar to an essay by ". - &Apache::loncommon::plainname($oname,$odom). + my %old_course_desc = + &Apache::lonnet::coursedescription($ocrsid, + {'one_time' => 1}); + + $similar="<hr /><h3><span class=\"LC_warning\">". + &mt('Essay is [_1]% similar to an essay by [_2] ([_3]:[_4]) in course [_5] (course id [_6]:[_7])', + $osim, + &Apache::loncommon::plainname($oname,$odom), + $oname,$odom, + $old_course_desc{'description'}, + $old_course_desc{'num'}, + $old_course_desc{'domain'}). '</span></h3><blockquote><i>'. &keywords_highlight($oessay). '</i></blockquote><hr />'; @@ -1959,31 +2083,32 @@ KEYWORDS ($env{'form.lastSub'} eq 'hdgrade' && $$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) { my $display_part=&get_display_part($partid,$symb); - $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '. + $lastsubonly.='<div class="LC_grade_submission_part"><b>Part:</b> '. $display_part.' <span class="LC_internal_info">( ID '.$respid. ' )</span> '; my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record); if (@$files) { - $lastsubonly.='<br /><span class="LC_warning">Like all files provided by users, this file may contain virusses</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 ++; + $file_counter++; &Apache::lonnet::allowuploaded('/adm/grades',$file); $lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border=0"> '.$file.'</a>'; } $lastsubonly.='<br />'; } - $lastsubonly.='<b>Submitted Answer: </b>'. + $lastsubonly.='<b>'.&mt('Submitted Answer:').' </b>'. &cleanRecord($subval,$responsetype,$symb,$partid, $respid,\%record,$order); if ($similar) {$lastsubonly.="<br /><br />$similar\n";} + $lastsubonly.='</div>'; } } } + $lastsubonly.='</div>'."\n"; } - $lastsubonly.='</td></tr><tr bgcolor="#ffffff"><td>'."\n"; $request->print($lastsubonly); - } elsif ($env{'form.lastSub'} eq 'datesub') { + } elsif ($env{'form.lastSub'} eq 'datesub') { my (undef,$responseType,undef,$parts) = &showResourceInfo($symb); $request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom)); } elsif ($env{'form.lastSub'} =~ /^(last|all)$/) { @@ -1995,13 +2120,12 @@ KEYWORDS $request->print('<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':' .$udom.'" />'."\n"); - # return if view submission with no grading option if ($env{'form.showgrading'} eq '' || (!&canmodify($usec))) { my $toGrade.='<input type="button" value="Grade Student" '. 'onClick="javascript:checksubmit(this.form,\'Grade Student\',\'' - .$counter.'\');" TARGET=_self> '."\n" if (&canmodify($usec)); - $toGrade.='</td></tr></table></td></tr></table>'."\n"; + .$counter.'\');" target="_self" /> '."\n" if (&canmodify($usec)); + $toGrade.='</div>'."\n"; if (($env{'form.command'} eq 'submission') || ($env{'form.command'} eq 'processGroup' && $counter == $total)) { $toGrade.='</form>'.&show_grading_menu_form($symb); @@ -2009,45 +2133,45 @@ KEYWORDS $request->print($toGrade); return; } else { - $request->print('</td></tr></table></td></tr></table>'."\n"); + $request->print('</div>'."\n"); } # essay grading message center if ($env{'form.handgrade'} eq 'yes') { + my $result='<div class="LC_grade_message_center">'; + + $result.='<div class="LC_grade_message_center_header">'. + &mt('Send Message').'</div><div class="LC_grade_message_center_body">'; my ($lastname,$givenn) = split(/,/,$env{'form.fullname'}); my $msgfor = $givenn.' '.$lastname; - if (scalar(@col_fullnames) > 0) { - my $lastone = pop @col_fullnames; - $msgfor .= ', '.(join ', ',@col_fullnames).' and '.$lastone.'.'; + if (scalar(@$col_fullnames) > 0) { + my $lastone = pop(@$col_fullnames); + $msgfor .= ', '.(join ', ',@$col_fullnames).' and '.$lastone.'.'; } $msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript - $result='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n". + $result.='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n". '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n"; $result.=' <a href="javascript:msgCenter(document.SCORE,'.$counter. - ',\''.$msgfor.'\')"; TARGET=_self>'. - &mt('Compose message to student').(scalar(@col_fullnames) >= 1 ? 's' : '').'</a><label> ('. + ',\''.$msgfor.'\');" target="_self">'. + &mt('Compose message to student').(scalar(@$col_fullnames) >= 1 ? 's' : '').'</a><label> ('. &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" /></label>)'. '<img src="'.$request->dir_config('lonIconsURL'). '/mailbkgrd.gif" width="14" height="10" name="mailicon'.$counter.'" />'."\n". '<br /> ('. - &mt('Message will be sent when you click on Save & Next below.').")\n"; + &mt('Message will be sent when you click on Save & Next below.').")\n"; + $result.='</div></div>'; $request->print($result); } - if ($perm{'vgr'}) { - $request->print('<br />'. - &Apache::loncommon::track_student_link(&mt('View recent activity'), - $uname,$udom,'check')); - } - if ($perm{'opa'}) { - $request->print('<br />'. - &Apache::loncommon::pprmlink(&mt('Set/Change parameters'), - $uname,$udom,$symb,'check')); - } my %seen = (); my @partlist; my @gradePartRespid; my @part_response_id = &flatten_responseType($responseType); + $request->print('<div class="LC_grade_assign">'. + + '<div class="LC_grade_assign_header">'. + &mt('Assign Grades').'</div>'. + '<div class="LC_grade_assign_body">'); foreach my $part_response_id (@part_response_id) { my ($partid,$respid) = @{ $part_response_id }; my $part_resp = join('_',@{ $part_response_id }); @@ -2059,6 +2183,21 @@ KEYWORDS push @gradePartRespid,$partid.'.'.$respid; $request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record)); } + $request->print('</div></div>'); + + $request->print('<div class="LC_grade_info_links">'); + if ($perm{'vgr'}) { + $request->print( + &Apache::loncommon::track_student_link(&mt('View recent activity'), + $uname,$udom,'check')); + } + if ($perm{'opa'}) { + $request->print( + &Apache::loncommon::pprmlink(&mt('Set/Change parameters'), + $uname,$udom,$symb,'check')); + } + $request->print('</div>'); + $result='<input type="hidden" name="partlist'.$counter. '" value="'.(join ":",@partlist).'" />'."\n"; $result.='<input type="hidden" name="gradePartRespid'. @@ -2069,14 +2208,20 @@ KEYWORDS $partlist[$ctr].'" />'."\n"; $ctr++; } - $request->print($result.'</td></tr></table></td></tr></table>'."\n"); + $request->print($result.''."\n"); + +# Done with printing info for one student + + $request->print('</div>');#LC_grade_show_user_body + $request->print('</div>');#LC_grade_show_user + # print end of form if ($counter == $total) { my $endform='<table border="0"><tr><td>'."\n"; $endform.='<input type="button" value="Save & Next" '. 'onClick="javascript:checksubmit(this.form,\'Save & Next\','. - $total.','.scalar(@partlist).');" TARGET=_self> '."\n"; + $total.','.scalar(@partlist).');" target="_self" /> '."\n"; my $ntstu ='<select name="NTSTU">'. '<option>1</option><option>2</option>'. '<option>3</option><option>5</option>'. @@ -2085,9 +2230,9 @@ KEYWORDS $ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</; $endform.=$ntstu.'student(s) '; $endform.='<input type="button" value="Previous" '. - 'onClick="javascript:checksubmit(this.form,\'Previous\');" TARGET=_self> '."\n". + 'onClick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> '."\n". '<input type="button" value="Next" '. - 'onClick="javascript:checksubmit(this.form,\'Next\');" TARGET=_self> '; + 'onClick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> '; $endform.='(Next and Previous (student) do not save the scores.)'."\n" ; $endform.="<input type='hidden' value='".&get_increment(). "' name='increment' />"; @@ -2098,6 +2243,62 @@ KEYWORDS return ''; } +sub check_collaborators { + my ($symb,$uname,$udom,$record,$handgrade,$counter) = @_; + my ($result,@col_fullnames); + my ($classlist,undef,$fullname) = &getclasslist('all','0'); + foreach my $part (keys(%$handgrade)) { + my $ncol = &Apache::lonnet::EXT('resource.'.$part. + '.maxcollaborators', + $symb,$udom,$uname); + next if ($ncol <= 0); + $part =~ s/\_/\./g; + next if ($record->{'resource.'.$part.'.collaborators'} eq ''); + my (@good_collaborators, @bad_collaborators); + foreach my $possible_collaborator + (split(/,?\s+/,$record->{'resource.'.$part.'.collaborators'})) { + $possible_collaborator =~ s/[\$\^\(\)]//g; + next if ($possible_collaborator eq ''); + my ($co_name,$co_dom) = split(/\@|:/,$possible_collaborator); + $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i); + next if ($co_name eq $uname && $co_dom eq $udom); + # Doing this grep allows 'fuzzy' specification + my @matches = grep(/^\Q$co_name\E:\Q$co_dom\E$/i, + keys(%$classlist)); + if (! scalar(@matches)) { + push(@bad_collaborators, $possible_collaborator); + } else { + push(@good_collaborators, @matches); + } + } + if (scalar(@good_collaborators) != 0) { + $result.='<br />'.&mt('Collaborators: '); + foreach my $name (@good_collaborators) { + my ($lastname,$givenn) = split(/,/,$$fullname{$name}); + push(@col_fullnames, $givenn.' '.$lastname); + $result.=$fullname->{$name}.' '; + } + $result.='<br />'."\n"; + my ($part)=split(/\./,$part); + $result.='<input type="hidden" name="collaborator'.$counter. + '" value="'.$part.':'.(join ':',@good_collaborators).'" />'. + "\n"; + } + if (scalar(@bad_collaborators) > 0) { + $result.='<div class="LC_warning">'; + $result.=&mt('This student has submitted [quant,_1,invalid collaborator]: [_2]',scalar(@bad_collaborators),join(', ',@bad_collaborators)); + $result .= '</div>'; + } + if (scalar(@bad_collaborators > $ncol)) { + $result .= '<div class="LC_warning">'; + $result .= &mt('This student has submitted too many '. + 'collaborators. Maximum is [_1].',$ncol); + $result .= '</div>'; + } + } + return ($result,$fullname,\@col_fullnames); +} + #--- Retrieve the last submission for all the parts sub get_last_submission { my ($returnhash)=@_; @@ -2169,18 +2370,10 @@ sub processHandGrade { } my $includemsg = $env{'form.includemsg'.$ctr}; my ($subject,$message,$msgstatus) = ('','',''); - my $restitle = &Apache::lonnet::gettitle($symb); - my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl', - $symb,$udom,$uname); - my ($feedurl,$baseurl,$showsymb,$messagetail); - $feedurl = &Apache::lonnet::clutter($url); - if ($encrypturl =~ /^yes$/i) { - $baseurl = &Apache::lonenc::encrypted($feedurl,1); - $showsymb = &Apache::lonenc::encrypted($symb,1); - } else { - $baseurl = $feedurl; - $showsymb = $symb; - } + my $restitle = &Apache::lonnet::gettitle($symb); + my ($feedurl,$showsymb) = + &get_feedurl_and_symb($symb,$uname,$udom); + my $messagetail; if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) { $subject = $env{'form.msgsub'} if ($includemsg =~ /msgsub/); unless ($subject=~/\w/) { $subject=&mt('Grading Feedback'); } @@ -2193,12 +2386,12 @@ sub processHandGrade { if ($env{'form.withgrades'.$ctr}) { $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt; $messagetail = " for <a href=\"". - $baseurl."?symb=$showsymb\">$env{'form.probTitle'}</a>"; + $feedurl."?symb=$showsymb\">$env{'form.probTitle'}</a>"; } $msgstatus = &Apache::lonmsg::user_normal_msg($uname,$udom,$subject, $message.$messagetail, - undef,$baseurl,undef, + undef,$feedurl,undef, undef,undef,$showsymb, $restitle); $request->print('<br />'.&mt('Sending message to [_1]:[_2]',$uname,$udom).': '. @@ -2215,26 +2408,16 @@ sub processHandGrade { if ($errorflag eq 'not_allowed') { $request->print("<span class=\"LC_error\">".&mt('Not allowed to modify grades for [_1]',"$collaborator:$udom")."</span>"); next; - } else { - if ($message ne '') { - $encrypturl= - &Apache::lonnet::EXT('resource.0.encrypturl', - $symb,$udom,$collaborator); - if ($encrypturl =~ /^yes$/i) { - $baseurl = &Apache::lonenc::encrypted($feedurl,1); - $showsymb = &Apache::lonenc::encrypted($symb,1); - } else { - $baseurl = $feedurl; - $showsymb = $symb; - } - if ($env{'form.withgrades'.$ctr}) { - $messagetail = " for <a href=\"". + } elsif ($message ne '') { + my ($baseurl,$showsymb) = + &get_feedurl_and_symb($symb,$collaborator, + $udom); + if ($env{'form.withgrades'.$ctr}) { + $messagetail = " for <a href=\"". $baseurl."?symb=$showsymb\">$env{'form.probTitle'}</a>"; - - } - $msgstatus = - &Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle); } + $msgstatus = + &Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle); } } } @@ -2588,28 +2771,31 @@ sub handback_files { $message .= "<strong>".&Apache::lonnet::gettitle($symb)."</strong><br />"; $message .= ' The returned file(s) are named: '. $file_msg; $message .= " and can be found in your portfolio space."; - my $url = (&Apache::lonnet::decode_symb($symb))[2]; - my $feedurl = &Apache::lonnet::clutter($url); - my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl', - $symb,$domain,$stuname); - my ($baseurl,$showsymb); - if ($encrypturl =~ /^yes$/i) { - $baseurl = &Apache::lonenc::encrypted($feedurl,1); - $showsymb = &Apache::lonenc::encrypted($symb,1); - } else { - $baseurl = $feedurl; - $showsymb = $symb; - } + my ($feedurl,$showsymb) = + &get_feedurl_and_symb($symb,$domain,$stuname); my $restitle = &Apache::lonnet::gettitle($symb); my $msgstatus = &Apache::lonmsg::user_normal_msg($stuname,$domain,$subject. ' (File Returned) ['.$restitle.']',$message,undef, - $baseurl,undef,undef,undef,$showsymb,$restitle); + $feedurl,undef,undef,undef,$showsymb,$restitle); } } return; } +sub get_feedurl_and_symb { + my ($symb,$uname,$udom) = @_; + my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb); + $url = &Apache::lonnet::clutter($url); + my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl', + $symb,$udom,$uname); + if ($encrypturl =~ /^yes$/i) { + &Apache::lonenc::encrypted(\$url,1); + &Apache::lonenc::encrypted(\$symb,1); + } + return ($url,$symb); +} + sub get_submitted_files { my ($udom,$uname,$partid,$respid,$record) = @_; my @files; @@ -2748,7 +2934,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; @@ -2969,31 +3154,31 @@ 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="'.$symb.'" />'."\n". + '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n". '<input type="hidden" name="command" value="editgrades" />'."\n". - '<input type="hidden" name="section" value="'.$env{'form.section'}.'" />'."\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; + my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section')); if ($env{'form.section'} eq 'all') { $sectionClass='Class </h3>'; } elsif ($env{'form.section'} eq 'none') { - $sectionClass='Students in no Section </h3>'; + $sectionClass=&mt('Students in no Section').'</h3>'; } else { - $sectionClass='Students in Section '.$env{'form.section'}.'</h3>'; + $sectionClass=&mt('Students in Section(s) [_1]',$section_display).'</h3>'; } - $result.='<h3>Assign Common Grade To '.$sectionClass; - $result.= '<table border=0><tr><td bgcolor="#777777">'."\n". - '<table border=0><tr bgcolor="#ffffdd"><td>'; + $result.='<h3>'.&mt('Assign Common Grade To [_1]',$sectionClass); + $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); my %weight = (); my $ctsparts = 0; - $result.='<table border="0">'; my %seen = (); my @part_response_id = &flatten_responseType($responseType); foreach my $part_response_id (@part_response_id) { @@ -3005,12 +3190,14 @@ sub viewgrades { my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb); $weight{$partid} = $wgt eq '' ? '1' : $wgt; + $result.=&Apache::loncommon::start_data_table_row().'<td>'; $result.='<input type="hidden" name="partid_'. $ctsparts.'" value="'.$partid.'" />'."\n"; $result.='<input type="hidden" name="weight_'. $partid.'" value="'.$weight{$partid}.'" />'."\n"; my $display_part=&get_display_part($partid,$symb); - $result.='<tr><td><b>Part:</b> '.$display_part.' <b>Point:</b> </td><td>'; + $result.= + '<b>Part:</b> '.$display_part.' <b>Point:</b> </td><td>'; $result.='<table border="0"><tr>'; my $ctr = 0; while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across @@ -3025,26 +3212,27 @@ sub viewgrades { $partid.'" size="4" '.'onChange="javascript:writePoint(\''. $partid.'\','.$weight{$partid}.',\'textval\')" /> /'. $weight{$partid}.' (problem weight)</td>'."\n"; - $result.= '</td><td><select name="SELVAL_'.$partid.'"'. + $result.= '<td><select name="SELVAL_'.$partid.'"'. 'onChange="javascript:writeRadText(\''.$partid.'\','. $weight{$partid}.')"> '. '<option selected="selected"> </option>'. '<option>excused</option>'. '<option>reset status</option></select></td>'. - '<td><label><input type="checkbox" name="FORCE_'.$partid.'" /> Override "Correct"</label></td></tr>'."\n"; + '<td><label><input type="checkbox" name="FORCE_'.$partid.'" /> Override "Correct"</label></td>'.&Apache::loncommon::end_data_table_row()."\n"; $ctsparts++; } - $result.='</table>'.'</td></tr></table>'.'</td></tr></table>'."\n". + $result.=&Apache::loncommon::end_data_table()."\n". '<input type="hidden" name="totalparts" value="'.$ctsparts.'" />'; $result.='<input type="button" value="Revert to Default" '. - 'onClick="javascript:resetEntry('.$ctsparts.');" TARGET=_self>'; + 'onClick="javascript:resetEntry('.$ctsparts.');" />'; #table listing all the students in a section/class #header of table $result.= '<h3>Assign Grade to Specific Students in '.$sectionClass; - $result.= '<table border=0><tr><td bgcolor="#777777">'."\n". - '<table border=0><tr bgcolor="#deffff"><td> <b>No.</b> </td>'. - '<td>'.&nameUserString('header')."</td>\n"; + $result.= &Apache::loncommon::start_data_table(). + &Apache::loncommon::start_data_table_header_row(). + '<th>No.</th>'. + '<th>'.&nameUserString('header')."</th>\n"; my (@parts) = sort(&getpartlist($symb)); my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb); my @partids = (); @@ -3056,16 +3244,16 @@ sub viewgrades { push(@partids, $partid); my $display_part=&get_display_part($partid,$symb); if ($display =~ /^Partial Credit Factor/) { - $result.='<td><b>Score Part:</b> '.$display_part. - ' <br /><b>(weight = '.$weight{$partid}.')</b></td>'."\n"; + $result.='<th>Score Part: '.$display_part. + ' <br />(weight = '.$weight{$partid}.')</th>'."\n"; next; } else { $display =~s/\[Part: \Q$partid\E\]/Part:<\/b> $display_part/; } $display =~ s|Problem Status|Grade Status<br />|; - $result.='<td><b>'.$display.'</td>'."\n"; + $result.='<th>'.$display.'</th>'."\n"; } - $result.='</tr>'; + $result.=&Apache::loncommon::end_data_table_header_row(); my %last_resets = &get_last_resets($symb,$env{'request.course.id'},\@partids); @@ -3085,14 +3273,18 @@ sub viewgrades { $result.=&viewstudentgrade($symb,$env{'request.course.id'}, $_,$$fullname{$_},\@parts,\%weight,$ctr,\%last_resets); } - $result.='</table></td></tr></table>'; + $result.=&Apache::loncommon::end_data_table(); $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n"; $result.='<input type="button" value="Save" '. - 'onClick="javascript:submit();" TARGET=_self /></form>'."\n"; + 'onClick="javascript:submit();" target="_self" /></form>'."\n"; if (scalar(%$fullname) eq 0) { my $colspan=3+scalar(@parts); - $result='<span class="LC_warning">There are no students in section "'.$env{'form.section'}. - '" with enrollment status "'.$env{'form.Status'}.'" to modify or grade.</span>'; + 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, $stu_status). + '</span>'; } $result.=&show_grading_menu_form($symb); return $result; @@ -3104,11 +3296,11 @@ sub viewstudentgrade { my ($uname,$udom) = split(/:/,$student); my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname); my %aggregates = (); - my $result='<tr bgcolor="#ffffdd"><td align="right">'. + my $result=&Apache::loncommon::start_data_table_row().'<td align="right">'. '<input type="hidden" name="ctr'.($ctr-1).'" value="'.$student.'" />'. "\n".$ctr.' </td><td> '. '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom. - '\')"; TARGET=_self>'.$fullname.'</a> '. + '\');" target="_self">'.$fullname.'</a> '. '<span class="LC_internal_info">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</span></td>'."\n"; $student=~s/:/_/; # colon doen't work in javascript for names foreach my $apart (@$parts) { @@ -3159,7 +3351,7 @@ sub viewstudentgrade { 'value="'.$score.'" size="4" /></td>'."\n"; } } - $result.='</tr>'; + $result.=&Apache::loncommon::end_data_table_row(); return $result; } @@ -3169,15 +3361,15 @@ sub editgrades { my ($request) = @_; my $symb=&get_symb($request); - my $title='<h3><span class="LC_info">Current Grade Status</span></h3>'; - $title.='<h4><b>Current Resource: </b>'.$env{'form.probTitle'}.'</h4><br />'."\n"; - $title.='<h4><b>Section: </b>'.$env{'form.section'}.'</h4>'."\n"; - - my $result= '<table border="0"><tr><td bgcolor="#777777">'."\n"; - $result.= '<table border="0"><tr bgcolor="#deffff">'. - '<td rowspan=2 valign="center"> <b>No.</b> </td>'. - '<td rowspan=2 valign="center">'.&nameUserString('header')."</td>\n"; - + my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section')); + my $title='<h2>'.&mt('Current Grade Status').'</h2>'; + $title.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4>'."\n"; + $title.='<h4>'.&mt('<b>Section: </b>[_1]',$section_display).'</h4>'."\n"; + + my $result= &Apache::loncommon::start_data_table(). + &Apache::loncommon::start_data_table_header_row(). + '<th rowspan="2" valign="middle">'.&mt('No.').'</th>'. + '<th rowspan="2" valign="middle">'.&nameUserString('header')."</th>\n"; my %scoreptr = ( 'correct' =>'correct_by_override', 'incorrect'=>'incorrect_by_override', @@ -3202,8 +3394,8 @@ sub editgrades { } my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb); foreach my $partid (@partid) { - $header .= '<td align="center"> <b>Old Score</b> </td>'. - '<td align="center"> <b>New Score</b> </td>'; + $header .= '<th align="center">Old Score</th>'. + '<th align="center">New Score</th>'; $columns{$partid}=2; foreach my $stores (@parts) { my ($part,$type) = &split_part_type($stores); @@ -3212,22 +3404,23 @@ sub editgrades { my $display=&Apache::lonnet::metadata($url,$stores.'.display'); $display =~ s/\[Part: (\w)+\]//; $display =~ s/Number of Attempts/Tries/; - $header .= '<td align="center"> <b>Old '.$display.'</b> </td>'. - '<td align="center"> <b>New '.$display.'</b> </td>'; + $header .= '<th align="center">Old '.$display.'</th>'. + '<th align="center">New '.$display.'</th>'; $columns{$partid}+=2; } } foreach my $partid (@partid) { my $display_part=&get_display_part($partid,$symb); - $result .= '<td colspan="'.$columns{$partid}. - '" align="center"><b>Part:</b> '.$display_part. - ' (Weight = '.$weight{$partid}.')</td>'; + $result .= '<th colspan="'.$columns{$partid}. + '" align="center">Part: '.$display_part. + ' (Weight = '.$weight{$partid}.')</th>'; } - $result .= '</tr><tr bgcolor="#deffff">'; - $result .= $header; - $result .= '</tr>'."\n"; - my $noupdate; + $result .= &Apache::loncommon::end_data_table_header_row(). + &Apache::loncommon::start_data_table_header_row(). + $header. + &Apache::loncommon::end_data_table_header_row(); + my @noupdate; my ($updateCtr,$noupdateCtr) = (1,1); for ($i=0; $i<$env{'form.total'}; $i++) { my $line; @@ -3239,7 +3432,8 @@ sub editgrades { my $usec=$classlist->{"$uname:$udom"}[5]; if (!&canmodify($usec)) { my $numcols=scalar(@partid)*4+2; - $noupdate.=$line."<td colspan=\"$numcols\"><span class=\"LC_warning\">Not allowed to modify student</span></td></tr>"; + push(@noupdate, + $line."<td colspan=\"$numcols\"><span class=\"LC_warning\">Not allowed to modify student</span></td></tr>"); next; } my %aggregate = (); @@ -3308,7 +3502,7 @@ sub editgrades { '<td align="center">'.$awarded.' </td>'; } } - $line.='</tr>'."\n"; + $line.="\n"; my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; @@ -3341,10 +3535,13 @@ sub editgrades { } } - $result.='<tr bgcolor="#ffffde"><td align="right"> '.$updateCtr.' </td>'.$line; + $result.=&Apache::loncommon::start_data_table_row(). + '<td align="right"> '.$updateCtr.' </td>'.$line. + &Apache::loncommon::end_data_table_row(); $updateCtr++; } else { - $noupdate.='<tr bgcolor="#ffffde"><td align="right"> '.$noupdateCtr.' </td>'.$line; + push(@noupdate, + '<td align="right"> '.$noupdateCtr.' </td>'.$line); $noupdateCtr++; } if ($aggregateflag) { @@ -3352,16 +3549,24 @@ sub editgrades { $cdom,$cnum); } } - if ($noupdate) { + if (@noupdate) { # my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3; my $numcols=scalar(@partid)*4+2; - $result .= '<tr bgcolor="#ffffff"><td align="center" colspan="'.$numcols.'">No Changes Occurred For the Students Below</td></tr><tr bgcolor="#ffffde">'.$noupdate; + $result .= &Apache::loncommon::start_data_table_row('LC_empty_row'). + '<td align="center" colspan="'.$numcols.'">No Changes Occurred For the Students Below</td>'. + &Apache::loncommon::end_data_table_row(); + foreach my $line (@noupdate) { + $result.= + &Apache::loncommon::start_data_table_row(). + $line. + &Apache::loncommon::end_data_table_row(); + } } - $result .= '</table></td></tr></table>'."\n". - &show_grading_menu_form ($symb); - my $msg = '<br /><b>Number of records updated = '.$rec_update. + $result .= &Apache::loncommon::end_data_table(). + &show_grading_menu_form($symb); + my $msg = '<p><b>Number of records updated = '.$rec_update. ' for '.$count.' student'.($count <= 1 ? '' : 's').'.</b><br />'. - '<b>Total number of students = '.$env{'form.total'}.'</b><br />'; + '<b>Total number of students = '.$env{'form.total'}.'</b></p>'; return $title.$msg.$result; } @@ -3369,7 +3574,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); } @@ -3472,6 +3677,7 @@ sub csvuploadmap_header { my ($result) = &showResourceInfo($symb,$env{'form.probTitle'}); my $checked=(($env{'form.noFirstLine'})?' checked="checked"':''); my $ignore=&mt('Ignore First Line'); + $symb = &Apache::lonenc::check_encrypt($symb); $request->print(<<ENDPICK); <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload"> <h3><span class="LC_info">Uploading Class Grades</span></h3> @@ -3566,6 +3772,7 @@ sub upcsvScores_form { my $upload=&mt("Upload Scores"); my $upfile_select=&Apache::loncommon::upfile_select_html(); my $ignore=&mt('Ignore First Line'); + $symb = &Apache::lonenc::check_encrypt($symb); $result.=<<ENDUPFORM; <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload"> <input type="hidden" name="symb" value="$symb" /> @@ -3751,7 +3958,8 @@ sub csvuploadassign { if ($wgt) { $entries{$fields{$dest}}=~s/\s//g; my $pcr=$entries{$fields{$dest}} / $wgt; - my $award='correct_by_override'; + my $award=($pcr == 0) ? 'incorrect_by_override' + : 'correct_by_override'; $grades{"resource.$part.awarded"}=$pcr; $grades{"resource.$part.solved"}=$award; $points{$part}=1; @@ -3773,7 +3981,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); @@ -3840,7 +4047,7 @@ LISTJAVASCRIPT $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n"; $result.=' <b>Problems from:</b> <select name="selectpage">'."\n"; - my ($titles,$symbx) = &getSymbMap($request); + my ($titles,$symbx) = &getSymbMap(); my ($curpage) =&Apache::lonnet::decode_symb($symb); # my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb); # my $type=($curpage =~ /\.(page|sequence)/); @@ -3870,11 +4077,12 @@ LISTJAVASCRIPT '<label><input type="radio" name="lastSub" value="none" /> none</label>'."\n". '<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> by dates and submissions</label>'."\n". '<label><input type="radio" name="lastSub" value="all" /> all details</label>'."\n"; - - $result.='<input type="hidden" name="section" value="'.$getsec.'" />'."\n". - '<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n". + + $result.=&build_section_inputs(); + 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="'.$symb.'" />'."\n". + '<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>'. @@ -3922,7 +4130,6 @@ LISTJAVASCRIPT } sub getSymbMap { - my ($request) = @_; my $navmap = Apache::lonnavmaps::navmap->new(); my %symbx = (); @@ -3998,7 +4205,7 @@ sub displayPage { '<input type="hidden" name="student" value="'.$env{'form.student'}.'" />'."\n". '<input type="hidden" name="page" value="'.$pageTitle.'" />'."\n". '<input type="hidden" name="title" value="'.$env{'form.title'}.'" />'."\n". - '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". + '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n". '<input type="hidden" name="overRideScore" value="no" />'."\n". '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n"; @@ -4103,31 +4310,33 @@ sub displaySubByDates { my $isCODE=0; my $isTask = ($symb =~/\.task$/); if (exists($record->{'resource.CODE'})) { $isCODE=1; } - my $studentTable='<table border="0" width="100%"><tr><td bgcolor="#777777">'. - '<table border="0" width="100%"><tr bgcolor="#e6ffff">'. - '<td><b>Date/Time</b></td>'. - ($isCODE?'<td><b>CODE</b></td>':''). - '<td><b>Submission</b></td>'. - '<td><b>Status </b></td></tr>'; + my $studentTable=&Apache::loncommon::start_data_table(). + &Apache::loncommon::start_data_table_header_row(). + '<th>'.&mt('Date/Time').'</th>'. + ($isCODE?'<th>'.&mt('CODE').'</th>':''). + '<th>'.&mt('Submission').'</th>'. + '<th>'.&mt('Status').'</th>'. + &Apache::loncommon::end_data_table_header_row(); my ($version); my %mark; my %orders; $mark{'correct_by_student'} = $checkIcon; if (!exists($$record{'1:timestamp'})) { - return '<br /> <span class="LC_warning">Nothing submitted - no attempts</span><br />'; + return '<br /> <span class="LC_warning">'.&mt('Nothing submitted - no attempts').'</span><br />'; } my $interaction; for ($version=1;$version<=$$record{'version'};$version++) { - my $timestamp = scalar(localtime($$record{$version.':timestamp'})); + my $timestamp = + &Apache::lonlocal::locallocaltime($$record{$version.':timestamp'}); if (exists($$record{$version.':resource.0.version'})) { $interaction = $$record{$version.':resource.0.version'}; } my $where = ($isTask ? "$version:resource.$interaction" : "$version:resource"); - #&Apache::lonnet::logthis(" got $where"); - $studentTable.='<tr bgcolor="#ffffff" valign="top"><td>'.$timestamp.'</td>'; + $studentTable.=&Apache::loncommon::start_data_table_row(). + '<td>'.$timestamp.'</td>'; if ($isCODE) { $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>'; } @@ -4146,15 +4355,14 @@ 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 '. + $displaySub[0].='<b>'.&mt('Part:').'</b> '.$display_part.' '; + $displaySub[0].='<span class="LC_internal_info">('.&mt('ID').' '. $responseId.')</span> <b>'; if ($$record{"$where.$partid.tries"} eq '') { - $displaySub[0].='Trial not counted'; + $displaySub[0].=&mt('Trial not counted'); } else { - $displaySub[0].='Trial '. - $$record{"$where.$partid.tries"}; + $displaySub[0].=&mt('Trial [_1]', + $$record{"$where.$partid.tries"}); } my $responseType=($isTask ? 'Task' : $responseType->{$partid}->{$responseId}); @@ -4194,12 +4402,12 @@ sub displaySubByDates { } $studentTable.='<td>'.$displaySub[0].' </td><td>'.$displaySub[1]; if ($displaySub[2]) { - $studentTable.='Manually graded by '.$displaySub[2]; + $studentTable.=&mt('Manually graded by [_1]',$displaySub[2]); } - $studentTable.=' </td></tr>'; - + $studentTable.=' </td>'. + &Apache::loncommon::end_data_table_row(); } - $studentTable.='</table></td></tr></table>'; + $studentTable.=&Apache::loncommon::end_data_table(); return $studentTable; } @@ -4367,18 +4575,88 @@ sub updateGradeByPage { # #------ start of section for handling grading by page/sequence --------- +=pod + +=head1 Bubble sheet grading routines + + For this documentation: + + 'scanline' refers to the full line of characters + from the file that we are parsing that represents one entire sheet + + 'bubble line' refers to the data + representing the line of bubbles that are on the physical bubble sheet + + +The overall process is that a scanned in bubble sheet data is uploaded +into a course. When a user wants to grade, they select a +sequence/folder of resources, a file of bubble sheet info, and pick +one of the predefined configurations for what each scanline looks +like. + +Next each scanline is checked for any errors of either 'missing +bubbles' (it's an error because it may have been mis-scanned +because too light bubbling), 'double bubble' (each bubble line should +have no more that one letter picked), invalid or duplicated CODE, +invalid student 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 +username:domain. + +During the validation phase the instructor can choose to skip scanlines. + +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) + scantron_skipped_filename (contains the exact text of scanlines that where skipped) + +Also there is a separate hash nohist_scantrondata that contains extra +correction information that isn't representable in the bubble sheet +file (see &scantron_getfile() for more information) + +After all scanlines are either valid, marked as valid or skipped, then +foreach line foreach problem in the picked sequence, an ssi request is +made that simulates a user submitting their selected letter(s) against +the homework problem. + +=over 4 + + + +=item defaultFormData + + Returns html hidden inputs used to hold context/default values. + + Arguments: + $symb - $symb of the current resource + +=cut + sub defaultFormData { my ($symb)=@_; - return ' - <input type="hidden" name="symb" value="'.$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 + + Return html dropdown of possible sequences to grade + + Arguments: + $symb - $symb of the current resource + +=cut + sub getSequenceDropDown { - my ($request,$symb)=@_; + my ($symb)=@_; my $result='<select name="selectpage">'."\n"; - my ($titles,$symbx) = &getSymbMap($request); + my ($titles,$symbx) = &getSymbMap(); my ($curpage)=&Apache::lonnet::decode_symb($symb); my $ctr=0; foreach (@$titles) { @@ -4392,6 +4670,15 @@ sub getSequenceDropDown { return $result; } + +=pod + +=item scantron_filenames + + Returns a list of the scantron files in the current course + +=cut + sub scantron_filenames { my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'}; my $cname=$env{'course.'.$env{'request.course.id'}.'.num'}; @@ -4407,6 +4694,17 @@ sub scantron_filenames { return @possiblenames; } +=pod + +=item scantron_uploads + + Returns html drop-down list of scantron files in current course. + + Arguments: + $file2grade - filename to set as selected in the dropdown + +=cut + sub scantron_uploads { my ($file2grade) = @_; my $result= '<select name="scantron_selectfile">'; @@ -4418,6 +4716,15 @@ sub scantron_uploads { return $result; } +=pod + +=item scantron_scantab + + Returns html drop down of the scantron formats in the scantronformat.tab + file. + +=cut + sub scantron_scantab { my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab'); my $result='<select name="scantron_format">'."\n"; @@ -4432,6 +4739,15 @@ sub scantron_scantab { return $result; } +=pod + +=item scantron_CODElist + + Returns html drop down of the saved CODE lists from current course, + generated from earlier printings. + +=cut + sub scantron_CODElist { my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; @@ -4446,23 +4762,48 @@ sub scantron_CODElist { return $namechoice; } +=pod + +=item scantron_CODEunique + + Returns the html for "Each CODE to be used once" radio. + +=cut + sub scantron_CODEunique { my $result='<span style="white-space: nowrap;"> <label><input type="radio" name="scantron_CODEunique" - value="yes" checked="checked" /> Yes </label> + value="yes" checked="checked" />'.&mt('Yes').' </label> </span> <span style="white-space: nowrap;"> <label><input type="radio" name="scantron_CODEunique" - value="no" /> No </label> + value="no" />'.&mt('No').' </label> </span>'; return $result; } +=pod + +=item scantron_selectphase + + Generates the initial screen to start the bubble sheet process. + Allows for - starting a grading run. + - downloading existing scan data (original, corrected + or skipped info) + + - uploading new scan data + + Arguments: + $r - The Apache request object + $file2grade - name of the file that contain the scanned data to score + +=cut + sub scantron_selectphase { my ($r,$file2grade) = @_; my ($symb)=&get_symb($r); if (!$symb) {return '';} - my $sequence_selector=&getSequenceDropDown($r,$symb); + my $sequence_selector=&getSequenceDropDown($symb); my $default_form_data=&defaultFormData($symb); my $grading_menu_button=&show_grading_menu_form($symb); my $file_selector=&scantron_uploads($file2grade); @@ -4470,8 +4811,9 @@ sub scantron_selectphase { my $CODE_selector=&scantron_CODElist(); my $CODE_unique=&scantron_CODEunique(); my $result; - #FIXME allow instructor to be able to download the scantron file - # and to upload it, + + # Chunk of form to prompt for a file to grade and how: + $result.= <<SCANTRONFORM; <table width="100%" border="0"> <tr> @@ -4504,7 +4846,7 @@ sub scantron_selectphase { <td> Options: </td> <td> <label><input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> Do only previously skipped records</label> <br /> - <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> Remove all exisiting corrections</label> <br /> + <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> Remove all existing corrections</label> <br /> <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> Skip hidden resources when grading</label> </td> </tr> @@ -4524,6 +4866,8 @@ SCANTRONFORM if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) || &Apache::lonnet::allowed('usc',$env{'request.course.id'})) { + # Chunk of form to prompt for a scantron file upload. + $r->print(<<SCANTRONFORM); <tr> <td bgcolor="#777777"> @@ -4569,6 +4913,10 @@ UPLOAD </tr> SCANTRONFORM } + + # Chunk of the form that prompts to view a scoring office file, + # corrected file, skipped records in a file. + $r->print(<<SCANTRONFORM); <tr> <form action='/adm/grades' name='scantron_download'> @@ -4595,14 +4943,70 @@ 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 } +=pod + +=item get_scantron_config + + Parse and return the scantron configuration line selected as a + hash of configuration file fields. + + Arguments: + which - the name of the configuration to parse from the file. + + + Returns: + If the named configuration is not in the file, an empty + hash is returned. + a hash with the fields + name - internal name for the this configuration setup + description - text to display to operator that describes this config + CODElocation - if 0 or the string 'none' + - no CODE exists for this config + if -1 || the string 'letter' + - a CODE exists for this config and is + a string of letters + Unsupported value (but planned for future support) + if a positive integer + - The CODE exists as the first n items from + the question section of the form + if the string 'number' + - The CODE exists for this config and is + a string of numbers + 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 + Qstart - column where the information from the bubbled + 'questions' start + Qlength - number of columns comprising a single bubble line from + the sheet. (usually either 1 or 10) + Qon - either a single character representing the character used + to signal a bubble was chosen in the positional setup, or + the string 'letter' if the letter of the chosen bubble is + in the final, or 'number' if a number representing the + chosen bubble is in the file (1->A 0->J) + Qoff - the character used to represent that a bubble was + left blank + PaperID - if the scanning process generates a unique number for each + sheet scanned the column that this ID number starts in + PaperIDlength - number of columns that comprise the unique ID number + for the sheet of paper + FirstName - column that the first name starts in + FirstNameLength - number of columns that the first name spans + + LastName - column that the last name starts in + LastNameLength - number of columns that the last name spans + +=cut + sub get_scantron_config { my ($which) = @_; my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab'); @@ -4635,6 +5039,25 @@ sub get_scantron_config { return %config; } +=pod + +=item username_to_idmap + + creates a hash keyed by student id with values of the corresponding + student username:domain. + + Arguments: + + $classlist - reference to the class list hash. This is a hash + keyed by student name:domain whose elements are references + to arrays containing various chunks of information + about the student. (See loncoursedata for more info). + + Returns + %idmap - the constructed hash + +=cut + sub username_to_idmap { my ($classlist)= @_; my %idmap; @@ -4645,8 +5068,50 @@ sub username_to_idmap { return %idmap; } +=pod + +=item scantron_fixup_scanline + + Process a requested correction to a scanline. + + Arguments: + $scantron_config - hash from &get_scantron_config() + $scan_data - hash of correction information + (see &scantron_getfile()) + $line - existing scanline + $whichline - line number of the passed in scanline + $field - type of change to process + (either + 'ID' -> correct the student ID number + 'CODE' -> correct the CODE + 'answer' -> fixup the submitted answers) + + $args - hash of additional info, + - 'ID' + 'newid' -> studentID to use in replacement + of existing one + - 'CODE' + 'CODE_ignore_dup' - set to true if duplicates + should be ignored. + 'CODE' - is new code or 'use_unfound' + if the existing unfound code should + be used as is + - 'answer' + 'response' - new answer or 'none' if blank + 'question' - the bubble line to change + + Returns: + $line - the modified scanline + + Side effects: + $scan_data - may be updated + +=cut + + sub scantron_fixup_scanline { my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_; + if ($field eq 'ID') { if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) { return ($line,1,'New value too large'); @@ -4703,6 +5168,25 @@ sub scantron_fixup_scanline { return $line; } +=pod + +=item scan_data + + Edit or look up an item in the scan_data hash. + + Arguments: + $scan_data - The hash (see scantron_getfile) + $key - shorthand of the key to edit (actual key is + scantronfilename_key). + $data - New value of the hash entry. + $delete - If true, the entry is removed from the hash. + + Returns: + The new value of the hash table field (undefined if deleted). + +=cut + + sub scan_data { my ($scan_data,$key,$value,$delete)=@_; my $filename=$env{'form.scantron_selectfile'}; @@ -4713,11 +5197,69 @@ sub scan_data { return $scan_data->{$filename.'_'.$key}; } +=pod + +=item scantron_parse_scanline + + Decodes a scanline from the selected scantron file + + Arguments: + line - The text of the scantron file line to process + whichline - Line number + scantron_config - Hash describing the format of the scantron lines. + scan_data - Hash of extra information about the scanline + (see scantron_getfile for more information) + just_header - True if should not process question answers but only + the stuff to the left of the answers. + Returns: + Hash containing the result of parsing the scanline + + Keys are all proceeded by the string 'scantron.' + + CODE - the CODE in use for this scanline + useCODE - 1 if the CODE is invalid but it usage has been forced + by the operator + 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 + PaperID - if used, the ID number printed on the sheet when the + paper was scanned + FirstName - first name from the sheet + LastName - last name from the sheet + + if just_header was not true these key may also exist + + 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) + <number>.answer - zero or more letters representing the selected + letters from the scanline for the bubble line + <number>. + if blank there was either no bubble or there where + multiple bubbles, (consult the keys missingerror and + doubleerror if this is an error condition) + +=cut + sub scantron_parse_scanline { - my ($line,$whichline,$scantron_config,$scan_data,$justHeader)=@_; + my ($line,$whichline,$scantron_config,$scan_data,$just_header)=@_; + my %record; - my $questions=substr($line,$$scantron_config{'Qstart'}-1); - my $data=substr($line,0,$$scantron_config{'Qstart'}-1); + 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')) { if ($$scantron_config{'CODElocation'} < 0 || @@ -4747,72 +5289,168 @@ sub scantron_parse_scanline { $record{'scantron.LastName'}= substr($data,$$scantron_config{'LastName'}-1, $$scantron_config{'LastNamelength'}); - if ($justHeader) { return \%record; } + if ($just_header) { return \%record; } my @alphabet=('A'..'Z'); my $questnum=0; - while ($questions) { + my $ansnum =1; # Multiple 'answer lines'/question. + + chomp($questions); # Get rid of any trailing \n. + $questions =~ s/\r$//; # Get rid of trailing \r too (MAC or Win uploads). + while (length($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++) { + my $item = $first_answer+$ans; + $record{"scantron.$item.answer"} = ''; + } + my @ans=@array; - my $i=length($ans[0]);shift(@ans); + my $i=0; + my $increment = 0; while ($#ans) { - $i+=length($ans[0])+1; - $record{"scantron.$questnum.answer"}.=$alphabet[$i]; + $i+=length($ans[0]) + $increment; + my $line = int($i/$$scantron_config{'Qlength'} + $first_answer); + my $bubble = $i%$$scantron_config{'Qlength'}; + $record{"scantron.$line.answer"}.=$alphabet[$bubble]; shift(@ans); + $increment = 1; } + $ansnum += $answers_needed; } } } @@ -4820,6 +5458,24 @@ sub scantron_parse_scanline { return \%record; } +=pod + +=item scantron_add_delay + + Adds an error message that occurred during the grading phase to a + queue of messages to be shown after grading pass is complete + + Arguments: + $delayqueue - arrary ref of hash ref of error messages + $scanline - the scanline that caused the error + $errormesage - the error message + $errorcode - a numeric code for the error + + Side Effects: + updates the $delayqueue to have a new hash ref of the error + +=cut + sub scantron_add_delay { my ($delayqueue,$scanline,$errormessage,$errorcode)=@_; push(@$delayqueue, @@ -4828,6 +5484,24 @@ sub scantron_add_delay { ); } +=pod + +=item scantron_find_student + + Finds the username for the current scanline + + Arguments: + $scantron_record - hash result from scantron_parse_scanline + $scan_data - hash of correction information + (see &scantron_getfile() form more information) + $idmap - hash from &username_to_idmap() + $line - number of current scanline + + Returns: + Either 'username:domain' or undef if unknown + +=cut + sub scantron_find_student { my ($scantron_record,$scan_data,$idmap,$line)=@_; my $scanID=$$scantron_record{'scantron.ID'}; @@ -4842,6 +5516,15 @@ sub scantron_find_student { return undef; } +=pod + +=item scantron_filter + + Filter sub for lonnavmaps, filters out hidden resources if ignore + hidden resources was selected + +=cut + sub scantron_filter { my ($curres)=@_; @@ -4858,6 +5541,15 @@ sub scantron_filter { return 0; } +=pod + +=item scantron_process_corrections + + Gets correction information out of submitted form data and corrects + the scanline + +=cut + sub scantron_process_corrections { my ($r) = @_; my %scantron_config=&get_scantron_config($env{'form.scantron_format'}); @@ -4915,12 +5607,30 @@ sub scantron_process_corrections { } } +=pod + +=item reset_skipping_status + + Forgets the current set of remember skipped scanlines (and thus + reverts back to considering all lines in the + scantron_skipped_<filename> file) + +=cut + sub reset_skipping_status { my ($scanlines,$scan_data)=&scantron_getfile(); &scan_data($scan_data,'remember_skipping',undef,1); &scantron_putfile(undef,$scan_data); } +=pod + +=item start_skipping + + Marks a scanline to be skipped. + +=cut + sub start_skipping { my ($scan_data,$i)=@_; my %remembered=split(':',&scan_data($scan_data,'remember_skipping')); @@ -4932,6 +5642,14 @@ sub start_skipping { &scan_data($scan_data,'remember_skipping',join(':',%remembered)); } +=pod + +=item should_be_skipped + + Checks whether a scanline should be skipped. + +=cut + sub should_be_skipped { my ($scanlines,$scan_data,$i)=@_; if ($env{'form.scantron_options_redo'} !~ /^redo_/) { @@ -4947,6 +5665,15 @@ sub should_be_skipped { return 1; } +=pod + +=item remember_current_skipped + + Discovers what scanlines are in the scantron_skipped_<filename> + file and remembers them into scan_data for later use. + +=cut + sub remember_current_skipped { my ($scanlines,$scan_data)=&scantron_getfile(); my %to_remember; @@ -4960,6 +5687,16 @@ sub remember_current_skipped { &scantron_putfile(undef,$scan_data); } +=pod + +=item check_for_error + + Checks if there was an error when attempting to remove a specific + scantron_.. bubble sheet data file. Prints out an error if + something went wrong. + +=cut + sub check_for_error { my ($r,$result)=@_; if ($result ne 'ok' && $result ne 'not_found' ) { @@ -4967,6 +5704,15 @@ sub check_for_error { } } +=pod + +=item scantron_warning_screen + + Interstitial screen to make sure the operator has selected the + correct options before we start the validation phase. + +=cut + sub scantron_warning_screen { my ($button_text)=@_; my $title=&Apache::lonnet::gettitle($env{'form.selectpage'}); @@ -4999,6 +5745,15 @@ $CODElist STUFF } +=pod + +=item scantron_do_warning + + Check if the operator has picked something for all required + fields. Error out if something is missing. + +=cut + sub scantron_do_warning { my ($r)=@_; my ($symb)=&get_symb($r); @@ -5030,6 +5785,14 @@ STUFF return ''; } +=pod + +=item scantron_form_start + + html hidden input for remembering all selected grading options + +=cut + sub scantron_form_start { my ($max_bubble)=@_; my $result= <<SCANTRONFORM; @@ -5044,9 +5807,31 @@ 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; } +=pod + +=item scantron_validate_file + + Dispatch routine for doing validation of a bubble sheet data file. + + Also processes any necessary information resets that need to + occur before validation begins (ignore previous corrections, + restarting the skipped records processing) + +=cut + sub scantron_validate_file { my ($r) = @_; my ($symb)=&get_symb($r); @@ -5054,7 +5839,7 @@ sub scantron_validate_file { my $default_form_data=&defaultFormData($symb); # do the detection of only doing skipped records first befroe we delete - # them when doing the corrections reset + # them when doing the corrections reset if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') { &reset_skipping_status(); } @@ -5073,7 +5858,7 @@ sub scantron_validate_file { if ($env{'form.scantron_corrections'}) { &scantron_process_corrections($r); } - $r->print("<p>Gathering neccessary info.</p>");$r->rflush(); + $r->print("<p>Gathering necessary info.</p>");$r->rflush(); #get the student pick code ready $r->print(&Apache::loncommon::studentbrowser_javascript()); my $max_bubble=&scantron_get_maxbubble(); @@ -5090,6 +5875,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>"); @@ -5130,6 +5916,17 @@ STUFF return ''; } + +=pod + +=item scantron_remove_file + + Removes the requested bubble sheet data file, makes sure that + scantron_original_<filename> is never removed + + +=cut + sub scantron_remove_file { my ($which)=@_; my $cname=$env{'course.'.$env{'request.course.id'}.'.num'}; @@ -5144,6 +5941,18 @@ sub scantron_remove_file { return &Apache::lonnet::removeuserfile($cname,$cdom,$file); } + +=pod + +=item scantron_remove_scan_data + + Removes all scan_data correction for the requested bubble sheet + data file. (In the case that both the are doing skipped records we need + to remember the old skipped lines for the time being so that element + persists for a while.) + +=cut + sub scantron_remove_scan_data { my $cname=$env{'course.'.$env{'request.course.id'}.'.num'}; my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'}; @@ -5166,6 +5975,51 @@ sub scantron_remove_scan_data { return $result; } + +=pod + +=item scantron_getfile + + Fetches the requested bubble sheet data file (all 3 versions), and + the scan_data hash + + Arguments: + None + + Returns: + 2 hash references + + - first one has + orig - + corrected - + skipped - each of which points to an array ref of the specified + file broken up into individual lines + count - number of scanlines + + - second is the scan_data hash possible keys are + ($number refers to scanline numbered $number and thus the key affects + only that scanline + $bubline refers to the specific bubble line element and the aspects + refers to that specific bubble line element) + + $number.user - username:domain to use + $number.CODE_ignore_dup + - ignore the duplicate CODE error + $number.useCODE + - use the CODE in the scanline as is + $number.no_bubble.$bubline + - it is valid that there is no bubbled in bubble + at $number $bubline + remember_skipping + - a frozen hash containing keys of $number and values + of either + 1 - we are on a 'do skipped records pass' and plan + on processing this line + 2 - we are on a 'do skipped records pass' and this + scanline has been marked to skip yet again + +=cut + sub scantron_getfile { #FIXME really would prefer a scantron directory my $cname=$env{'course.'.$env{'request.course.id'}.'.num'}; @@ -5198,6 +6052,21 @@ sub scantron_getfile { return (\%scanlines,\%scan_data); } +=pod + +=item lonnet_putfile + + Wrapper routine to call &Apache::lonnet::finishuserfileupload + + Arguments: + $contents - data to store + $filename - filename to store $contents into + + Returns: + result value from &Apache::lonnet::finishuserfileupload + +=cut + sub lonnet_putfile { my ($contents,$filename)=@_; my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'}; @@ -5207,6 +6076,22 @@ sub lonnet_putfile { } +=pod + +=item scantron_putfile + + Stores the current version of the bubble sheet data files, and the + scan_data hash. (Does not modify the original version only the + corrected and skipped versions. + + Arguments: + $scanlines - hash ref that looks like the first return value from + &scantron_getfile() + $scan_data - hash ref that looks like the second return value from + &scantron_getfile() + +=cut + sub scantron_putfile { my ($scanlines,$scan_data) = @_; #FIXME really would prefer a scantron directory @@ -5227,6 +6112,28 @@ sub scantron_putfile { &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname); } +=pod + +=item scantron_get_line + + Returns the correct version of the scanline + + Arguments: + $scanlines - hash ref that looks like the first return value from + &scantron_getfile() + $scan_data - hash ref that looks like the second return value from + &scantron_getfile() + $i - number of the requested line (starts at 0) + + Returns: + A scanline, (either the original or the corrected one if it + exists), or undef if the requested scanline should be + skipped. (Either because it's an skipped scanline, or it's an + unskipped scanline and we are not doing a 'do skipped scanlines' + pass. + +=cut + sub scantron_get_line { my ($scanlines,$scan_data,$i)=@_; if (&should_be_skipped($scanlines,$scan_data,$i)) { return undef; } @@ -5235,6 +6142,23 @@ sub scantron_get_line { return $scanlines->{'orig'}[$i]; } +=pod + +=item scantron_todo_count + + Counts the number of scanlines that need processing. + + Arguments: + $scanlines - hash ref that looks like the first return value from + &scantron_getfile() + $scan_data - hash ref that looks like the second return value from + &scantron_getfile() + + Returns: + $count - number of scanlines to process + +=cut + sub get_todo_count { my ($scanlines,$scan_data)=@_; my $count=0; @@ -5246,6 +6170,25 @@ sub get_todo_count { return $count; } +=pod + +=item scantron_put_line + + Updates the 'corrected' or 'skipped' versions of the bubble sheet + data file. + + Arguments: + $scanlines - hash ref that looks like the first return value from + &scantron_getfile() + $scan_data - hash ref that looks like the second return value from + &scantron_getfile() + $i - line number to update + $newline - contents of the updated scanline + $skip - if true make the line for skipping and update the + 'skipped' file + +=cut + sub scantron_put_line { my ($scanlines,$scan_data,$i,$newline,$skip)=@_; if ($skip) { @@ -5256,6 +6199,21 @@ sub scantron_put_line { $scanlines->{'corrected'}[$i]=$newline; } +=pod + +=item scantron_clear_skip + + Remove a line from the 'skipped' file + + Arguments: + $scanlines - hash ref that looks like the first return value from + &scantron_getfile() + $scan_data - hash ref that looks like the second return value from + &scantron_getfile() + $i - line number to update + +=cut + sub scantron_clear_skip { my ($scanlines,$scan_data,$i)=@_; if (exists($scanlines->{'skipped'}[$i])) { @@ -5265,6 +6223,15 @@ sub scantron_clear_skip { return 0; } +=pod + +=item scantron_filter_not_exam + + Filter routine used by &Apache::lonnavmaps::retrieveResources(), to + filter out resources that are not marked as 'exam' mode + +=cut + sub scantron_filter_not_exam { my ($curres)=@_; @@ -5281,6 +6248,15 @@ sub scantron_filter_not_exam { return 0; } +=pod + +=item scantron_validate_sequence + + Validates the selected sequence, checking for resource that are + not set to exam mode. + +=cut + sub scantron_validate_sequence { my ($r,$currentphase) = @_; @@ -5304,6 +6280,15 @@ 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) = @_; @@ -5314,6 +6299,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++) { @@ -5366,10 +6353,40 @@ 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)=@_; -#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 @@ -5468,8 +6485,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"}; - &scantron_bubble_selector($r,$scan_config,$question,split('',$selected)); + my $selected = &get_response_bubbles($scan_record, $question); + my @select_array = split(/:/,$selected); + &scantron_bubble_selector($r,$scan_config,$question, + @select_array); } } elsif ($error eq 'missingbubble') { $r->print("<p>There have been <b>no</b> bubbles scanned for some question(s)</p>\n"); @@ -5479,8 +6498,9 @@ ENDSCRIPT $r->print('<input type="hidden" name="scantron_questions" value="'. join(',',@{$arg}).'" />'); foreach my $question (@{$arg}) { - my $selected=$$scan_record{"scantron.$question.answer"}; - &scantron_bubble_selector($r,$scan_config,$question); + my $selected = &get_response_bubbles($scan_record, $question); + my @select_array = split(/:/,$selected); # ought to be an array of empties. + &scantron_bubble_selector($r,$scan_config,$question, @select_array); } } else { $r->print("\n<ul>"); @@ -5489,32 +6509,98 @@ ENDSCRIPT } +=pod + +=item scantron_bubble_selector + + Generates the html radiobuttons to correct a single bubble line + possibly showing the existing the selected bubbles if known + + Arguments: + $r - Apache request object + $scan_config - hash from &get_scantron_config() + $quest - number of the bubble line to make a corrector for + @lines - array of answer lines. + +=cut + sub scantron_bubble_selector { - my ($r,$scan_config,$quest,@selected)=@_; + 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}; + + my $total_lines = $lines*2; my @alphabet=('A'..'Z'); - $r->print("<table border='1'><tr><td rowspan='2'>$quest</td>"); - for (my $i=0;$i<$max+1;$i++) { - $r->print("\n".'<td align="center">'); - if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) } - else { $r->print(' '); } - $r->print('</td>'); - } - $r->print('</tr><tr>'); - for (my $i=0;$i<$max;$i++) { - $r->print("\n". - '<td><label><input type="radio" name="scantron_correct_Q_'. - $quest.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>"); - } - $r->print('<td><label><input type="radio" name="scantron_correct_Q_'. + $r->print("<table border='1'><tr><td rowspan='".$total_lines."'>$quest</td>"); + + for (my $l = 0; $l < $lines; $l++) { + if ($l != 0) { + $r->print('<tr>'); + } + my @selected = split(//,$lines[$l]); + for (my $i=0;$i<$max;$i++) { + $r->print("\n".'<td align="center">'); + if ($selected[0] eq $alphabet[$i]) { + $r->print('X'); + shift(@selected) ; + } else { + $r->print(' '); + } + $r->print('</td>'); + + } + + if ($l == 0) { + my $lspan = $total_lines * 2; # 2 table rows per bubble line. + + $r->print('<td rowspan='.$lspan.'><label><input type="radio" name="scantron_correct_Q_'. $quest.'" value="none" /> No bubble </label></td>'); - $r->print('</tr></table>'); + + } + + $r->print('</tr><tr>'); + + # FIXME: This may have to be a bit more clever for + # multiline questions (different values e.g..). + + for (my $i=0;$i<$max;$i++) { + $r->print("\n". + '<td><label><input type="radio" name="scantron_correct_Q_'. + $quest.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>"); + } + $r->print('</tr>'); + + + } + $r->print('</table>'); } +=pod + +=item num_matches + + Counts the number of characters that are the same between the two arguments. + + Arguments: + $orig - CODE from the scanline + $code - CODE to match against + + Returns: + $count - integer count of the number of same characters between the + two arguments + +=cut + sub num_matches { my ($orig,$code) = @_; my @code=split(//,$code); @@ -5526,6 +6612,26 @@ sub num_matches { return $same; } +=pod + +=item scantron_get_closely_matching_CODEs + + Cycles through all CODEs and finds the set that has the greatest + number of same characters as the provided CODE + + Arguments: + $allcodes - hash ref returned by &get_codes() + $CODE - CODE from the current scanline + + Returns: + 2 element list + - first elements is number of how closely matching the best fit is + (5 means best set has 5 matching characters) + - second element is an arrary ref containing the set of valid CODEs + that best fit the passed in CODE + +=cut + sub scantron_get_closely_matching_CODEs { my ($allcodes,$CODE)=@_; my @CODEs; @@ -5536,6 +6642,23 @@ sub scantron_get_closely_matching_CODEs return ($#CODEs,$CODEs[-1]); } +=pod + +=item get_codes + + Builds a hash which has keys of all of the valid CODEs from the selected + set of remembered CODEs. + + Arguments: + $old_name - name of the set of remembered CODEs + $cdom - domain of the course + $cnum - internal course name + + Returns: + %allcodes - keys are the valid CODEs, values are all 1 + +=cut + sub get_codes { my ($old_name, $cdom, $cnum) = @_; if (!$old_name) { @@ -5558,6 +6681,16 @@ sub get_codes { return %allcodes; } +=pod + +=item scantron_validate_CODE + + Validates all scanlines in the selected file to not have any + invalid or underspecified CODEs and that none of the codes are + duplicated if this was requested. + +=cut + sub scantron_validate_CODE { my ($r,$currentphase) = @_; my %scantron_config=&get_scantron_config($env{'form.scantron_format'}); @@ -5575,6 +6708,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); @@ -5609,6 +6744,15 @@ sub scantron_validate_CODE { return (0,$currentphase+1); } +=pod + +=item scantron_validate_doublebubble + + Validates all scanlines in the selected file to not have any + bubble lines with multiple bubbles marked. + +=cut + sub scantron_validate_doublebubble { my ($r,$currentphase) = @_; #get student info @@ -5618,6 +6762,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; } @@ -5632,32 +6779,100 @@ 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'} 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'}; } +=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 @@ -5676,6 +6891,9 @@ sub scantron_validate_missingbubbles { $scan_data); if (!defined($$scan_record{'scantron.missingerror'})) { next; } my @to_correct; + + # Probably here's where the error is... + foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) { if ($missing > $max_bubble) { next; } push(@to_correct,$missing); @@ -5690,6 +6908,30 @@ 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) = @_; my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'}); @@ -5724,6 +6966,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++; @@ -5774,8 +7019,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; } } @@ -5794,6 +7037,14 @@ SCANTRONFORM 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'})); @@ -5830,6 +7081,15 @@ UPLOAD 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)=@_; my ($symb)=&get_symb($r,1); @@ -5885,6 +7145,14 @@ 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())) { @@ -5893,6 +7161,16 @@ 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)); @@ -5929,6 +7207,12 @@ DOWNLOAD return ''; } +=pod + +=back + +=cut + #-------- end of section for handling grading scantron forms ------- # #------------------------------------------------------------------- @@ -5939,7 +7223,7 @@ DOWNLOAD sub show_grading_menu_form { my ($symb)=@_; my $result.='<br /><form action="/adm/grades" method="post">'."\n". - '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". + '<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="command" value="gradingmenu" />'."\n". '<input type="submit" name="submit" value="Grading Menu" />'."\n". @@ -5959,8 +7243,127 @@ 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); + + $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.'" />'."\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 .="</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 '';} @@ -6003,9 +7406,8 @@ sub gradingmenu { </script> GRADINGMENUJS &commonJSfunctions($request); - my $result='<h3> <span class="LC_info">Manual Grading/View Submission</span></h3>'; my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle); - $result.=$table; + my $result; my (undef,$sections) = &getclasslist('all','0'); my $savedState = &savedState(); my $saveCmd = ($$savedState{'saveCmd'} eq '' ? 'submission' : $$savedState{'saveCmd'}); @@ -6014,7 +7416,7 @@ GRADINGMENUJS my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'}); $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n". - '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". + '<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.'" />'."\n". '<input type="hidden" name="command" value="" />'."\n". @@ -6022,88 +7424,104 @@ 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". - ' <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"><td>'."\n". - ' '.&mt('Select Section').': <select name="section">'."\n"; + $result.=' + <div class="LC_grade_select_mode"> + <div class="LC_grade_select_mode_current"> + <h2> + '.&mt('Grade Current Resource').' + </h2> + <div class="LC_grade_select_mode_body"> + <div class="LC_grades_resource_info"> + '.$table.' + </div> + <div class="LC_grade_select_mode_selector"> + <div class="LC_grade_select_mode_selector_header"> + '.&mt('Sections').' + </div> + <div class="LC_grade_select_mode_selector_body"> + <select name="section" multiple="multiple" size="5">'."\n"; if (ref($sections)) { - foreach (sort (@$sections)) { - $result.='<option value="'.$_.'" '. - ($saveSec eq $_ ? 'selected="selected"':'').'>'.$_.'</option>'."\n"; + foreach my $section (sort (@$sections)) { + $result.='<option value="'.$section.'" '. + ($saveSec eq $section ? 'selected="selected"':'').'>'.$section.'</option>'."\n"; } } $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> '; - - $result.=&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">'. - '<option value="yes" '. - ($saveSub eq 'yes' ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>'. - '<option value="queued" '. - ($saveSub eq 'queued' ? 'selected="selected"' : '').'>'.&mt('in grading queue').'</option>'. - '<option value="graded" '. - ($saveSub eq 'graded' ? 'selected="selected"' : '').'>'.&mt('with ungraded submissions').'</option>'. - '<option value="incorrect" '. - ($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>'. - '<option value="all" '. - ($saveSub eq 'all' ? 'selected="selected"' : '').'>'.&mt('with any status').'</option></select></td></tr>'."\n"; - - $result.='<tr bgcolor="#ffffe6"valign="top"><td>'. - '<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" '. - ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '. - 'The <b>complete</b> set/page/sequence: For one student</label></td></tr>'."\n"; - - $result.='<tr bgcolor="#ffffe6"><td><br />'. - '<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'. - '</td></tr></table>'."\n"; - - $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". - '</td></tr></table></form>'."\n"; + $result.=' + </div> + </div> + <div class="LC_grade_select_mode_selector"> + <div class="LC_grade_select_mode_selector_header"> + '.&mt('Groups').' + </div> + <div class="LC_grade_select_mode_selector_body"> + '.&Apache::lonstatistics::GroupSelect('group','multiple',5).' + </div> + </div> + <div class="LC_grade_select_mode_selector"> + <div class="LC_grade_select_mode_selector_header"> + '.&mt('Access Status').' + </div> + <div class="LC_grade_select_mode_selector_body"> + '.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,5,undef,'mult').' + </div> + </div> + <div class="LC_grade_select_mode_selector"> + <div class="LC_grade_select_mode_selector_header"> + '.&mt('Submission Status').' + </div> + <div class="LC_grade_select_mode_selector_body"> + <select name="submitonly" size="5"> + <option value="yes" '. ($saveSub eq 'yes' ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option> + <option value="queued" '. ($saveSub eq 'queued' ? 'selected="selected"' : '').'>'.&mt('in grading queue').'</option> + <option value="graded" '. ($saveSub eq 'graded' ? 'selected="selected"' : '').'>'.&mt('with ungraded submissions').'</option> + <option value="incorrect" '.($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option> + <option value="all" '. ($saveSub eq 'all' ? 'selected="selected"' : '').'>'.&mt('with any status').'</option> + </select> + </div> + </div> + <div class="LC_grade_select_mode_type_body"> + <div class="LC_grade_select_mode_type"> + <label> + <input type="radio" name="radioChoice" value="submission" '. + ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '. + &mt('Select individual students to grade and view submissions.').' + </label> + </div> + <div class="LC_grade_select_mode_type"> + <label> + <input type="radio" name="radioChoice" value="viewgrades" '. + ($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '. + &mt('Grade all selected students in a grading table.').' + </label> + </div> + <div class="LC_grade_select_mode_type"> + <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next->').'" /> + </div> + </div> + </div> + </div> + <div class="LC_grade_select_mode_page"> + <h2> + '.&mt('Grade Complete Folder for One Student').' + </h2> + <div class="LC_grades_select_mode_body"> + <div class="LC_grade_select_mode_type_body"> + <div class="LC_grade_select_mode_type"> + <label> + <input type="radio" name="radioChoice" value="pickStudentPage" '. + ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '. + &mt('The <b>complete</b> page/sequence/folder: For one student').' + </label> + </div> + <div class="LC_grade_select_mode_type"> + <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next->').'" /> + </div> + </div> + </div> + </div> + </div> + </form>'; return $result; } @@ -6130,53 +7548,64 @@ sub init_perm { } sub gather_clicker_ids { - my %clickerids=(); + my %clicker_ids; my $classlist = &Apache::loncoursedata::get_classlist(); # Set up a couple variables. - my $usernameidx = &Apache::loncoursedata::CL_SNAME(); - my $domainidx = &Apache::loncoursedata::CL_SDOM(); - - foreach my $student (keys %$classlist) { + my $username_idx = &Apache::loncoursedata::CL_SNAME(); + my $domain_idx = &Apache::loncoursedata::CL_SDOM(); + my $status_idx = &Apache::loncoursedata::CL_STATUS(); - my $username = $classlist->{$student}->[$usernameidx]; - my $domain = $classlist->{$student}->[$domainidx]; + 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 = - (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1]; + (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1]; foreach my $id (split(/\,/,$clickers)) { - $id=~s/^0+//; - if (exists($clickerids{$id})) { - $clickerids{$id}.=','.$username.':'.$domain; + $id=~s/^[\#0]+//; + $id=~s/[\-\:]//g; + if (exists($clicker_ids{$id})) { + $clicker_ids{$id}.=','.$username.':'.$domain; } else { - $clickerids{$id}=$username.':'.$domain; + $clicker_ids{$id}=$username.':'.$domain; } } } - return %clickerids; + return %clicker_ids; } sub gather_adv_clicker_ids { - my %clickerids=(); + my %clicker_ids; my $cnum=$env{'course.'.$env{'request.course.id'}.'.num'}; my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'}; my %coursepersonnel=&Apache::lonnet::get_course_adv_roles($cdom.'/'.$cnum); - foreach my $element (sort keys %coursepersonnel) { + foreach my $element (sort(keys(%coursepersonnel))) { foreach my $person (split(/\,/,$coursepersonnel{$element})) { my ($puname,$pudom)=split(/\:/,$person); my $clickers = - (&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1]; + (&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1]; foreach my $id (split(/\,/,$clickers)) { - $id=~s/^0+//; - if (exists($clickerids{$id})) { - $clickerids{$id}.=','.$puname.':'.$pudom; - } else { - $clickerids{$id}=$puname.':'.$pudom; - } + $id=~s/^[\#0]+//; + $id=~s/[\-\:]//g; + if (exists($clicker_ids{$id})) { + $clicker_ids{$id}.=','.$puname.':'.$pudom; + } else { + $clicker_ids{$id}=$puname.':'.$pudom; + } } } } - return %clickerids; + return %clicker_ids; +} + +sub clicker_grading_parameters { + return ('gradingmechanism' => 'scalar', + 'upfiletype' => 'scalar', + 'specificid' => 'scalar', + 'pcorrect' => 'scalar', + 'pincorrect' => 'scalar'); } sub process_clicker { @@ -6192,16 +7621,33 @@ sub process_clicker { $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 + my %Saveable_Parameters=&clicker_grading_parameters(); + &Apache::loncommon::restore_course_settings('grades_clicker', + \%Saveable_Parameters); + if (!$env{'form.pcorrect'}) { $env{'form.pcorrect'}=100; } + if (!$env{'form.pincorrect'}) { $env{'form.pincorrect'}=100; } + if (!$env{'form.gradingmechanism'}) { $env{'form.gradingmechanism'}='attendance'; } + if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; } + + my %checked; + foreach my $gradingmechanism ('attendance','personnel','specific') { + if ($env{'form.gradingmechanism'} eq $gradingmechanism) { + $checked{$gradingmechanism}="checked='checked'"; + } + } + my $upload=&mt("Upload File"); my $type=&mt("Type"); my $attendance=&mt("Award points just for participation"); my $personnel=&mt("Correctness determined from response by course personnel"); - my $specific=&mt("Correctness determined from response with clicker ID"); + my $specific=&mt("Correctness determined from response with clicker ID(s)"); my $pcorrect=&mt("Percentage points for correct solution"); my $pincorrect=&mt("Percentage points for incorrect solution"); - my $selectform=&Apache::loncommon::select_form('iclicker','upfiletype', - ('iclicker' => 'i>clicker')); - + my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype', + ('iclicker' => 'i>clicker', + 'interwrite' => 'interwrite PRS')); + $symb = &Apache::lonenc::check_encrypt($symb); $result.=<<ENDUPFORM; <script type="text/javascript"> function sanitycheck() { @@ -6249,13 +7695,13 @@ 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="checked" onClick="sanitycheck()" /></label> -<br /><label>$personnel: <input type="radio" name="gradingmechanism" value="personnel" onClick="sanitycheck()" /></label> -<br /><label>$specific: <input type="radio" name="gradingmechanism" value="specific" onClick="sanitycheck()" /></label> -<input type="text" name="specificid" size="15" /> -<input type="hidden" name="waschecked" value="attendance" /> -<br /><label>$pcorrect: <input type="text" name="pcorrect" size="4" value="100" onChange="sanitycheck()" /></label> -<br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="100" onChange="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> +<br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onChange="sanitycheck()" /></label> <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="$upload" /> </form> ENDUPFORM @@ -6269,84 +7715,324 @@ sub process_clicker_file { my ($r)=@_; my ($symb)=&get_symb($r); if (!$symb) {return '';} + + my %Saveable_Parameters=&clicker_grading_parameters(); + &Apache::loncommon::store_course_settings('grades_clicker', + \%Saveable_Parameters); + my ($result) = &showResourceInfo($symb,$env{'form.probTitle'}); if (($env{'form.gradingmechanism'} eq 'specific') && ($env{'form.specificid'}!~/\w/)) { - $result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>'; - return $result.&show_grading_menu_form($symb); + $result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>'; + return $result.&show_grading_menu_form($symb); } - my %clickerids=&gather_clicker_ids(); - my %correctids=(); + my %clicker_ids=&gather_clicker_ids(); + my %correct_ids; if ($env{'form.gradingmechanism'} eq 'personnel') { - %correctids=&gather_adv_clicker_ids(); + %correct_ids=&gather_adv_clicker_ids(); } if ($env{'form.gradingmechanism'} eq 'specific') { - my $correctid=$env{'form.specificid'}; - $correctid=~tr/a-z/A-Z/; - $correctid=~s/\s//gs; - $correctid=~s/^0+//; - $correctids{$correctid}='specified'; + foreach my $correct_id (split(/[\s\,]/,$env{'form.specificid'})) {; + $correct_id=~tr/a-z/A-Z/; + $correct_id=~s/\s//gs; + $correct_id=~s/^[\#0]+//; + $correct_id=~s/[\-\:]//g; + if ($correct_id) { + $correct_ids{$correct_id}='specified'; + } + } } if ($env{'form.gradingmechanism'} eq 'attendance') { - $result.=&mt('Score based on attendance only'); + $result.=&mt('Score based on attendance only'); } else { - my $number=0; - $result.='<h3>'.&mt('Correctness determined by the following IDs').'</h3>'; - foreach my $id (sort(keys(%correctids))) { - $result.='<tt>'.$id.'</tt> - '; - if ($correctids{$id} eq 'specified') { - $result.=&mt('specified'); - } else { - my ($uname,$udom)=split(/\:/,$correctids{$id}); - $result.=&Apache::loncommon::plainname($uname,$udom); - } - $result.='<br />'; - $number++; - } - if ($number==0) { - $result.='<span class="LC_error">'.&mt('No IDs found to determine correct answer').'</span>'; - return $result.&show_grading_menu_form($symb); - } + my $number=0; + $result.='<p><b>'.&mt('Correctness determined by the following IDs').'</b>'; + foreach my $id (sort(keys(%correct_ids))) { + $result.='<br /><tt>'.$id.'</tt> - '; + if ($correct_ids{$id} eq 'specified') { + $result.=&mt('specified'); + } else { + my ($uname,$udom)=split(/\:/,$correct_ids{$id}); + $result.=&Apache::loncommon::plainname($uname,$udom); + } + $number++; + } + $result.="</p>\n"; + if ($number==0) { + $result.='<span class="LC_error">'.&mt('No IDs found to determine correct answer').'</span>'; + return $result.&show_grading_menu_form($symb); + } } if (length($env{'form.upfile'}) < 2) { - $result.="<span class=\"LC_error\">Error:</span> The file you attempted to upload, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>, contained no information. Please check that you entered the correct filename."; + $result.=&mt('[_1] Error: [_2] The file you attempted to upload, [_3] contained no information. Please check that you entered the correct filename.', + '<span class="LC_error">', + '</span>', + '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'); return $result.&show_grading_menu_form($symb); } - my %contents=(); + +# Were able to get all the info needed, now analyze the file + + $result.=&Apache::loncommon::studentbrowser_javascript(); + $symb = &Apache::lonenc::check_encrypt($symb); + my $heading=&mt('Scanning clicker file'); + $result.=(<<ENDHEADER); +<br /><table width="100%" border="0"><tr><td bgcolor="#777777"> +<table width="100%" border="0"><tr bgcolor="#e6ffff"><td> +<b>$heading</b></td></tr><tr bgcolor=#ffffe6><td> +<form method="post" action="/adm/grades" name="clickeranalysis"> +<input type="hidden" name="symb" value="$symb" /> +<input type="hidden" name="command" value="assignclickergrades" /> +<input type="hidden" name="probTitle" value="$env{'form.probTitle'}" /> +<input type="hidden" name="saveState" value="$env{'form.saveState'}" /> +<input type="hidden" name="gradingmechanism" value="$env{'form.gradingmechanism'}" /> +<input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" /> +<input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" /> +ENDHEADER + my %responses; + my @questiontitles; my $errormsg=''; my $number=0; if ($env{'form.upfiletype'} eq 'iclicker') { - ($errormsg,$number,%contents)=&iclicker_eval(); - $result.=$errormsg; + ($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses); + } + if ($env{'form.upfiletype'} eq 'interwrite') { + ($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses); + } + $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 />'; +# Remember Question Titles +# FIXME: Possibly need delimiter other than ":" + for (my $i=0;$i<$number;$i++) { + $result.='<input type="hidden" name="question:'.$i.'" value="'. + &HTML::Entities::encode($questiontitles[$i],'"&<>').'" />'; + } + my $correct_count=0; + my $student_count=0; + my $unknown_count=0; +# Match answers with usernames +# FIXME: Possibly need delimiter other than ":" + foreach my $id (keys(%responses)) { + if ($correct_ids{$id}) { + $result.="\n".'<input type="hidden" name="correct:'.$correct_count.':'.$correct_ids{$id}.'" value="'.$responses{$id}.'" />'; + $correct_count++; + } elsif ($clicker_ids{$id}) { + 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}.'" />'. + "\n".&mt("Username").": <input type='text' name='uname".$id."' /> ". + "\n".&mt("Domain").": ". + &Apache::loncommon::select_dom_form($env{'course.'.$env{'request.course.id'}.'.domain'},'udom'.$id).' '. + &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id); + $unknown_count++; + } + } + $result.='<hr />'. + &mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count); + if ($env{'form.gradingmechanism'} ne 'attendance') { + if ($correct_count==0) { + $errormsg.="Found no correct answers answers for grading!"; + } elsif ($correct_count>1) { + $result.='<br /><span class="LC_warning">'.&mt("Found [_1] entries for grading!",$correct_count).'</span>'; + } } + if ($number<1) { + $errormsg.="Found no questions."; + } + if ($errormsg) { + $result.='<br /><span class="LC_error">'.&mt($errormsg).'</span>'; + } else { + $result.='<br /><input type="submit" name="finalize" value="'.&mt('Finalize Grading').'" />'; + } + $result.='</form></td></tr></table>'."\n". + '</td></tr></table><br /><br />'."\n"; return $result.&show_grading_menu_form($symb); } sub iclicker_eval { + my ($questiontitles,$responses)=@_; + my $number=0; + my $errormsg=''; + foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) { + my %components=&Apache::loncommon::record_sep($line); + my @entries=map {$components{$_}} (sort(keys(%components))); + if ($entries[0] eq 'Question') { + for (my $i=3;$i<$#entries;$i+=6) { + $$questiontitles[$number]=$entries[$i]; + $number++; + } + } + if ($entries[0]=~/^\#/) { + my $id=$entries[0]; + my @idresponses; + $id=~s/^[\#0]+//; + for (my $i=0;$i<$number;$i++) { + my $idx=3+$i*6; + push(@idresponses,$entries[$idx]); + } + $$responses{$id}=join(',',@idresponses); + } + } + return ($errormsg,$number); +} + +sub interwrite_eval { + my ($questiontitles,$responses)=@_; my $number=0; my $errormsg=''; + my $skipline=1; + my $questionnumber=0; + my %idresponses=(); foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) { - chomp($line); - foreach my $quoted ($line=~/\,\s*\"([^\"]*)\"\s*\,/g) { - my $replace=$quoted; - $replace=~s/\,//g; - &Apache::lonnet::logthis($quoted.' - '.$replace.'<br />'); - $line=~s/\,\s*\"\Q$quoted\E\"\s*\,/,$replace,/gs; + my %components=&Apache::loncommon::record_sep($line); + my @entries=map {$components{$_}} (sort(keys(%components))); + if ($entries[1] eq 'Time') { $skipline=0; next; } + if ($entries[1] eq 'Response') { $skipline=1; } + next if $skipline; + if ($entries[0]!=$questionnumber) { + $questionnumber=$entries[0]; + $$questiontitles[$number]=&mt('Question [_1]',$questionnumber); + $number++; + } + my $id=$entries[4]; + $id=~s/^[\#0]+//; + $id=~s/^v\d*\://i; + $id=~s/[\-\:]//g; + $idresponses{$id}[$number]=$entries[6]; + } + foreach my $id (keys %idresponses) { + $$responses{$id}=join(',',@{$idresponses{$id}}); + $$responses{$id}=~s/^\s*\,//; + } + return ($errormsg,$number); +} + +sub assign_clicker_grades { + my ($r)=@_; + my ($symb)=&get_symb($r); + if (!$symb) {return '';} +# See which part we are saving to + my ($partlist,$handgrade,$responseType) = &response_type($symb); +# FIXME: This should probably look for the first handgradeable part + my $part=$$partlist[0]; +# Start screen output + my ($result) = &showResourceInfo($symb,$env{'form.probTitle'}); + + my $heading=&mt('Assigning grades based on clicker file'); + $result.=(<<ENDHEADER); +<br /><table width="100%" border="0"><tr><td bgcolor="#777777"> +<table width="100%" border="0"><tr bgcolor="#e6ffff"><td> +<b>$heading</b></td></tr><tr bgcolor=#ffffe6><td> +ENDHEADER +# Get correct result +# FIXME: Possibly need delimiter other than ":" + my @correct=(); + my $gradingmechanism=$env{'form.gradingmechanism'}; + my $number=$env{'form.number'}; + if ($gradingmechanism ne 'attendance') { + foreach my $key (keys(%env)) { + if ($key=~/^form\.correct\:/) { + my @input=split(/\,/,$env{$key}); + for (my $i=0;$i<=$#input;$i++) { + if (($correct[$i]) && ($input[$i]) && + ($correct[$i] ne $input[$i])) { + $result.='<br /><span class="LC_warning">'. + &mt('More than one correct result given for question "[_1]": [_2] versus [_3].', + $env{'form.question:'.$i},$correct[$i],$input[$i]).'</span>'; + } elsif ($input[$i]) { + $correct[$i]=$input[$i]; + } + } + } } - my @entries=split(/\,/,$line); - if ($entries[0] eq 'Question') { - for (my $i=3;$i<$#entries;$i+=6) { - $number++; - $errormsg.='<br />'.$entries[$i]; + for (my $i=0;$i<$number;$i++) { + if (!$correct[$i]) { + $result.='<br /><span class="LC_error">'. + &mt('No correct result given for question "[_1]"!', + $env{'form.question:'.$i}).'</span>'; } } + $result.='<br />'.&mt("Correct answer: [_1]",join(', ',map { ($_?$_:'-') } @correct)); } - return ($errormsg,$number); +# Start grading + my $pcorrect=$env{'form.pcorrect'}; + my $pincorrect=$env{'form.pincorrect'}; + my $storecount=0; + foreach my $key (keys(%env)) { + my $user=''; + if ($key=~/^form\.student\:(.*)$/) { + $user=$1; + } + if ($key=~/^form\.unknown\:(.*)$/) { + 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) { + my @answer=split(/\,/,$env{$key}); + my $sum=0; + for (my $i=0;$i<$number;$i++) { + if ($answer[$i]) { + if ($gradingmechanism eq 'attendance') { + $sum+=$pcorrect; + } else { + if ($answer[$i] eq $correct[$i]) { + $sum+=$pcorrect; + } else { + $sum+=$pincorrect; + } + } + } + } + my $ave=$sum/(100*$number); +# Store + my ($username,$domain)=split(/\:/,$user); + my %grades=(); + $grades{"resource.$part.solved"}='correct_by_override'; + $grades{"resource.$part.awarded"}=$ave; + $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}"; + my $returncode=&Apache::lonnet::cstore(\%grades,$symb, + $env{'request.course.id'}, + $domain,$username); + if ($returncode ne 'ok') { + $result.="<br /><span class=\"LC_error\">Failed to save student $username:$domain. Message when trying to save was ($returncode)</span>"; + } else { + $storecount++; + } + } + } +# We are done + $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); } sub handler { my $request=$_[0]; - - &reset_perm(); + &reset_caches(); if ($env{'browser.mathml'}) { &Apache::loncommon::content_type($request,'text/xml'); } else { @@ -6358,9 +8044,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'}) { @@ -6401,7 +8090,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'}) { @@ -6414,6 +8105,8 @@ sub handler { $request->print(&process_clicker($request)); } elsif ($command eq 'processclickerfile' && $perm{'mgr'}) { $request->print(&process_clicker_file($request)); + } elsif ($command eq 'assignclickergrades' && $perm{'mgr'}) { + $request->print(&assign_clicker_grades($request)); } elsif ($command eq 'csvform' && $perm{'mgr'}) { $request->print(&upcsvScores_form($request)); } elsif ($command eq 'csvupload' && $perm{'mgr'}) { @@ -6457,6 +8150,7 @@ sub handler { } } $request->print(&Apache::loncommon::end_page()); + &reset_caches(); return ''; }