![]() ![]() | ![]() |
- include the courseid in the handback lock
1: # The LearningOnline Network with CAPA 2: # The LON-CAPA Grading handler 3: # 4: # $Id: grades.pm,v 1.372 2006/08/16 18:07:25 albertel Exp $ 5: # 6: # Copyright Michigan State University Board of Trustees 7: # 8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA). 9: # 10: # LON-CAPA is free software; you can redistribute it and/or modify 11: # it under the terms of the GNU General Public License as published by 12: # the Free Software Foundation; either version 2 of the License, or 13: # (at your option) any later version. 14: # 15: # LON-CAPA is distributed in the hope that it will be useful, 16: # but WITHOUT ANY WARRANTY; without even the implied warranty of 17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18: # GNU General Public License for more details. 19: # 20: # You should have received a copy of the GNU General Public License 21: # along with LON-CAPA; if not, write to the Free Software 22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23: # 24: # /home/httpd/html/adm/gpl.txt 25: # 26: # http://www.lon-capa.org/ 27: # 28: 29: package Apache::grades; 30: use strict; 31: use Apache::style; 32: use Apache::lonxml; 33: use Apache::lonnet; 34: use Apache::loncommon; 35: use Apache::lonhtmlcommon; 36: use Apache::lonnavmaps; 37: use Apache::lonhomework; 38: use Apache::loncoursedata; 39: use Apache::lonmsg(); 40: use Apache::Constants qw(:common); 41: use Apache::lonlocal; 42: use String::Similarity; 43: use lib '/home/httpd/lib/perl'; 44: use LONCAPA; 45: 46: use POSIX qw(floor); 47: 48: my %oldessays=(); 49: my %perm=(); 50: 51: # ----- These first few routines are general use routines.---- 52: # 53: # --- Retrieve the parts from the metadata file.--- 54: sub getpartlist { 55: my ($symb) = @_; 56: my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb); 57: my $partorder = &Apache::lonnet::metadata($url, 'partorder'); 58: my @parts; 59: if ($partorder) { 60: for my $part (split (/,/,$partorder)) { 61: if (!&Apache::loncommon::check_if_partid_hidden($part,$symb)) { 62: push(@parts, $part); 63: } 64: } 65: } else { 66: my $metadata = &Apache::lonnet::metadata($url, 'packages'); 67: foreach (split(/\,/,$metadata)) { 68: if ($_ =~ /^part_(.*)$/) { 69: if (!&Apache::loncommon::check_if_partid_hidden($1,$symb)) { 70: push(@parts, $1); 71: } 72: } 73: } 74: } 75: my @stores; 76: foreach my $part (@parts) { 77: my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys')); 78: foreach my $key (@metakeys) { 79: if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); } 80: } 81: } 82: return @stores; 83: } 84: 85: # --- Get the symbolic name of a problem and the url 86: sub get_symb { 87: my ($request,$silent) = @_; 88: (my $url=$env{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--; 89: my $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url))); 90: if ($symb eq '') { 91: if (!$silent) { 92: $request->print("Unable to handle ambiguous references:$url:."); 93: return (); 94: } 95: } 96: return ($symb); 97: } 98: 99: #--- Format fullname, username:domain if different for display 100: #--- Use anywhere where the student names are listed 101: sub nameUserString { 102: my ($type,$fullname,$uname,$udom) = @_; 103: if ($type eq 'header') { 104: return '<b> Fullname </b><font color="#999999">(Username)</font>'; 105: } else { 106: return ' '.$fullname.'<font color="#999999"> ('.$uname. 107: ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</font>'; 108: } 109: } 110: 111: #--- Get the partlist and the response type for a given problem. --- 112: #--- Indicate if a response type is coded handgraded or not. --- 113: sub response_type { 114: my ($symb) = shift; 115: my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb); 116: my $allkeys = &Apache::lonnet::metadata($url,'keys'); 117: my %vPart; 118: foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) { 119: $vPart{$partid}=1; 120: } 121: my %seen = (); 122: my (@partlist,%handgrade,%responseType); 123: foreach (split(/,/,&Apache::lonnet::metadata($url,'packages'))) { 124: if (/^\w+response_.*/ || /^Task_/) { 125: my ($responsetype,$part) = split(/_/,$_,2); 126: my ($partid,$respid) = split(/_/,$part); 127: if ($responsetype eq 'Task') { $respid='0'; } 128: if (&Apache::loncommon::check_if_partid_hidden($partid,$symb)) { 129: next; 130: } 131: if (%vPart && !exists($vPart{$partid})) { 132: next; 133: } 134: $responsetype =~ s/response$//; # make it compatible w/ navmaps - should move to that!! 135: my ($value) = &Apache::lonnet::EXT('resource.'.$part.'.handgrade',$symb); 136: $handgrade{$part} = ($value eq 'yes' ? 'yes' : 'no'); 137: if (!exists($responseType{$partid})) { $responseType{$partid}={}; } 138: $responseType{$partid}->{$respid}=$responsetype; 139: next if ($seen{$partid} > 0); 140: $seen{$partid}++; 141: push @partlist,$partid; 142: } 143: } 144: return (\@partlist,\%handgrade,\%responseType); 145: } 146: 147: sub get_display_part { 148: my ($partID,$symb)=@_; 149: my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb); 150: if (defined($display) and $display ne '') { 151: $display.= " (<font color=\"#999900\">id $partID</font>)"; 152: } else { 153: $display=$partID; 154: } 155: return $display; 156: } 157: 158: #--- Show resource title 159: #--- and parts and response type 160: sub showResourceInfo { 161: my ($symb,$probTitle,$checkboxes) = @_; 162: my $col=3; 163: if ($checkboxes) { $col=4; } 164: my $result ='<table border="0">'. 165: '<tr><td colspan="'.$col.'"><font size="+1"><b>'.&mt('Current Resource').': </b>'. 166: $probTitle.'</font></td></tr>'."\n"; 167: my ($partlist,$handgrade,$responseType) = &response_type($symb); 168: my %resptype = (); 169: my $hdgrade='no'; 170: my %partsseen; 171: for my $part_resID (sort keys(%$handgrade)) { 172: my $handgrade=$$handgrade{$part_resID}; 173: my ($partID,$resID) = split(/_/,$part_resID); 174: my $responsetype = $responseType->{$partID}->{$resID}; 175: $hdgrade = $handgrade if ($handgrade eq 'yes'); 176: $result.='<tr>'; 177: if ($checkboxes) { 178: if (exists($partsseen{$partID})) { 179: $result.="<td> </td>"; 180: } else { 181: $result.="<td><input type='checkbox' name='vPart' value='$partID' checked='on' /></td>"; 182: } 183: $partsseen{$partID}=1; 184: } 185: my $display_part=&get_display_part($partID,$symb); 186: $result.='<td><b>Part: </b>'.$display_part.' <font color="#999999">'. 187: $resID.'</font></td>'. 188: '<td><b>Type: </b>'.$responsetype.'</td></tr>'; 189: # '<td><b>Handgrade: </b>'.$handgrade.'</td></tr>'; 190: } 191: $result.='</table>'."\n"; 192: return $result,$responseType,$hdgrade,$partlist,$handgrade; 193: } 194: 195: 196: sub get_order { 197: my ($partid,$respid,$symb,$uname,$udom)=@_; 198: my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb); 199: $url=&Apache::lonnet::clutter($url); 200: my $subresult=&Apache::lonnet::ssi($url, 201: ('grade_target' => 'analyze'), 202: ('grade_domain' => $udom), 203: ('grade_symb' => $symb), 204: ('grade_courseid' => 205: $env{'request.course.id'}), 206: ('grade_username' => $uname)); 207: (undef,$subresult)=split(/_HASH_REF__/,$subresult,2); 208: my %analyze=&Apache::lonnet::str2hash($subresult); 209: return ($analyze{"$partid.$respid.shown"}); 210: } 211: #--- Clean response type for display 212: #--- Currently filters option/rank/radiobutton/match/essay/Task 213: # response types only. 214: sub cleanRecord { 215: my ($answer,$response,$symb,$partid,$respid,$record,$order,$version, 216: $uname,$udom) = @_; 217: my $grayFont = '<font color="#999999">'; 218: if ($response =~ /^(option|rank)$/) { 219: my %answer=&Apache::lonnet::str2hash($answer); 220: my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"}); 221: my ($toprow,$bottomrow); 222: foreach my $foil (@$order) { 223: if ($grading{$foil} == 1) { 224: $toprow.='<td><b>'.$answer{$foil}.' </b></td>'; 225: } else { 226: $toprow.='<td><i>'.$answer{$foil}.' </i></td>'; 227: } 228: $bottomrow.='<td>'.$grayFont.$foil.'</font> </td>'; 229: } 230: return '<blockquote><table border="1">'. 231: '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'. 232: '<tr valign="top"><td>'.$grayFont.'Option ID</font></td>'. 233: $grayFont.$bottomrow.'</tr>'.'</table></blockquote>'; 234: } elsif ($response eq 'match') { 235: my %answer=&Apache::lonnet::str2hash($answer); 236: my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"}); 237: my @items=&Apache::lonnet::str2array($record->{$version."resource.$partid.$respid.submissionitems"}); 238: my ($toprow,$middlerow,$bottomrow); 239: foreach my $foil (@$order) { 240: my $item=shift(@items); 241: if ($grading{$foil} == 1) { 242: $toprow.='<td><b>'.$item.' </b></td>'; 243: $middlerow.='<td><b>'.$grayFont.$answer{$foil}.' </font></b></td>'; 244: } else { 245: $toprow.='<td><i>'.$item.' </i></td>'; 246: $middlerow.='<td><i>'.$grayFont.$answer{$foil}.' </font></i></td>'; 247: } 248: $bottomrow.='<td>'.$grayFont.$foil.'</font> </td>'; 249: } 250: return '<blockquote><table border="1">'. 251: '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'. 252: '<tr valign="top"><td>'.$grayFont.'Item ID</font></td>'. 253: $middlerow.'</tr>'. 254: '<tr valign="top"><td>'.$grayFont.'Option ID</font></td>'. 255: $bottomrow.'</tr>'.'</table></blockquote>'; 256: } elsif ($response eq 'radiobutton') { 257: my %answer=&Apache::lonnet::str2hash($answer); 258: my ($toprow,$bottomrow); 259: my $correct=($order->[0])+1; 260: for (my $i=1;$i<=$#$order;$i++) { 261: my $foil=$order->[$i]; 262: if (exists($answer{$foil})) { 263: if ($i == $correct) { 264: $toprow.='<td><b>true</b></td>'; 265: } else { 266: $toprow.='<td><i>true</i></td>'; 267: } 268: } else { 269: $toprow.='<td>false</td>'; 270: } 271: $bottomrow.='<td>'.$grayFont.$foil.'</font> </td>'; 272: } 273: return '<blockquote><table border="1">'. 274: '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'. 275: '<tr valign="top"><td>'.$grayFont.'Option ID</font></td>'. 276: $grayFont.$bottomrow.'</tr>'.'</table></blockquote>'; 277: } elsif ($response eq 'essay') { 278: if (! exists ($env{'form.'.$symb})) { 279: my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade', 280: $env{'course.'.$env{'request.course.id'}.'.domain'}, 281: $env{'course.'.$env{'request.course.id'}.'.num'}); 282: 283: my $loginuser = $env{'user.name'}.':'.$env{'user.domain'}; 284: $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : ''; 285: $env{'form.kwclr'} = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red'; 286: $env{'form.kwsize'} = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0'; 287: $env{'form.kwstyle'} = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : ''; 288: $env{'form.'.$symb} = 1; # so that we don't have to read it from disk for multiple sub of the same prob. 289: } 290: $answer =~ s-\n-<br />-g; 291: return '<br /><br /><blockquote><tt>'.&keywords_highlight($answer).'</tt></blockquote>'; 292: } elsif ( $response eq 'organic') { 293: my $result='Smile representation: "<tt>'.$answer.'</tt>"'; 294: my $jme=$record->{$version."resource.$partid.$respid.molecule"}; 295: $result.=&Apache::chemresponse::jme_img($jme,$answer,400); 296: return $result; 297: } elsif ( $response eq 'Task') { 298: if ( $answer eq 'SUBMITTED') { 299: my $files = $record->{$version."resource.$respid.$partid.bridgetask.portfiles"}; 300: my $result = &Apache::bridgetask::file_list($files,$uname,$udom); 301: return $result; 302: } elsif ( grep(/^\Q$version\E.*?\.instance$/, keys(%{$record})) ) { 303: my @matches = grep(/^\Q$version\E.*?\.instance$/, 304: keys(%{$record})); 305: return join('<br />',($version,@matches)); 306: 307: 308: } else { 309: my $result = 310: '<p>' 311: .&mt('Overall result: [_1]', 312: $record->{$version."resource.$respid.$partid.status"}) 313: .'</p>'; 314: 315: $result .= '<ul>'; 316: my @grade = grep(/^\Q${version}resource.$respid.$partid.\E[^.]*[.]status$/, 317: keys(%{$record})); 318: foreach my $grade (sort(@grade)) { 319: my ($dim) = ($grade =~/[.]([^.]+)[.]status$/); 320: $result.= '<li>'.&mt("Dimension: [_1], status [_2] ", 321: $dim, $record->{$grade}). 322: '</li>'; 323: } 324: $result.='</ul>'; 325: return $result; 326: } 327: 328: } 329: return $answer; 330: } 331: 332: #-- A couple of common js functions 333: sub commonJSfunctions { 334: my $request = shift; 335: $request->print(<<COMMONJSFUNCTIONS); 336: <script type="text/javascript" language="javascript"> 337: function radioSelection(radioButton) { 338: var selection=null; 339: if (radioButton.length > 1) { 340: for (var i=0; i<radioButton.length; i++) { 341: if (radioButton[i].checked) { 342: return radioButton[i].value; 343: } 344: } 345: } else { 346: if (radioButton.checked) return radioButton.value; 347: } 348: return selection; 349: } 350: 351: function pullDownSelection(selectOne) { 352: var selection=""; 353: if (selectOne.length > 1) { 354: for (var i=0; i<selectOne.length; i++) { 355: if (selectOne[i].selected) { 356: return selectOne[i].value; 357: } 358: } 359: } else { 360: // only one value it must be the selected one 361: return selectOne.value; 362: } 363: } 364: </script> 365: COMMONJSFUNCTIONS 366: } 367: 368: #--- Dumps the class list with usernames,list of sections, 369: #--- section, ids and fullnames for each user. 370: sub getclasslist { 371: my ($getsec,$filterlist) = @_; 372: my @getsec; 373: if (!ref($getsec)) { 374: if ($getsec ne '' && $getsec ne 'all') { 375: @getsec=($getsec); 376: } 377: } else { 378: @getsec=@{$getsec}; 379: } 380: if (grep(/^all$/,@getsec)) { undef(@getsec); } 381: 382: my $classlist=&Apache::loncoursedata::get_classlist(); 383: # Bail out if we were unable to get the classlist 384: return if (! defined($classlist)); 385: # 386: my %sections; 387: my %fullnames; 388: foreach my $student (keys(%$classlist)) { 389: my $end = 390: $classlist->{$student}->[&Apache::loncoursedata::CL_END()]; 391: my $start = 392: $classlist->{$student}->[&Apache::loncoursedata::CL_START()]; 393: my $id = 394: $classlist->{$student}->[&Apache::loncoursedata::CL_ID()]; 395: my $section = 396: $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()]; 397: my $fullname = 398: $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()]; 399: my $status = 400: $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()]; 401: # filter students according to status selected 402: if ($filterlist && $env{'form.Status'} ne 'Any') { 403: if ($env{'form.Status'} ne $status) { 404: delete ($classlist->{$student}); 405: next; 406: } 407: } 408: $section = ($section ne '' ? $section : 'none'); 409: if (&canview($section)) { 410: if (!@getsec || grep(/^\Q$section\E$/,@getsec)) { 411: $sections{$section}++; 412: $fullnames{$student}=$fullname; 413: } else { 414: delete($classlist->{$student}); 415: } 416: } else { 417: delete($classlist->{$student}); 418: } 419: } 420: my %seen = (); 421: my @sections = sort(keys(%sections)); 422: return ($classlist,\@sections,\%fullnames); 423: } 424: 425: sub canmodify { 426: my ($sec)=@_; 427: if ($perm{'mgr'}) { 428: if (!defined($perm{'mgr_section'})) { 429: # can modify whole class 430: return 1; 431: } else { 432: if ($sec eq $perm{'mgr_section'}) { 433: #can modify the requested section 434: return 1; 435: } else { 436: # can't modify the request section 437: return 0; 438: } 439: } 440: } 441: #can't modify 442: return 0; 443: } 444: 445: sub canview { 446: my ($sec)=@_; 447: if ($perm{'vgr'}) { 448: if (!defined($perm{'vgr_section'})) { 449: # can modify whole class 450: return 1; 451: } else { 452: if ($sec eq $perm{'vgr_section'}) { 453: #can modify the requested section 454: return 1; 455: } else { 456: # can't modify the request section 457: return 0; 458: } 459: } 460: } 461: #can't modify 462: return 0; 463: } 464: 465: #--- Retrieve the grade status of a student for all the parts 466: sub student_gradeStatus { 467: my ($symb,$udom,$uname,$partlist) = @_; 468: my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname); 469: my %partstatus = (); 470: foreach (@$partlist) { 471: my ($status,undef) = split(/_/,$record{"resource.$_.solved"},2); 472: $status = 'nothing' if ($status eq ''); 473: $partstatus{$_} = $status; 474: my $subkey = "resource.$_.submitted_by"; 475: $partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne ''); 476: } 477: return %partstatus; 478: } 479: 480: # hidden form and javascript that calls the form 481: # Use by verifyscript and viewgrades 482: # Shows a student's view of problem and submission 483: sub jscriptNform { 484: my ($symb) = @_; 485: my $jscript='<script type="text/javascript" language="javascript">'."\n". 486: ' function viewOneStudent(user,domain) {'."\n". 487: ' document.onestudent.student.value = user;'."\n". 488: ' document.onestudent.userdom.value = domain;'."\n". 489: ' document.onestudent.submit();'."\n". 490: ' }'."\n". 491: '</script>'."\n"; 492: $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\n". 493: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 494: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n". 495: '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n". 496: '<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n". 497: '<input type="hidden" name="command" value="submission" />'."\n". 498: '<input type="hidden" name="student" value="" />'."\n". 499: '<input type="hidden" name="userdom" value="" />'."\n". 500: '</form>'."\n"; 501: return $jscript; 502: } 503: 504: # Given the score (as a number [0-1] and the weight) what is the final 505: # point value? This function will round to the nearest tenth, third, 506: # or quarter if one of those is within the tolerance of .00001. 507: sub compute_points { 508: my ($score, $weight) = @_; 509: 510: my $tolerance = .00001; 511: my $points = $score * $weight; 512: 513: # Check for nearness to 1/x. 514: my $check_for_nearness = sub { 515: my ($factor) = @_; 516: my $num = ($points * $factor) + $tolerance; 517: my $floored_num = floor($num); 518: if ($num - $floored_num < 2 * $tolerance * $factor) { 519: return $floored_num / $factor; 520: } 521: return $points; 522: }; 523: 524: $points = $check_for_nearness->(10); 525: $points = $check_for_nearness->(3); 526: $points = $check_for_nearness->(4); 527: 528: return $points; 529: } 530: 531: #------------------ End of general use routines -------------------- 532: 533: # 534: # Find most similar essay 535: # 536: 537: sub most_similar { 538: my ($uname,$udom,$uessay)=@_; 539: 540: # ignore spaces and punctuation 541: 542: $uessay=~s/\W+/ /gs; 543: 544: # ignore empty submissions (occuring when only files are sent) 545: 546: unless ($uessay=~/\w+/) { return ''; } 547: 548: # these will be returned. Do not care if not at least 50 percent similar 549: my $limit=0.6; 550: my $sname=''; 551: my $sdom=''; 552: my $scrsid=''; 553: my $sessay=''; 554: # go through all essays ... 555: foreach my $tkey (keys %oldessays) { 556: my ($tname,$tdom,$tcrsid)=split(/\./,$tkey); 557: # ... except the same student 558: if (($tname ne $uname) || ($tdom ne $udom)) { 559: my $tessay=$oldessays{$tkey}; 560: $tessay=~s/\W+/ /gs; 561: # String similarity gives up if not even limit 562: my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit); 563: # Found one 564: if ($tsimilar>$limit) { 565: $limit=$tsimilar; 566: $sname=$tname; 567: $sdom=$tdom; 568: $scrsid=$tcrsid; 569: $sessay=$oldessays{$tkey}; 570: } 571: } 572: } 573: if ($limit>0.6) { 574: return ($sname,$sdom,$scrsid,$sessay,$limit); 575: } else { 576: return ('','','','',0); 577: } 578: } 579: 580: #------------------------------------------------------------------- 581: 582: #------------------------------------ Receipt Verification Routines 583: # 584: #--- Check whether a receipt number is valid.--- 585: sub verifyreceipt { 586: my $request = shift; 587: 588: my $courseid = $env{'request.course.id'}; 589: my $receipt = &Apache::lonnet::recprefix($courseid).'-'. 590: $env{'form.receipt'}; 591: $receipt =~ s/[^\-\d]//g; 592: my $symb = &Apache::lonnet::symbread(); 593: 594: my $title.='<h3><font color="#339933">Verifying Submission Receipt '. 595: $receipt.'</h3></font>'."\n". 596: '<font size=+1><b>Resource: </b>'.$env{'form.probTitle'}.'</font><br /><br />'."\n"; 597: 598: my ($string,$contents,$matches) = ('','',0); 599: my (undef,undef,$fullname) = &getclasslist('all','0'); 600: 601: my $receiptparts=0; 602: if ($env{"course.$courseid.receiptalg"} eq 'receipt2') { $receiptparts=1; } 603: my $parts=['0']; 604: if ($receiptparts) { ($parts)=&response_type($symb); } 605: foreach (sort 606: { 607: if (lc($$fullname{$a}) ne lc($$fullname{$b})) { 608: return (lc($$fullname{$a}) cmp lc($$fullname{$b})); 609: } 610: return $a cmp $b; 611: } (keys(%$fullname))) { 612: my ($uname,$udom)=split(/\:/); 613: foreach my $part (@$parts) { 614: if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) { 615: $contents.='<tr bgcolor="#ffffe6"><td> '."\n". 616: '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom. 617: '\')"; TARGET=_self>'.$$fullname{$_}.'</a> </td>'."\n". 618: '<td> '.$uname.' </td>'. 619: '<td> '.$udom.' </td>'; 620: if ($receiptparts) { 621: $contents.='<td> '.$part.' </td>'; 622: } 623: $contents.='</tr>'."\n"; 624: 625: $matches++; 626: } 627: } 628: } 629: if ($matches == 0) { 630: $string = $title.'No match found for the above receipt.'; 631: } else { 632: $string = &jscriptNform($symb).$title. 633: 'The above receipt matches the following student'. 634: ($matches <= 1 ? '.' : 's.')."\n". 635: '<table border="0"><tr><td bgcolor="#777777">'."\n". 636: '<table border="0"><tr bgcolor="#e6ffff">'."\n". 637: '<td><b> Fullname </b></td>'."\n". 638: '<td><b> Username </b></td>'."\n". 639: '<td><b> Domain </b></td>'; 640: if ($receiptparts) { 641: $string.='<td> Problem Part </td>'; 642: } 643: $string.='</tr>'."\n".$contents. 644: '</table></td></tr></table>'."\n"; 645: } 646: return $string.&show_grading_menu_form($symb); 647: } 648: 649: #--- This is called by a number of programs. 650: #--- Called from the Grading Menu - View/Grade an individual student 651: #--- Also called directly when one clicks on the subm button 652: # on the problem page. 653: sub listStudents { 654: my ($request) = shift; 655: 656: my ($symb) = &get_symb($request); 657: my $cdom = $env{"course.$env{'request.course.id'}.domain"}; 658: my $cnum = $env{"course.$env{'request.course.id'}.num"}; 659: my $getsec = $env{'form.section'} eq '' ? 'all' : $env{'form.section'}; 660: my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'}; 661: 662: my $viewgrade = $env{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View'; 663: $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 664: &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'}; 665: 666: my $result='<h3><font color="#339933"> '.$viewgrade. 667: ' Submissions for a Student or a Group of Students</font></h3>'; 668: 669: my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($symb,$env{'form.probTitle'},($env{'form.showgrading'} eq 'yes')); 670: 671: $request->print(<<LISTJAVASCRIPT); 672: <script type="text/javascript" language="javascript"> 673: function checkSelect(checkBox) { 674: var ctr=0; 675: var sense=""; 676: if (checkBox.length > 1) { 677: for (var i=0; i<checkBox.length; i++) { 678: if (checkBox[i].checked) { 679: ctr++; 680: } 681: } 682: sense = "a student or group of students"; 683: } else { 684: if (checkBox.checked) { 685: ctr = 1; 686: } 687: sense = "the student"; 688: } 689: if (ctr == 0) { 690: alert("Please select "+sense+" before clicking on the Next button."); 691: return false; 692: } 693: document.gradesub.submit(); 694: } 695: 696: function reLoadList(formname) { 697: if (formname.saveStatusOld.value == pullDownSelection(formname.Status)) {return;} 698: formname.command.value = 'submission'; 699: formname.submit(); 700: } 701: </script> 702: LISTJAVASCRIPT 703: 704: &commonJSfunctions($request); 705: $request->print($result); 706: 707: my $checkhdgrade = ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1 ) ? 'checked' : ''; 708: my $checklastsub = $checkhdgrade eq '' ? 'checked' : ''; 709: my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'. 710: "\n".$table. 711: ' <b>View Problem Text: </b><label><input type="radio" name="vProb" value="no" checked="on" /> no </label>'."\n". 712: '<label><input type="radio" name="vProb" value="yes" /> one student </label>'."\n". 713: '<label><input type="radio" name="vProb" value="all" /> all students </label><br />'."\n". 714: ' <b>View Answer: </b><label><input type="radio" name="vAns" value="no" /> no </label>'."\n". 715: '<label><input type="radio" name="vAns" value="yes" /> one student </label>'."\n". 716: '<label><input type="radio" name="vAns" value="all" checked="on" /> all students </label><br />'."\n". 717: ' <b>Submissions: </b>'."\n"; 718: if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) { 719: $gradeTable.='<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> essay part only </label>'."\n"; 720: } 721: 722: my $saveStatus = $env{'form.Status'} eq '' ? 'Active' : $env{'form.Status'}; 723: $env{'form.Status'} = $saveStatus; 724: 725: $gradeTable.='<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> last submission only </label>'."\n". 726: '<label><input type="radio" name="lastSub" value="last" /> last submission & parts info </label>'."\n". 727: '<label><input type="radio" name="lastSub" value="datesub" /> by dates and submissions </label>'."\n". 728: '<label><input type="radio" name="lastSub" value="all" /> all details</label><br />'."\n". 729: ' <b>Grading Increments:</b> <select name="increment">'. 730: '<option value="1">Whole Points</option>'. 731: '<option value=".5">Half Points</option>'. 732: '<option value=".25">Quarter Points</option>'. 733: '<option value=".1">Tenths of a Point</option>'. 734: '</select>'. 735: 736: '<input type="hidden" name="section" value="'.$getsec.'" />'."\n". 737: '<input type="hidden" name="submitonly" value="'.$submitonly.'" />'."\n". 738: '<input type="hidden" name="handgrade" value="'.$env{'form.handgrade'}.'" /><br />'."\n". 739: '<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" /><br />'."\n". 740: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n". 741: '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n". 742: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 743: '<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n"; 744: 745: if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) { 746: $gradeTable.='<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n"; 747: } else { 748: $gradeTable.='<b>Student Status:</b> '. 749: &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);').'<br />'; 750: } 751: 752: $gradeTable.='To '.lc($viewgrade).' a submission or a group of submissions, click on the check box(es) '. 753: 'next to the student\'s name(s). Then click on the Next button.<br />'."\n". 754: '<input type="hidden" name="command" value="processGroup" />'."\n"; 755: 756: # checkall buttons 757: $gradeTable.=&check_script('gradesub', 'stuinfo'); 758: $gradeTable.='<input type="button" '."\n". 759: 'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n". 760: 'value="Next->" /> <br />'."\n"; 761: $gradeTable.=&check_buttons(); 762: $gradeTable.='<label><input type="checkbox" name="checkPlag" checked="on" />Check For Plagiarism</label>'; 763: my ($classlist, undef, $fullname) = &getclasslist($getsec,'1'); 764: $gradeTable.='<table border="0"><tr><td bgcolor="#777777">'. 765: '<table border="0"><tr bgcolor="#e6ffff">'; 766: my $loop = 0; 767: while ($loop < 2) { 768: $gradeTable.='<td><b> No.</b> </td><td><b> Select </b></td>'. 769: '<td>'.&nameUserString('header').' Section/Group</td>'; 770: if ($env{'form.showgrading'} eq 'yes' 771: && $submitonly ne 'queued' 772: && $submitonly ne 'all') { 773: foreach (sort(@$partlist)) { 774: my $display_part=&get_display_part((split(/_/))[0],$symb); 775: $gradeTable.='<td><b> Part: '.$display_part. 776: ' Status </b></td>'; 777: } 778: } elsif ($submitonly eq 'queued') { 779: $gradeTable.='<td><b> '.&mt('Queue Status').' </b></td>'; 780: } 781: $loop++; 782: # $gradeTable.='<td></td>' if ($loop%2 ==1); 783: } 784: $gradeTable.='</tr>'."\n"; 785: 786: my $ctr = 0; 787: foreach my $student (sort 788: { 789: if (lc($$fullname{$a}) ne lc($$fullname{$b})) { 790: return (lc($$fullname{$a}) cmp lc($$fullname{$b})); 791: } 792: return $a cmp $b; 793: } 794: (keys(%$fullname))) { 795: my ($uname,$udom) = split(/:/,$student); 796: 797: my %status = (); 798: 799: if ($submitonly eq 'queued') { 800: my %queue_status = 801: &Apache::bridgetask::get_student_status($symb,$cdom,$cnum, 802: $udom,$uname); 803: next if (!defined($queue_status{'gradingqueue'})); 804: $status{'gradingqueue'} = $queue_status{'gradingqueue'}; 805: } 806: 807: if ($env{'form.showgrading'} eq 'yes' 808: && $submitonly ne 'queued' 809: && $submitonly ne 'all') { 810: (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist); 811: my $submitted = 0; 812: my $graded = 0; 813: my $incorrect = 0; 814: foreach (keys(%status)) { 815: $submitted = 1 if ($status{$_} ne 'nothing'); 816: $graded = 1 if ($status{$_} =~ /^ungraded/); 817: $incorrect = 1 if ($status{$_} =~ /^incorrect/); 818: 819: my ($foo,$partid,$foo1) = split(/\./,$_); 820: if ($status{'resource.'.$partid.'.submitted_by'} ne '') { 821: $submitted = 0; 822: my ($part)=split(/\./,$partid); 823: $gradeTable.='<input type="hidden" name="'. 824: $student.':'.$part.':submitted_by" value="'. 825: $status{'resource.'.$partid.'.submitted_by'}.'" />'; 826: } 827: } 828: 829: next if (!$submitted && ($submitonly eq 'yes' || 830: $submitonly eq 'incorrect' || 831: $submitonly eq 'graded')); 832: next if (!$graded && ($submitonly eq 'graded')); 833: next if (!$incorrect && $submitonly eq 'incorrect'); 834: } 835: 836: $ctr++; 837: my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()]; 838: 839: if ( $perm{'vgr'} eq 'F' ) { 840: $gradeTable.='<tr bgcolor="#ffffe6">' if ($ctr%2 ==1); 841: $gradeTable.='<td align="right">'.$ctr.' </td>'. 842: '<td align="center"><label><input type=checkbox name="stuinfo" value="'. 843: $student.':'.$$fullname{$student}.':::SECTION'.$section. 844: ') " /> </label></td>'."\n".'<td>'. 845: &nameUserString(undef,$$fullname{$student},$uname,$udom). 846: ' '.$section.'</td>'."\n"; 847: 848: if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') { 849: foreach (sort keys(%status)) { 850: next if (/^resource.*?submitted_by$/); 851: $gradeTable.='<td align="center"> '.$status{$_}.' </td>'."\n"; 852: } 853: } 854: # $gradeTable.='<td></td>' if ($ctr%2 ==1); 855: $gradeTable.='</tr>'."\n" if ($ctr%2 ==0); 856: } 857: } 858: if ($ctr%2 ==1) { 859: $gradeTable.='<td> </td><td> </td><td> </td>'; 860: if ($env{'form.showgrading'} eq 'yes' 861: && $submitonly ne 'queued' 862: && $submitonly ne 'all') { 863: foreach (@$partlist) { 864: $gradeTable.='<td> </td>'; 865: } 866: } elsif ($submitonly eq 'queued') { 867: $gradeTable.='<td> </td>'; 868: } 869: $gradeTable.='</tr>'; 870: } 871: 872: $gradeTable.='</table></td></tr></table>'."\n". 873: '<input type="button" '. 874: 'onClick="javascript:checkSelect(this.form.stuinfo);" '. 875: 'value="Next->" /></form>'."\n"; 876: if ($ctr == 0) { 877: my $num_students=(scalar(keys(%$fullname))); 878: if ($num_students eq 0) { 879: $gradeTable='<br /> <font color="red">There are no students currently enrolled.</font>'; 880: } else { 881: my $submissions='submissions'; 882: if ($submitonly eq 'incorrect') { $submissions = 'incorrect submissions'; } 883: if ($submitonly eq 'graded' ) { $submissions = 'ungraded submissions'; } 884: if ($submitonly eq 'queued' ) { $submissions = 'queued submissions'; } 885: $gradeTable='<br /> <font color="red">'. 886: 'No '.$submissions.' found for this resource for any students. ('.$num_students. 887: ' students checked for '.$submissions.')</font><br />'; 888: } 889: } elsif ($ctr == 1) { 890: $gradeTable =~ s/type=checkbox/type=checkbox checked/; 891: } 892: $gradeTable.=&show_grading_menu_form($symb); 893: $request->print($gradeTable); 894: return ''; 895: } 896: 897: #---- Called from the listStudents routine 898: 899: sub check_script { 900: my ($form, $type)=@_; 901: my $chkallscript='<script type="text/javascript"> 902: function checkall() { 903: for (i=0; i<document.forms.'.$form.'.elements.length; i++) { 904: ele = document.forms.'.$form.'.elements[i]; 905: if (ele.name == "'.$type.'") { 906: document.forms.'.$form.'.elements[i].checked=true; 907: } 908: } 909: } 910: 911: function checksec() { 912: for (i=0; i<document.forms.'.$form.'.elements.length; i++) { 913: ele = document.forms.'.$form.'.elements[i]; 914: string = document.forms.'.$form.'.chksec.value; 915: if 916: (ele.value.indexOf(":::SECTION"+string)>0) { 917: document.forms.'.$form.'.elements[i].checked=true; 918: } 919: } 920: } 921: 922: 923: function uncheckall() { 924: for (i=0; i<document.forms.'.$form.'.elements.length; i++) { 925: ele = document.forms.'.$form.'.elements[i]; 926: if (ele.name == "'.$type.'") { 927: document.forms.'.$form.'.elements[i].checked=false; 928: } 929: } 930: } 931: 932: </script>'."\n"; 933: return $chkallscript; 934: } 935: 936: sub check_buttons { 937: my $buttons.='<input type="button" onclick="checkall()" value="Check All" />'; 938: $buttons.='<input type="button" onclick="uncheckall()" value="Uncheck All" /> '; 939: $buttons.='<input type="button" onclick="checksec()" value="Check Section/Group" />'; 940: $buttons.='<input type="text" size="5" name="chksec" /> '; 941: return $buttons; 942: } 943: 944: # Displays the submissions for one student or a group of students 945: sub processGroup { 946: my ($request) = shift; 947: my $ctr = 0; 948: my @stuchecked = &Apache::loncommon::get_env_multiple('form.stuinfo'); 949: my $total = scalar(@stuchecked)-1; 950: 951: foreach (@stuchecked) { 952: my ($uname,$udom,$fullname) = split(/:/); 953: $env{'form.student'} = $uname; 954: $env{'form.userdom'} = $udom; 955: $env{'form.fullname'} = $fullname; 956: &submission($request,$ctr,$total); 957: $ctr++; 958: } 959: return ''; 960: } 961: 962: #------------------------------------------------------------------------------------ 963: # 964: #-------------------------- Next few routines handles grading by student, essentially 965: # handles essay response type problem/part 966: # 967: #--- Javascript to handle the submission page functionality --- 968: sub sub_page_js { 969: my $request = shift; 970: $request->print(<<SUBJAVASCRIPT); 971: <script type="text/javascript" language="javascript"> 972: function updateRadio(formname,id,weight) { 973: var gradeBox = formname["GD_BOX"+id]; 974: var radioButton = formname["RADVAL"+id]; 975: var oldpts = formname["oldpts"+id].value; 976: var pts = checkSolved(formname,id) == 'update' ? gradeBox.value : oldpts; 977: gradeBox.value = pts; 978: var resetbox = false; 979: if (isNaN(pts) || pts < 0) { 980: alert("A number equal or greater than 0 is expected. Entered value = "+pts); 981: for (var i=0; i<radioButton.length; i++) { 982: if (radioButton[i].checked) { 983: gradeBox.value = i; 984: resetbox = true; 985: } 986: } 987: if (!resetbox) { 988: formtextbox.value = ""; 989: } 990: return; 991: } 992: 993: if (pts > weight) { 994: var resp = confirm("You entered a value ("+pts+ 995: ") greater than the weight for the part. Accept?"); 996: if (resp == false) { 997: gradeBox.value = oldpts; 998: return; 999: } 1000: } 1001: 1002: for (var i=0; i<radioButton.length; i++) { 1003: radioButton[i].checked=false; 1004: if (pts == i && pts != "") { 1005: radioButton[i].checked=true; 1006: } 1007: } 1008: updateSelect(formname,id); 1009: formname["stores"+id].value = "0"; 1010: } 1011: 1012: function writeBox(formname,id,pts) { 1013: var gradeBox = formname["GD_BOX"+id]; 1014: if (checkSolved(formname,id) == 'update') { 1015: gradeBox.value = pts; 1016: } else { 1017: var oldpts = formname["oldpts"+id].value; 1018: gradeBox.value = oldpts; 1019: var radioButton = formname["RADVAL"+id]; 1020: for (var i=0; i<radioButton.length; i++) { 1021: radioButton[i].checked=false; 1022: if (i == oldpts) { 1023: radioButton[i].checked=true; 1024: } 1025: } 1026: } 1027: formname["stores"+id].value = "0"; 1028: updateSelect(formname,id); 1029: return; 1030: } 1031: 1032: function clearRadBox(formname,id) { 1033: if (checkSolved(formname,id) == 'noupdate') { 1034: updateSelect(formname,id); 1035: return; 1036: } 1037: gradeSelect = formname["GD_SEL"+id]; 1038: for (var i=0; i<gradeSelect.length; i++) { 1039: if (gradeSelect[i].selected) { 1040: var selectx=i; 1041: } 1042: } 1043: var stores = formname["stores"+id]; 1044: if (selectx == stores.value) { return }; 1045: var gradeBox = formname["GD_BOX"+id]; 1046: gradeBox.value = ""; 1047: var radioButton = formname["RADVAL"+id]; 1048: for (var i=0; i<radioButton.length; i++) { 1049: radioButton[i].checked=false; 1050: } 1051: stores.value = selectx; 1052: } 1053: 1054: function checkSolved(formname,id) { 1055: if (formname["solved"+id].value == "correct_by_student" && formname.overRideScore.value == 'no') { 1056: var reply = confirm("This problem has been graded correct by the computer. Do you want to change the score?"); 1057: if (!reply) {return "noupdate";} 1058: formname.overRideScore.value = 'yes'; 1059: } 1060: return "update"; 1061: } 1062: 1063: function updateSelect(formname,id) { 1064: formname["GD_SEL"+id][0].selected = true; 1065: return; 1066: } 1067: 1068: //=========== Check that a point is assigned for all the parts ============ 1069: function checksubmit(formname,val,total,parttot) { 1070: formname.gradeOpt.value = val; 1071: if (val == "Save & Next") { 1072: for (i=0;i<=total;i++) { 1073: for (j=0;j<parttot;j++) { 1074: var partid = formname["partid"+i+"_"+j].value; 1075: if (formname["GD_SEL"+i+"_"+partid][0].selected) { 1076: var points = formname["GD_BOX"+i+"_"+partid].value; 1077: if (points == "") { 1078: var name = formname["name"+i].value; 1079: var studentID = (name != '' ? name : formname["unamedom"+i].value); 1080: var resp = confirm("You did not assign a score for "+studentID+ 1081: ", part "+partid+". Continue?"); 1082: if (resp == false) { 1083: formname["GD_BOX"+i+"_"+partid].focus(); 1084: return false; 1085: } 1086: } 1087: } 1088: 1089: } 1090: } 1091: 1092: } 1093: if (val == "Grade Student") { 1094: formname.showgrading.value = "yes"; 1095: if (formname.Status.value == "") { 1096: formname.Status.value = "Active"; 1097: } 1098: formname.studentNo.value = total; 1099: } 1100: formname.submit(); 1101: } 1102: 1103: //======= Check that a score is assigned for all the problems (page/sequence grading only) ========= 1104: function checkSubmitPage(formname,total) { 1105: noscore = new Array(100); 1106: var ptr = 0; 1107: for (i=1;i<total;i++) { 1108: var partid = formname["q_"+i].value; 1109: if (formname["GD_SEL"+i+"_"+partid][0].selected) { 1110: var points = formname["GD_BOX"+i+"_"+partid].value; 1111: var status = formname["solved"+i+"_"+partid].value; 1112: if (points == "" && status != "correct_by_student") { 1113: noscore[ptr] = i; 1114: ptr++; 1115: } 1116: } 1117: } 1118: if (ptr != 0) { 1119: var sense = ptr == 1 ? ": " : "s: "; 1120: var prolist = ""; 1121: if (ptr == 1) { 1122: prolist = noscore[0]; 1123: } else { 1124: var i = 0; 1125: while (i < ptr-1) { 1126: prolist += noscore[i]+", "; 1127: i++; 1128: } 1129: prolist += "and "+noscore[i]; 1130: } 1131: var resp = confirm("You did not assign any score for the following problem"+sense+prolist+". Continue?"); 1132: if (resp == false) { 1133: return false; 1134: } 1135: } 1136: 1137: formname.submit(); 1138: } 1139: </script> 1140: SUBJAVASCRIPT 1141: } 1142: 1143: #--- javascript for essay type problem -- 1144: sub sub_page_kw_js { 1145: my $request = shift; 1146: my $iconpath = $request->dir_config('lonIconsURL'); 1147: &commonJSfunctions($request); 1148: 1149: my $inner_js_msg_central=<<INNERJS; 1150: <script text="text/javascript"> 1151: function checkInput() { 1152: opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value); 1153: var nmsg = opener.document.SCORE.savemsgN.value; 1154: var usrctr = document.msgcenter.usrctr.value; 1155: var newval = opener.document.SCORE["newmsg"+usrctr]; 1156: newval.value = opener.checkEntities(document.msgcenter.newmsg.value); 1157: 1158: var msgchk = ""; 1159: if (document.msgcenter.subchk.checked) { 1160: msgchk = "msgsub,"; 1161: } 1162: var includemsg = 0; 1163: for (var i=1; i<=nmsg; i++) { 1164: var opnmsg = opener.document.SCORE["savemsg"+i]; 1165: var frmmsg = document.msgcenter["msg"+i]; 1166: opnmsg.value = opener.checkEntities(frmmsg.value); 1167: var showflg = opener.document.SCORE["shownOnce"+i]; 1168: showflg.value = "1"; 1169: var chkbox = document.msgcenter["msgn"+i]; 1170: if (chkbox.checked) { 1171: msgchk += "savemsg"+i+","; 1172: includemsg = 1; 1173: } 1174: } 1175: if (document.msgcenter.newmsgchk.checked) { 1176: msgchk += "newmsg"+usrctr; 1177: includemsg = 1; 1178: } 1179: imgformname = opener.document.SCORE["mailicon"+usrctr]; 1180: imgformname.src = "$iconpath/"+((includemsg) ? "mailto.gif" : "mailbkgrd.gif"); 1181: var includemsg = opener.document.SCORE["includemsg"+usrctr]; 1182: includemsg.value = msgchk; 1183: 1184: self.close() 1185: 1186: } 1187: </script> 1188: INNERJS 1189: 1190: my $inner_js_highlight_central=<<INNERJS; 1191: <script type="text/javascript"> 1192: function updateChoice(flag) { 1193: opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr); 1194: opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize); 1195: opener.document.SCORE.kwstyle.value = opener.radioSelection(document.hlCenter.kwdstyle); 1196: opener.document.SCORE.refresh.value = "on"; 1197: if (opener.document.SCORE.keywords.value!=""){ 1198: opener.document.SCORE.submit(); 1199: } 1200: self.close() 1201: } 1202: </script> 1203: INNERJS 1204: 1205: my $start_page_msg_central = 1206: &Apache::loncommon::start_page('Message Central',$inner_js_msg_central, 1207: {'js_ready' => 1, 1208: 'only_body' => 1, 1209: 'bgcolor' =>'#FFFFFF',}); 1210: my $end_page_msg_central = 1211: &Apache::loncommon::end_page({'js_ready' => 1}); 1212: 1213: 1214: my $start_page_highlight_central = 1215: &Apache::loncommon::start_page('Highlight Central', 1216: $inner_js_highlight_central, 1217: {'js_ready' => 1, 1218: 'only_body' => 1, 1219: 'bgcolor' =>'#FFFFFF',}); 1220: my $end_page_highlight_central = 1221: &Apache::loncommon::end_page({'js_ready' => 1}); 1222: 1223: my $docopen=&Apache::lonhtmlcommon::javascript_docopen(); 1224: $docopen=~s/^document\.//; 1225: $request->print(<<SUBJAVASCRIPT); 1226: <script type="text/javascript" language="javascript"> 1227: 1228: //===================== Show list of keywords ==================== 1229: function keywords(formname) { 1230: var nret = prompt("Keywords list, separated by a space. Add/delete to list if desired.",formname.keywords.value); 1231: if (nret==null) return; 1232: formname.keywords.value = nret; 1233: 1234: if (formname.keywords.value != "") { 1235: formname.refresh.value = "on"; 1236: formname.submit(); 1237: } 1238: return; 1239: } 1240: 1241: //===================== Script to view submitted by ================== 1242: function viewSubmitter(submitter) { 1243: document.SCORE.refresh.value = "on"; 1244: document.SCORE.NCT.value = "1"; 1245: document.SCORE.unamedom0.value = submitter; 1246: document.SCORE.submit(); 1247: return; 1248: } 1249: 1250: //===================== Script to add keyword(s) ================== 1251: function getSel() { 1252: if (document.getSelection) txt = document.getSelection(); 1253: else if (document.selection) txt = document.selection.createRange().text; 1254: else return; 1255: var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," "); 1256: if (cleantxt=="") { 1257: alert("Please select a word or group of words from document and then click this link."); 1258: return; 1259: } 1260: var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt); 1261: if (nret==null) return; 1262: document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret; 1263: if (document.SCORE.keywords.value != "") { 1264: document.SCORE.refresh.value = "on"; 1265: document.SCORE.submit(); 1266: } 1267: return; 1268: } 1269: 1270: //====================== Script for composing message ============== 1271: // preload images 1272: img1 = new Image(); 1273: img1.src = "$iconpath/mailbkgrd.gif"; 1274: img2 = new Image(); 1275: img2.src = "$iconpath/mailto.gif"; 1276: 1277: function msgCenter(msgform,usrctr,fullname) { 1278: var Nmsg = msgform.savemsgN.value; 1279: savedMsgHeader(Nmsg,usrctr,fullname); 1280: var subject = msgform.msgsub.value; 1281: var msgchk = document.SCORE["includemsg"+usrctr].value; 1282: re = /msgsub/; 1283: var shwsel = ""; 1284: if (re.test(msgchk)) { shwsel = "checked" } 1285: subject = (document.SCORE.shownSub.value == 0 ? checkEntities(subject) : subject); 1286: displaySubject(checkEntities(subject),shwsel); 1287: for (var i=1; i<=Nmsg; i++) { 1288: var testmsg = "savemsg"+i+","; 1289: re = new RegExp(testmsg,"g"); 1290: shwsel = ""; 1291: if (re.test(msgchk)) { shwsel = "checked" } 1292: var message = document.SCORE["savemsg"+i].value; 1293: message = (document.SCORE["shownOnce"+i].value == 0 ? checkEntities(message) : message); 1294: displaySavedMsg(i,message,shwsel); //I do not get it. w/o checkEntities on saved messages, 1295: //any < is already converted to <, etc. However, only once!! 1296: } 1297: newmsg = document.SCORE["newmsg"+usrctr].value; 1298: shwsel = ""; 1299: re = /newmsg/; 1300: if (re.test(msgchk)) { shwsel = "checked" } 1301: newMsg(newmsg,shwsel); 1302: msgTail(); 1303: return; 1304: } 1305: 1306: function checkEntities(strx) { 1307: if (strx.length == 0) return strx; 1308: var orgStr = ["&", "<", ">", '"']; 1309: var newStr = ["&", "<", ">", """]; 1310: var counter = 0; 1311: while (counter < 4) { 1312: strx = strReplace(strx,orgStr[counter],newStr[counter]); 1313: counter++; 1314: } 1315: return strx; 1316: } 1317: 1318: function strReplace(strx, orgStr, newStr) { 1319: return strx.split(orgStr).join(newStr); 1320: } 1321: 1322: function savedMsgHeader(Nmsg,usrctr,fullname) { 1323: var height = 70*Nmsg+250; 1324: var scrollbar = "no"; 1325: if (height > 600) { 1326: height = 600; 1327: scrollbar = "yes"; 1328: } 1329: var xpos = (screen.width-600)/2; 1330: xpos = (xpos < 0) ? '0' : xpos; 1331: var ypos = (screen.height-height)/2-30; 1332: ypos = (ypos < 0) ? '0' : ypos; 1333: 1334: pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars='+scrollbar+',screenx='+xpos+',screeny='+ypos+',width=600,height='+height); 1335: pWin.focus(); 1336: pDoc = pWin.document; 1337: pDoc.$docopen; 1338: pDoc.write('$start_page_msg_central'); 1339: 1340: pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">"); 1341: pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">"); 1342: pDoc.write("<font color=\\"green\\" size=+1> Compose Message for \"+fullname+\"</font><br /><br />"); 1343: 1344: pDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">"); 1345: pDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">"); 1346: pDoc.write("<td><b>Type</b></td><td><b>Include</b></td><td><b>Message</td></tr>"); 1347: } 1348: function displaySubject(msg,shwsel) { 1349: pDoc = pWin.document; 1350: pDoc.write("<tr bgcolor=\\"#ffffdd\\">"); 1351: pDoc.write("<td>Subject</td>"); 1352: pDoc.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"></td>"); 1353: pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"60\\" maxlength=\\"80\\"></td></tr>"); 1354: } 1355: 1356: function displaySavedMsg(ctr,msg,shwsel) { 1357: pDoc = pWin.document; 1358: pDoc.write("<tr bgcolor=\\"#ffffdd\\">"); 1359: pDoc.write("<td align=\\"center\\">"+ctr+"</td>"); 1360: pDoc.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"></td>"); 1361: pDoc.write("<td><textarea name=\\"msg"+ctr+"\\" cols=\\"60\\" rows=\\"3\\">"+msg+"</textarea></td></tr>"); 1362: } 1363: 1364: function newMsg(newmsg,shwsel) { 1365: pDoc = pWin.document; 1366: pDoc.write("<tr bgcolor=\\"#ffffdd\\">"); 1367: pDoc.write("<td align=\\"center\\">New</td>"); 1368: pDoc.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"></td>"); 1369: pDoc.write("<td><textarea name=\\"newmsg\\" cols=\\"60\\" rows=\\"3\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" >"+newmsg+"</textarea></td></tr>"); 1370: } 1371: 1372: function msgTail() { 1373: pDoc = pWin.document; 1374: pDoc.write("</table>"); 1375: pDoc.write("</td></tr></table> "); 1376: pDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\"> "); 1377: pDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />"); 1378: pDoc.write("</form>"); 1379: pDoc.write('$end_page_msg_central'); 1380: pDoc.close(); 1381: } 1382: 1383: //====================== Script for keyword highlight options ============== 1384: function kwhighlight() { 1385: var kwclr = document.SCORE.kwclr.value; 1386: var kwsize = document.SCORE.kwsize.value; 1387: var kwstyle = document.SCORE.kwstyle.value; 1388: var redsel = ""; 1389: var grnsel = ""; 1390: var blusel = ""; 1391: if (kwclr=="red") {var redsel="checked"}; 1392: if (kwclr=="green") {var grnsel="checked"}; 1393: if (kwclr=="blue") {var blusel="checked"}; 1394: var sznsel = ""; 1395: var sz1sel = ""; 1396: var sz2sel = ""; 1397: if (kwsize=="0") {var sznsel="checked"}; 1398: if (kwsize=="+1") {var sz1sel="checked"}; 1399: if (kwsize=="+2") {var sz2sel="checked"}; 1400: var synsel = ""; 1401: var syisel = ""; 1402: var sybsel = ""; 1403: if (kwstyle=="") {var synsel="checked"}; 1404: if (kwstyle=="<i>") {var syisel="checked"}; 1405: if (kwstyle=="<b>") {var sybsel="checked"}; 1406: highlightCentral(); 1407: highlightbody('red','red',redsel,'0','normal',sznsel,'','normal',synsel); 1408: highlightbody('green','green',grnsel,'+1','+1',sz1sel,'<i>','italic',syisel); 1409: highlightbody('blue','blue',blusel,'+2','+2',sz2sel,'<b>','bold',sybsel); 1410: highlightend(); 1411: return; 1412: } 1413: 1414: function highlightCentral() { 1415: // if (window.hwdWin) window.hwdWin.close(); 1416: var xpos = (screen.width-400)/2; 1417: xpos = (xpos < 0) ? '0' : xpos; 1418: var ypos = (screen.height-330)/2-30; 1419: ypos = (ypos < 0) ? '0' : ypos; 1420: 1421: hwdWin = window.open('', 'KeywordHighlightCentral', 'resizeable=yes,toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx='+xpos+',screeny='+ypos); 1422: hwdWin.focus(); 1423: var hDoc = hwdWin.document; 1424: hDoc.$docopen; 1425: hDoc.write('$start_page_highlight_central'); 1426: hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">"); 1427: hDoc.write("<font color=\\"green\\" size=+1> Keyword Highlight Options</font><br /><br />"); 1428: 1429: hDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">"); 1430: hDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">"); 1431: hDoc.write("<td><b>Text Color</b></td><td><b>Font Size</b></td><td><b>Font Style</td></tr>"); 1432: } 1433: 1434: function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) { 1435: var hDoc = hwdWin.document; 1436: hDoc.write("<tr bgcolor=\\"#ffffdd\\">"); 1437: hDoc.write("<td align=\\"left\\">"); 1438: hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+"> "+clrtxt+"</td>"); 1439: hDoc.write("<td align=\\"left\\">"); 1440: hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+"> "+sztxt+"</td>"); 1441: hDoc.write("<td align=\\"left\\">"); 1442: hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+"> "+sytxt+"</td>"); 1443: hDoc.write("</tr>"); 1444: } 1445: 1446: function highlightend() { 1447: var hDoc = hwdWin.document; 1448: hDoc.write("</table>"); 1449: hDoc.write("</td></tr></table> "); 1450: hDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:updateChoice(1)\\"> "); 1451: hDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />"); 1452: hDoc.write("</form>"); 1453: hDoc.write('$end_page_highlight_central'); 1454: hDoc.close(); 1455: } 1456: 1457: </script> 1458: SUBJAVASCRIPT 1459: } 1460: 1461: sub get_increment { 1462: my $increment = $env{'form.increment'}; 1463: if ($increment != 1 && $increment != .5 && $increment != .25 && 1464: $increment != .1) { 1465: $increment = 1; 1466: } 1467: return $increment; 1468: } 1469: 1470: #--- displays the grading box, used in essay type problem and grading by page/sequence 1471: sub gradeBox { 1472: my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_; 1473: my $checkIcon = '<img src="'.$request->dir_config('lonIconsURL'). 1474: '/check.gif" height="16" border="0" />'; 1475: my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname); 1476: my $wgtmsg = ($wgt > 0 ? '(problem weight)' : 1477: '<font color="red">problem weight assigned by computer</font>'); 1478: $wgt = ($wgt > 0 ? $wgt : '1'); 1479: my $score = ($$record{'resource.'.$partid.'.awarded'} eq '' ? 1480: '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt)); 1481: my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n"; 1482: my $display_part=&get_display_part($partid,$symb); 1483: my %last_resets = &get_last_resets($symb,$env{'request.course.id'}, 1484: [$partid]); 1485: my $aggtries = $$record{'resource.'.$partid.'.tries'}; 1486: if ($last_resets{$partid}) { 1487: $aggtries = &get_num_tries($record,$last_resets{$partid},$partid); 1488: } 1489: $result.='<table border="0"><tr><td>'. 1490: '<b>Part: </b>'.$display_part.' <b>Points: </b></td><td>'."\n"; 1491: my $ctr = 0; 1492: my $thisweight = 0; 1493: my $increment = &get_increment(); 1494: $result.='<table border="0"><tr>'."\n"; # display radio buttons in a nice table 10 across 1495: while ($thisweight<=$wgt) { 1496: $result.= '<td><nobr><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '. 1497: 'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','. 1498: $thisweight.')" value="'.$thisweight.'" '. 1499: ($score eq $thisweight ? 'checked':'').' /> '.$thisweight."</label></nobr></td>\n"; 1500: $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : ''); 1501: $thisweight += $increment; 1502: $ctr++; 1503: } 1504: $result.='</tr></table>'; 1505: $result.='</td><td> <b>or</b> </td>'."\n"; 1506: $result.='<td><input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'. 1507: ($score ne ''? ' value = "'.$score.'"':'').' size="4" '. 1508: 'onChange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','. 1509: $wgt.')" /></td>'."\n"; 1510: $result.='<td>/'.$wgt.' '.$wgtmsg. 1511: ($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? ' '.$checkIcon : ''). 1512: ' </td><td>'."\n"; 1513: $result.='<select name="GD_SEL'.$counter.'_'.$partid.'" '. 1514: 'onChange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n"; 1515: if ($$record{'resource.'.$partid.'.solved'} eq 'excused') { 1516: $result.='<option> </option>'. 1517: '<option selected="on">excused</option>'; 1518: } else { 1519: $result.='<option selected="on"> </option>'. 1520: '<option>excused</option>'; 1521: } 1522: $result.='<option>reset status</option></select>'."\n"; 1523: $result.="  \n"; 1524: $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n". 1525: '<input type="hidden" name="oldpts'.$counter.'_'.$partid.'" value="'.$score.'" />'."\n". 1526: '<input type="hidden" name="solved'.$counter.'_'.$partid.'" value="'. 1527: $$record{'resource.'.$partid.'.solved'}.'" />'."\n". 1528: '<input type="hidden" name="totaltries'.$counter.'_'.$partid.'" value="'. 1529: $$record{'resource.'.$partid.'.tries'}.'" />'."\n". 1530: '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'. 1531: $aggtries.'" />'."\n"; 1532: $result.='</td></tr></table>'."\n"; 1533: $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record); 1534: return $result; 1535: } 1536: 1537: sub handback_box { 1538: my ($symb,$uname,$udom,$counter,$partid,$record) = @_; 1539: my ($partlist,$handgrade,$responseType) = &response_type($symb); 1540: my (@respids); 1541: foreach my $part_resp (sort(keys(%$handgrade))) { 1542: my ($part,$resp) = split(/_/,$part_resp); 1543: if ($part eq $partid) { 1544: push @respids,$resp; 1545: } 1546: } 1547: my $result; 1548: foreach my $respid (@respids) { 1549: my $prefix = $counter.'_'.$partid.'_'.$respid.'_'; 1550: my $files=&get_submitted_files($udom,$uname,$partid,$respid,$record); 1551: next if (!@$files); 1552: my $file_counter = 1; 1553: foreach my $file (@$files) { 1554: if ($file =~ /\/portfolio\//) { 1555: my ($file_path, $file_disp) = ($file =~ m|(.+/)(.+)$|); 1556: my ($name,$version,$ext) = &file_name_version_ext($file_disp); 1557: $file_disp = "$name.$ext"; 1558: $file = $file_path.$file_disp; 1559: $result.=&mt('Return commented version of [_1] to student.', 1560: '<span class="LC_filename">'.$file_disp.'</span>'); 1561: $result.='<input type="file" name="'.$prefix.'returndoc'.$file_counter.'" />'."\n"; 1562: $result.='<input type="hidden" name="'.$prefix.'origdoc'.$file_counter.'" value="'.$file.'" /><br />'; 1563: $result.='(File will be uploaded when you click on Save & Next below.)<br />'; 1564: $file_counter++; 1565: } 1566: } 1567: } 1568: return $result; 1569: } 1570: 1571: sub show_problem { 1572: my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode) = @_; 1573: my $rendered; 1574: &Apache::lonxml::remember_problem_counter(); 1575: if ($mode eq 'both' or $mode eq 'text') { 1576: $rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom, 1577: $env{'request.course.id'}); 1578: } 1579: if ($removeform) { 1580: $rendered=~s|<form(.*?)>||g; 1581: $rendered=~s|</form>||g; 1582: $rendered=~s|name="submit"|name="would_have_been_submit"|g; 1583: } 1584: my $companswer; 1585: if ($mode eq 'both' or $mode eq 'answer') { 1586: &Apache::lonxml::restore_problem_counter(); 1587: $companswer=&Apache::loncommon::get_student_answers($symb,$uname,$udom, 1588: $env{'request.course.id'}); 1589: } 1590: if ($removeform) { 1591: $companswer=~s|<form(.*?)>||g; 1592: $companswer=~s|</form>||g; 1593: $companswer=~s|name="submit"|name="would_have_been_submit"|g; 1594: } 1595: my $result.='<table border="0" width="100%"><tr><td bgcolor="#777777">'; 1596: $result.='<table border="0" width="100%">'; 1597: if ($viewon) { 1598: $result.='<tr><td bgcolor="#e6ffff"><b> '; 1599: if ($mode eq 'both' or $mode eq 'text') { 1600: $result.='View of the problem - '; 1601: } else { 1602: $result.='Correct answer: '; 1603: } 1604: $result.=$env{'form.fullname'}.'</b></td></tr>'; 1605: } 1606: if ($mode eq 'both') { 1607: $result.='<tr><td bgcolor="#ffffff">'.$rendered.'<br />'; 1608: $result.='<b>Correct answer:</b><br />'.$companswer; 1609: } elsif ($mode eq 'text') { 1610: $result.='<tr><td bgcolor="#ffffff">'.$rendered; 1611: } elsif ($mode eq 'answer') { 1612: $result.='<tr><td bgcolor="#ffffff">'.$companswer; 1613: } 1614: $result.='</td></tr></table>'; 1615: $result.='</td></tr></table><br />'; 1616: return $result; 1617: } 1618: 1619: # --------------------------- show submissions of a student, option to grade 1620: sub submission { 1621: my ($request,$counter,$total) = @_; 1622: 1623: my ($uname,$udom) = ($env{'form.student'},$env{'form.userdom'}); 1624: $udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student? 1625: my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'}); 1626: $env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq ''; 1627: 1628: my $symb = &get_symb($request); 1629: if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; } 1630: 1631: if (!&canview($usec)) { 1632: $request->print('<font color="red">Unable to view requested student.('. 1633: $uname.'@'.$udom.' in section '.$usec.' in course id '. 1634: $env{'request.course.id'}.')</font>'); 1635: $request->print(&show_grading_menu_form($symb)); 1636: return; 1637: } 1638: 1639: if (!$env{'form.lastSub'}) { $env{'form.lastSub'} = 'datesub'; } 1640: if (!$env{'form.vProb'}) { $env{'form.vProb'} = 'yes'; } 1641: if (!$env{'form.vAns'}) { $env{'form.vAns'} = 'yes'; } 1642: my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : ''); 1643: my $checkIcon = '<img src="'.$request->dir_config('lonIconsURL'). 1644: '/check.gif" height="16" border="0" />'; 1645: 1646: # header info 1647: if ($counter == 0) { 1648: &sub_page_js($request); 1649: &sub_page_kw_js($request) if ($env{'form.handgrade'} eq 'yes'); 1650: $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 1651: &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'}; 1652: 1653: $request->print('<h3> <font color="#339933">Submission Record</font></h3>'."\n". 1654: '<font size=+1> <b>Resource: </b>'.$env{'form.probTitle'}.'</font>'."\n"); 1655: 1656: if ($env{'form.handgrade'} eq 'no') { 1657: my $checkMark='<br /><br /> <b>Note:</b> Part(s) graded correct by the computer is marked with a '. 1658: $checkIcon.' symbol.'."\n"; 1659: $request->print($checkMark); 1660: } 1661: 1662: # option to display problem, only once else it cause problems 1663: # with the form later since the problem has a form. 1664: if ($env{'form.vProb'} eq 'yes' or $env{'form.vAns'} eq 'yes') { 1665: my $mode; 1666: if ($env{'form.vProb'} eq 'yes' && $env{'form.vAns'} eq 'yes') { 1667: $mode='both'; 1668: } elsif ($env{'form.vProb'} eq 'yes') { 1669: $mode='text'; 1670: } elsif ($env{'form.vAns'} eq 'yes') { 1671: $mode='answer'; 1672: } 1673: &Apache::lonxml::clear_problem_counter(); 1674: $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode)); 1675: } 1676: 1677: # kwclr is the only variable that is guaranteed to be non blank 1678: # if this subroutine has been called once. 1679: my %keyhash = (); 1680: if ($env{'form.kwclr'} eq '' && $env{'form.handgrade'} eq 'yes') { 1681: %keyhash = &Apache::lonnet::dump('nohist_handgrade', 1682: $env{'course.'.$env{'request.course.id'}.'.domain'}, 1683: $env{'course.'.$env{'request.course.id'}.'.num'}); 1684: 1685: my $loginuser = $env{'user.name'}.':'.$env{'user.domain'}; 1686: $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : ''; 1687: $env{'form.kwclr'} = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red'; 1688: $env{'form.kwsize'} = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0'; 1689: $env{'form.kwstyle'} = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : ''; 1690: $env{'form.msgsub'} = $keyhash{$symb.'_subject'} ne '' ? 1691: $keyhash{$symb.'_subject'} : $env{'form.probTitle'}; 1692: $env{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0'; 1693: } 1694: my $overRideScore = $env{'form.overRideScore'} eq '' ? 'no' : $env{'form.overRideScore'}; 1695: 1696: $request->print('<form action="/adm/grades" method="post" name="SCORE" enctype="multipart/form-data">'."\n". 1697: '<input type="hidden" name="command" value="handgrade" />'."\n". 1698: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n". 1699: '<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n". 1700: '<input type="hidden" name="overRideScore" value="'.$overRideScore.'" />'."\n". 1701: '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n". 1702: '<input type="hidden" name="refresh" value="off" />'."\n". 1703: '<input type="hidden" name="studentNo" value="" />'."\n". 1704: '<input type="hidden" name="gradeOpt" value="" />'."\n". 1705: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 1706: '<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" />'."\n". 1707: '<input type="hidden" name="vProb" value="'.$env{'form.vProb'}.'" />'."\n". 1708: '<input type="hidden" name="vAns" value="'.$env{'form.vAns'}.'" />'."\n". 1709: '<input type="hidden" name="lastSub" value="'.$env{'form.lastSub'}.'" />'."\n". 1710: '<input type="hidden" name="section" value="'.$env{'form.section'}.'" />'."\n". 1711: '<input type="hidden" name="submitonly" value="'.$env{'form.submitonly'}.'" />'."\n". 1712: '<input type="hidden" name="handgrade" value="'.$env{'form.handgrade'}.'" />'."\n". 1713: '<input type="hidden" name="NCT"'. 1714: ' value="'.($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : $total+1).'" />'."\n"); 1715: if ($env{'form.handgrade'} eq 'yes') { 1716: $request->print('<input type="hidden" name="keywords" value="'.$env{'form.keywords'}.'" />'."\n". 1717: '<input type="hidden" name="kwclr" value="'.$env{'form.kwclr'}.'" />'."\n". 1718: '<input type="hidden" name="kwsize" value="'.$env{'form.kwsize'}.'" />'."\n". 1719: '<input type="hidden" name="kwstyle" value="'.$env{'form.kwstyle'}.'" />'."\n". 1720: '<input type="hidden" name="msgsub" value="'.$env{'form.msgsub'}.'" />'."\n". 1721: '<input type="hidden" name="shownSub" value="0" />'."\n". 1722: '<input type="hidden" name="savemsgN" value="'.$env{'form.savemsgN'}.'" />'."\n"); 1723: foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) { 1724: $request->print('<input type="hidden" name="vPart" value="'.$partid.'" />'."\n"); 1725: } 1726: } 1727: 1728: my ($cts,$prnmsg) = (1,''); 1729: while ($cts <= $env{'form.savemsgN'}) { 1730: $prnmsg.='<input type="hidden" name="savemsg'.$cts.'" value="'. 1731: (!exists($keyhash{$symb.'_savemsg'.$cts}) ? 1732: &Apache::lonfeedback::clear_out_html($env{'form.savemsg'.$cts}) : 1733: &Apache::lonfeedback::clear_out_html($keyhash{$symb.'_savemsg'.$cts})). 1734: '" />'."\n". 1735: '<input type="hidden" name="shownOnce'.$cts.'" value="0" />'."\n"; 1736: $cts++; 1737: } 1738: $request->print($prnmsg); 1739: 1740: if ($env{'form.handgrade'} eq 'yes' && $env{'form.showgrading'} eq 'yes') { 1741: # 1742: # Print out the keyword options line 1743: # 1744: $request->print(<<KEYWORDS); 1745: <b>Keyword Options:</b> 1746: <a href="javascript:keywords(document.SCORE)"; TARGET=_self>List</a> 1747: <a href="#" onMouseDown="javascript:getSel(); return false" 1748: CLASS="page">Paste Selection to List</a> 1749: <a href="javascript:kwhighlight()"; TARGET=_self>Highlight Attribute</a><br /><br /> 1750: KEYWORDS 1751: # 1752: # Load the other essays for similarity check 1753: # 1754: my (undef,undef,$essayurl) = &Apache::lonnet::decode_symb($symb); 1755: my ($adom,$aname,$apath)=($essayurl=~/^(\w+)\/(\w+)\/(.*)$/); 1756: $apath=&escape($apath); 1757: $apath=~s/\W/\_/gs; 1758: %oldessays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname); 1759: } 1760: } 1761: 1762: if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') { 1763: $request->print('<br /><br /><br />') if ($counter > 0); 1764: my $mode; 1765: if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') { 1766: $mode='both'; 1767: } elsif ($env{'form.vProb'} eq 'all' ) { 1768: $mode='text'; 1769: } elsif ($env{'form.vAns'} eq 'all') { 1770: $mode='answer'; 1771: } 1772: &Apache::lonxml::clear_problem_counter(); 1773: $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode)); 1774: } 1775: 1776: my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname); 1777: my ($partlist,$handgrade,$responseType) = &response_type($symb); 1778: 1779: # Display student info 1780: $request->print(($counter == 0 ? '' : '<br />')); 1781: my $result='<table border="0" width="100%"><tr><td bgcolor="#777777">'."\n". 1782: '<table border="0" width="100%"><tr bgcolor="#edffff"><td>'."\n"; 1783: 1784: $result.='<b>Fullname: </b>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'<br />'."\n"; 1785: $result.='<input type="hidden" name="name'.$counter. 1786: '" value="'.$env{'form.fullname'}.'" />'."\n"; 1787: 1788: # If any part of the problem is an essay-response (handgraded), then check for collaborators 1789: my @col_fullnames; 1790: my ($classlist,$fullname); 1791: if ($env{'form.handgrade'} eq 'yes') { 1792: ($classlist,undef,$fullname) = &getclasslist('all','0'); 1793: for (keys (%$handgrade)) { 1794: my $ncol = &Apache::lonnet::EXT('resource.'.$_. 1795: '.maxcollaborators', 1796: $symb,$udom,$uname); 1797: next if ($ncol <= 0); 1798: s/\_/\./g; 1799: next if ($record{'resource.'.$_.'.collaborators'} eq ''); 1800: my @goodcollaborators = (); 1801: my @badcollaborators = (); 1802: foreach (split(/,?\s+/,$record{'resource.'.$_.'.collaborators'})) { 1803: $_ =~ s/[\$\^\(\)]//g; 1804: next if ($_ eq ''); 1805: my ($co_name,$co_dom) = split /\@|:/,$_; 1806: $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i); 1807: next if ($co_name eq $uname && $co_dom eq $udom); 1808: # Doing this grep allows 'fuzzy' specification 1809: my @Matches = grep /^$co_name:$co_dom$/i,keys %$classlist; 1810: if (! scalar(@Matches)) { 1811: push @badcollaborators,$_; 1812: } else { 1813: push @goodcollaborators, @Matches; 1814: } 1815: } 1816: if (scalar(@goodcollaborators) != 0) { 1817: $result.='<b>Collaborators: </b>'; 1818: foreach (@goodcollaborators) { 1819: my ($lastname,$givenn) = split(/,/,$$fullname{$_}); 1820: push @col_fullnames, $givenn.' '.$lastname; 1821: $result.=$$fullname{$_}.' '; 1822: } 1823: $result.='<br />'."\n"; 1824: my ($part)=split(/\./,$_); 1825: $result.='<input type="hidden" name="collaborator'.$counter. 1826: '" value="'.$part.':'.(join ':',@goodcollaborators).'" />'. 1827: "\n"; 1828: } 1829: if (scalar(@badcollaborators) > 0) { 1830: $result.='<table border="0"><tr bgcolor="#ffbbbb"><td>'; 1831: $result.='This student has submitted '; 1832: $result.=(scalar(@badcollaborators) == 1) ? 'an invalid collaborator' : 'invalid collaborators'; 1833: $result .= ': '.join(', ',@badcollaborators); 1834: $result .= '</td></tr></table>'; 1835: } 1836: if (scalar(@badcollaborators > $ncol)) { 1837: $result .= '<table border="0"><tr bgcolor="#ffbbbb"><td>'; 1838: $result .= 'This student has submitted too many '. 1839: 'collaborators. Maximum is '.$ncol.'.'; 1840: $result .= '</td></tr></table>'; 1841: } 1842: } 1843: } 1844: $request->print($result."\n"); 1845: 1846: # print student answer/submission 1847: # Options are (1) Handgaded submission only 1848: # (2) Last submission, includes submission that is not handgraded 1849: # (for multi-response type part) 1850: # (3) Last submission plus the parts info 1851: # (4) The whole record for this student 1852: if ($env{'form.lastSub'} =~ /^(lastonly|hdgrade)$/) { 1853: my ($string,$timestamp)= &get_last_submission(\%record); 1854: my $lastsubonly=''. 1855: ($$timestamp eq '' ? '' : '<b>Date Submitted:</b> '. 1856: $$timestamp)."</td></tr>\n"; 1857: if ($$timestamp eq '') { 1858: $lastsubonly.='<tr><td bgcolor="#ffffe6">'.$$string[0]; 1859: } else { 1860: my %seenparts; 1861: for my $part (sort keys(%$handgrade)) { 1862: my ($partid,$respid) = split(/_/,$part); 1863: my $display_part=&get_display_part($partid,$symb); 1864: if ($env{"form.$uname:$udom:$partid:submitted_by"}) { 1865: if (exists($seenparts{$partid})) { next; } 1866: $seenparts{$partid}=1; 1867: my $submitby='<b>Part:</b> '.$display_part. 1868: ' <b>Collaborative submission by:</b> '. 1869: '<a href="javascript:viewSubmitter(\''. 1870: $env{"form.$uname:$udom:$partid:submitted_by"}. 1871: '\')"; TARGET=_self>'. 1872: $$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a><br />'; 1873: $request->print($submitby); 1874: next; 1875: } 1876: my $responsetype = $responseType->{$partid}->{$respid}; 1877: if (!exists($record{"resource.$partid.$respid.submission"})) { 1878: $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '. 1879: $display_part.' <font color="#999999">( ID '.$respid. 1880: ' )</font> '. 1881: '<font color="red">Nothing submitted - no attempts</font><br /><br />'; 1882: next; 1883: } 1884: foreach (@$string) { 1885: my ($partid,$respid) = /^resource\.([^\.]*)\.([^\.]*)\.submission/; 1886: if ($part ne ($partid.'_'.$respid)) { next; } 1887: my ($ressub,$subval) = split(/:/,$_,2); 1888: # Similarity check 1889: my $similar=''; 1890: if($env{'form.checkPlag'}){ 1891: my ($oname,$odom,$ocrsid,$oessay,$osim)= 1892: &most_similar($uname,$udom,$subval); 1893: if ($osim) { 1894: $osim=int($osim*100.0); 1895: $similar="<hr /><h3><font color=\"#FF0000\">Essay". 1896: " is $osim% similar to an essay by ". 1897: &Apache::loncommon::plainname($oname,$odom). 1898: '</font></h3><blockquote><i>'. 1899: &keywords_highlight($oessay). 1900: '</i></blockquote><hr />'; 1901: } 1902: } 1903: my $order=&get_order($partid,$respid,$symb,$uname,$udom); 1904: if ($env{'form.lastSub'} eq 'lastonly' || 1905: ($env{'form.lastSub'} eq 'hdgrade' && 1906: $$handgrade{$part} eq 'yes')) { 1907: my $display_part=&get_display_part($partid,$symb); 1908: $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '. 1909: $display_part.' <font color="#999999">( ID '.$respid. 1910: ' )</font> '; 1911: my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record); 1912: if (@$files) { 1913: $lastsubonly.='<br /><font color="red" size="1">Like all files provided by users, this file may contain virusses</font><br />'; 1914: my $file_counter = 0; 1915: foreach my $file (@$files) { 1916: $file_counter ++; 1917: &Apache::lonnet::allowuploaded('/adm/grades',$file); 1918: $lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border=0"> '.$file.'</a>'; 1919: } 1920: $lastsubonly.='<br />'; 1921: } 1922: $lastsubonly.='<b>Submitted Answer: </b>'. 1923: &cleanRecord($subval,$responsetype,$symb,$partid, 1924: $respid,\%record,$order); 1925: if ($similar) {$lastsubonly.="<br /><br />$similar\n";} 1926: } 1927: } 1928: } 1929: } 1930: $lastsubonly.='</td></tr><tr bgcolor="#ffffff"><td>'."\n"; 1931: $request->print($lastsubonly); 1932: } elsif ($env{'form.lastSub'} eq 'datesub') { 1933: my (undef,$responseType,undef,$parts) = &showResourceInfo($symb); 1934: $request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom)); 1935: } elsif ($env{'form.lastSub'} =~ /^(last|all)$/) { 1936: $request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom, 1937: $env{'request.course.id'}, 1938: $last,'.submission', 1939: 'Apache::grades::keywords_highlight')); 1940: } 1941: 1942: $request->print('<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':' 1943: .$udom.'" />'."\n"); 1944: 1945: # return if view submission with no grading option 1946: if ($env{'form.showgrading'} eq '' || (!&canmodify($usec))) { 1947: my $toGrade.='<input type="button" value="Grade Student" '. 1948: 'onClick="javascript:checksubmit(this.form,\'Grade Student\',\'' 1949: .$counter.'\');" TARGET=_self> '."\n" if (&canmodify($usec)); 1950: $toGrade.='</td></tr></table></td></tr></table>'."\n"; 1951: if (($env{'form.command'} eq 'submission') || 1952: ($env{'form.command'} eq 'processGroup' && $counter == $total)) { 1953: $toGrade.='</form>'.&show_grading_menu_form($symb); 1954: } 1955: $request->print($toGrade); 1956: return; 1957: } else { 1958: $request->print('</td></tr></table></td></tr></table>'."\n"); 1959: } 1960: 1961: # essay grading message center 1962: if ($env{'form.handgrade'} eq 'yes') { 1963: my ($lastname,$givenn) = split(/,/,$env{'form.fullname'}); 1964: my $msgfor = $givenn.' '.$lastname; 1965: if (scalar(@col_fullnames) > 0) { 1966: my $lastone = pop @col_fullnames; 1967: $msgfor .= ', '.(join ', ',@col_fullnames).' and '.$lastone.'.'; 1968: } 1969: $msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript 1970: $result='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n". 1971: '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n"; 1972: $result.=' <a href="javascript:msgCenter(document.SCORE,'.$counter. 1973: ',\''.$msgfor.'\')"; TARGET=_self>'. 1974: &mt('Compose message to student').(scalar(@col_fullnames) >= 1 ? 's' : '').'</a><label> ('. 1975: &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" /></label>)'. 1976: '<img src="'.$request->dir_config('lonIconsURL'). 1977: '/mailbkgrd.gif" width="14" height="10" name="mailicon'.$counter.'" />'."\n". 1978: '<br /> ('. 1979: &mt('Message will be sent when you click on Save & Next below.').")\n"; 1980: $request->print($result); 1981: } 1982: if ($perm{'vgr'}) { 1983: $request->print('<br />'. 1984: &Apache::loncommon::track_student_link(&mt('View recent activity'), 1985: $uname,$udom,'check')); 1986: } 1987: if ($perm{'opa'}) { 1988: $request->print('<br />'. 1989: &Apache::loncommon::pprmlink(&mt('Set/Change parameters'), 1990: $uname,$udom,$symb,'check')); 1991: } 1992: 1993: my %seen = (); 1994: my @partlist; 1995: my @gradePartRespid; 1996: for my $part_resp (sort(keys(%$handgrade))) { 1997: my ($partid,$respid) = split(/_/, $part_resp); 1998: next if ($seen{$partid} > 0); 1999: $seen{$partid}++; 2000: next if ($$handgrade{$part_resp} =~ /:no$/ && $env{'form.lastSub'} =~ /^(hdgrade)$/); 2001: push @partlist,$partid; 2002: push @gradePartRespid,$partid.'.'.$respid; 2003: $request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record)); 2004: } 2005: $result='<input type="hidden" name="partlist'.$counter. 2006: '" value="'.(join ":",@partlist).'" />'."\n"; 2007: $result.='<input type="hidden" name="gradePartRespid'. 2008: '" value="'.(join ":",@gradePartRespid).'" />'."\n" if ($counter == 0); 2009: my $ctr = 0; 2010: while ($ctr < scalar(@partlist)) { 2011: $result.='<input type="hidden" name="partid'.$counter.'_'.$ctr.'" value="'. 2012: $partlist[$ctr].'" />'."\n"; 2013: $ctr++; 2014: } 2015: $request->print($result.'</td></tr></table></td></tr></table>'."\n"); 2016: 2017: # print end of form 2018: if ($counter == $total) { 2019: my $endform='<table border="0"><tr><td>'."\n"; 2020: $endform.='<input type="button" value="Save & Next" '. 2021: 'onClick="javascript:checksubmit(this.form,\'Save & Next\','. 2022: $total.','.scalar(@partlist).');" TARGET=_self> '."\n"; 2023: my $ntstu ='<select name="NTSTU">'. 2024: '<option>1</option><option>2</option>'. 2025: '<option>3</option><option>5</option>'. 2026: '<option>7</option><option>10</option></select>'."\n"; 2027: my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1'); 2028: $ntstu =~ s/<option>$nsel</<option selected="on">$nsel</; 2029: $endform.=$ntstu.'student(s) '; 2030: $endform.='<input type="button" value="Previous" '. 2031: 'onClick="javascript:checksubmit(this.form,\'Previous\');" TARGET=_self> '."\n". 2032: '<input type="button" value="Next" '. 2033: 'onClick="javascript:checksubmit(this.form,\'Next\');" TARGET=_self> '; 2034: $endform.='(Next and Previous (student) do not save the scores.)'."\n" ; 2035: $endform.="<input type='hidden' value='".&get_increment(). 2036: "' name='increment' />"; 2037: $endform.='</td><tr></table></form>'; 2038: $endform.=&show_grading_menu_form($symb); 2039: $request->print($endform); 2040: } 2041: return ''; 2042: } 2043: 2044: #--- Retrieve the last submission for all the parts 2045: sub get_last_submission { 2046: my ($returnhash)=@_; 2047: my (@string,$timestamp); 2048: if ($$returnhash{'version'}) { 2049: my %lasthash=(); 2050: my ($version); 2051: for ($version=1;$version<=$$returnhash{'version'};$version++) { 2052: foreach (sort(split(/\:/,$$returnhash{$version.':keys'}))) { 2053: $lasthash{$_}=$$returnhash{$version.':'.$_}; 2054: $timestamp = scalar(localtime($$returnhash{$version.':timestamp'})); 2055: } 2056: } 2057: foreach ((keys %lasthash)) { 2058: if ($_ =~ /\.submission$/) { 2059: my ($partid,$foo) = split(/submission$/,$_); 2060: my $draft = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ? 2061: '<font color="red">Draft Copy</font> ' : ''; 2062: push @string, (join(':',$_,$draft.$lasthash{$_})); 2063: } 2064: } 2065: } 2066: @string = $string[0] eq '' ? '<font color="red">Nothing submitted - no attempts.</font>' : @string; 2067: return \@string,\$timestamp; 2068: } 2069: 2070: #--- High light keywords, with style choosen by user. 2071: sub keywords_highlight { 2072: my $string = shift; 2073: my $size = $env{'form.kwsize'} eq '0' ? '' : 'size='.$env{'form.kwsize'}; 2074: my $styleon = $env{'form.kwstyle'} eq '' ? '' : $env{'form.kwstyle'}; 2075: (my $styleoff = $styleon) =~ s/\</\<\//; 2076: my @keylist = split(/[,\s+]/,$env{'form.keywords'}); 2077: foreach (@keylist) { 2078: $string =~ s/\b\Q$_\E(\b|\.)/<font color\=$env{'form.kwclr'} $size\>$styleon$_$styleoff<\/font>/gi; 2079: } 2080: return $string; 2081: } 2082: 2083: #--- Called from submission routine 2084: sub processHandGrade { 2085: my ($request) = shift; 2086: my $symb = &get_symb($request); 2087: my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb); 2088: my $button = $env{'form.gradeOpt'}; 2089: my $ngrade = $env{'form.NCT'}; 2090: my $ntstu = $env{'form.NTSTU'}; 2091: my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; 2092: my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; 2093: 2094: if ($button eq 'Save & Next') { 2095: my $ctr = 0; 2096: while ($ctr < $ngrade) { 2097: my ($uname,$udom) = split(/:/,$env{'form.unamedom'.$ctr}); 2098: my ($errorflag,$pts,$wgt) = &saveHandGrade($request,$symb,$uname,$udom,$ctr); 2099: if ($errorflag eq 'no_score') { 2100: $ctr++; 2101: next; 2102: } 2103: if ($errorflag eq 'not_allowed') { 2104: $request->print("<font color=\"red\">Not allowed to modify grades for $uname:$udom</font>"); 2105: $ctr++; 2106: next; 2107: } 2108: my $includemsg = $env{'form.includemsg'.$ctr}; 2109: my ($subject,$message,$msgstatus) = ('','',''); 2110: if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) { 2111: $subject = $env{'form.msgsub'} if ($includemsg =~ /msgsub/); 2112: unless ($subject=~/\w/) { $subject=&mt('Grading Feedback'); } 2113: $subject.=' ['.&Apache::lonnet::declutter($url).']'; 2114: my (@msgnum) = split(/,/,$includemsg); 2115: foreach (@msgnum) { 2116: $message.=$env{'form.'.$_} if ($_ =~ /savemsg|newmsg/ && $_ ne ''); 2117: } 2118: $message =&Apache::lonfeedback::clear_out_html($message); 2119: if ($env{'form.withgrades'.$ctr}) { 2120: $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt; 2121: $message.=" for <a href=\"". 2122: &Apache::lonnet::clutter($url). 2123: "?symb=$symb\">$env{'form.probTitle'}</a>"; 2124: } 2125: $msgstatus = &Apache::lonmsg::user_normal_msg($uname,$udom, 2126: $subject, 2127: $message); 2128: $request->print('<br />'.&mt('Sending message to [_1]@[_2]',$uname,$udom).': '. 2129: $msgstatus); 2130: } 2131: if ($env{'form.collaborator'.$ctr}) { 2132: my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr"); 2133: foreach my $collabstr (@collabstrs) { 2134: my ($part,@collaborators) = split(/:/,$collabstr); 2135: foreach my $collaborator (@collaborators) { 2136: my ($errorflag,$pts,$wgt) = 2137: &saveHandGrade($request,$symb,$collaborator,$udom,$ctr, 2138: $env{'form.unamedom'.$ctr},$part); 2139: if ($errorflag eq 'not_allowed') { 2140: $request->print("<span class=\"LC_error\">".&mt('Not allowed to modify grades for [_1]',"$collaborator:$udom")."</span>"); 2141: next; 2142: } else { 2143: if ($message ne '') { 2144: $msgstatus = &Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message); 2145: } 2146: } 2147: } 2148: } 2149: } 2150: $ctr++; 2151: } 2152: } 2153: 2154: if ($env{'form.handgrade'} eq 'yes') { 2155: # Keywords sorted in alphabatical order 2156: my $loginuser = $env{'user.name'}.':'.$env{'user.domain'}; 2157: my %keyhash = (); 2158: $env{'form.keywords'} =~ s/,\s{0,}|\s+/ /g; 2159: $env{'form.keywords'} =~ s/^\s+|\s+$//; 2160: my (@keywords) = sort(split(/\s+/,$env{'form.keywords'})); 2161: $env{'form.keywords'} = join(' ',@keywords); 2162: $keyhash{$symb.'_keywords'} = $env{'form.keywords'}; 2163: $keyhash{$symb.'_subject'} = $env{'form.msgsub'}; 2164: $keyhash{$loginuser.'_kwclr'} = $env{'form.kwclr'}; 2165: $keyhash{$loginuser.'_kwsize'} = $env{'form.kwsize'}; 2166: $keyhash{$loginuser.'_kwstyle'} = $env{'form.kwstyle'}; 2167: 2168: # message center - Order of message gets changed. Blank line is eliminated. 2169: # New messages are saved in env for the next student. 2170: # All messages are saved in nohist_handgrade.db 2171: my ($ctr,$idx) = (1,1); 2172: while ($ctr <= $env{'form.savemsgN'}) { 2173: if ($env{'form.savemsg'.$ctr} ne '') { 2174: $keyhash{$symb.'_savemsg'.$idx} = $env{'form.savemsg'.$ctr}; 2175: $idx++; 2176: } 2177: $ctr++; 2178: } 2179: $ctr = 0; 2180: while ($ctr < $ngrade) { 2181: if ($env{'form.newmsg'.$ctr} ne '') { 2182: $keyhash{$symb.'_savemsg'.$idx} = $env{'form.newmsg'.$ctr}; 2183: $env{'form.savemsg'.$idx} = $env{'form.newmsg'.$ctr}; 2184: $idx++; 2185: } 2186: $ctr++; 2187: } 2188: $env{'form.savemsgN'} = --$idx; 2189: $keyhash{$symb.'_savemsgN'} = $env{'form.savemsgN'}; 2190: my $putresult = &Apache::lonnet::put 2191: ('nohist_handgrade',\%keyhash,$cdom,$cnum); 2192: } 2193: # Called by Save & Refresh from Highlight Attribute Window 2194: my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1'); 2195: if ($env{'form.refresh'} eq 'on') { 2196: my ($ctr,$total) = (0,0); 2197: while ($ctr < $ngrade) { 2198: $total++ if $env{'form.unamedom'.$ctr} ne ''; 2199: $ctr++; 2200: } 2201: $env{'form.NTSTU'}=$ngrade; 2202: $ctr = 0; 2203: while ($ctr < $total) { 2204: my $processUser = $env{'form.unamedom'.$ctr}; 2205: ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser); 2206: $env{'form.fullname'} = $$fullname{$processUser}; 2207: &submission($request,$ctr,$total-1); 2208: $ctr++; 2209: } 2210: return ''; 2211: } 2212: 2213: # Go directly to grade student - from submission or link from chart page 2214: if ($button eq 'Grade Student') { 2215: (undef,undef,$env{'form.handgrade'},undef,undef) = &showResourceInfo($symb); 2216: my $processUser = $env{'form.unamedom'.$env{'form.studentNo'}}; 2217: ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser); 2218: $env{'form.fullname'} = $$fullname{$processUser}; 2219: &submission($request,0,0); 2220: return ''; 2221: } 2222: 2223: # Get the next/previous one or group of students 2224: my $firststu = $env{'form.unamedom0'}; 2225: my $laststu = $env{'form.unamedom'.($ngrade-1)}; 2226: my $ctr = 2; 2227: while ($laststu eq '') { 2228: $laststu = $env{'form.unamedom'.($ngrade-$ctr)}; 2229: $ctr++; 2230: $laststu = $firststu if ($ctr > $ngrade); 2231: } 2232: 2233: my (@parsedlist,@nextlist); 2234: my ($nextflg) = 0; 2235: foreach (sort 2236: { 2237: if (lc($$fullname{$a}) ne lc($$fullname{$b})) { 2238: return (lc($$fullname{$a}) cmp lc($$fullname{$b})); 2239: } 2240: return $a cmp $b; 2241: } (keys(%$fullname))) { 2242: if ($nextflg == 1 && $button =~ /Next$/) { 2243: push @parsedlist,$_; 2244: } 2245: $nextflg = 1 if ($_ eq $laststu); 2246: if ($button eq 'Previous') { 2247: last if ($_ eq $firststu); 2248: push @parsedlist,$_; 2249: } 2250: } 2251: $ctr = 0; 2252: @parsedlist = reverse @parsedlist if ($button eq 'Previous'); 2253: my ($partlist) = &response_type($symb); 2254: foreach my $student (@parsedlist) { 2255: my $submitonly=$env{'form.submitonly'}; 2256: my ($uname,$udom) = split(/:/,$student); 2257: 2258: if ($submitonly eq 'queued') { 2259: my %queue_status = 2260: &Apache::bridgetask::get_student_status($symb,$cdom,$cnum, 2261: $udom,$uname); 2262: next if (!defined($queue_status{'gradingqueue'})); 2263: } 2264: 2265: if ($submitonly =~ /^(yes|graded|incorrect)$/) { 2266: # my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname); 2267: my %status=&student_gradeStatus($symb,$udom,$uname,$partlist); 2268: my $submitted = 0; 2269: my $ungraded = 0; 2270: my $incorrect = 0; 2271: foreach (keys(%status)) { 2272: $submitted = 1 if ($status{$_} ne 'nothing'); 2273: $ungraded = 1 if ($status{$_} =~ /^ungraded/); 2274: $incorrect = 1 if ($status{$_} =~ /^incorrect/); 2275: my ($foo,$partid,$foo1) = split(/\./,$_); 2276: if ($status{'resource.'.$partid.'.submitted_by'} ne '') { 2277: $submitted = 0; 2278: } 2279: } 2280: next if (!$submitted && ($submitonly eq 'yes' || 2281: $submitonly eq 'incorrect' || 2282: $submitonly eq 'graded')); 2283: next if (!$ungraded && ($submitonly eq 'graded')); 2284: next if (!$incorrect && $submitonly eq 'incorrect'); 2285: } 2286: push @nextlist,$student if ($ctr < $ntstu); 2287: last if ($ctr == $ntstu); 2288: $ctr++; 2289: } 2290: 2291: $ctr = 0; 2292: my $total = scalar(@nextlist)-1; 2293: 2294: foreach (sort @nextlist) { 2295: my ($uname,$udom,$submitter) = split(/:/); 2296: $env{'form.student'} = $uname; 2297: $env{'form.userdom'} = $udom; 2298: $env{'form.fullname'} = $$fullname{$_}; 2299: &submission($request,$ctr,$total); 2300: $ctr++; 2301: } 2302: if ($total < 0) { 2303: my $the_end = '<h3><font color="red">LON-CAPA User Message</font></h3><br />'."\n"; 2304: $the_end.='<b>Message: </b> No more students for this section or class.<br /><br />'."\n"; 2305: $the_end.='Click on the button below to return to the grading menu.<br /><br />'."\n"; 2306: $the_end.=&show_grading_menu_form($symb); 2307: $request->print($the_end); 2308: } 2309: return ''; 2310: } 2311: 2312: #---- Save the score and award for each student, if changed 2313: sub saveHandGrade { 2314: my ($request,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_; 2315: my @version_parts; 2316: my $usec = &Apache::lonnet::getsection($domain,$stuname, 2317: $env{'request.course.id'}); 2318: if (!&canmodify($usec)) { return('not_allowed'); } 2319: my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$stuname); 2320: my @parts_graded; 2321: my %newrecord = (); 2322: my ($pts,$wgt) = ('',''); 2323: my %aggregate = (); 2324: my $aggregateflag = 0; 2325: my @parts = split(/:/,$env{'form.partlist'.$newflg}); 2326: foreach my $new_part (@parts) { 2327: #collaborator ($submi may vary for different parts 2328: if ($submitter && $new_part ne $part) { next; } 2329: my $dropMenu = $env{'form.GD_SEL'.$newflg.'_'.$new_part}; 2330: if ($dropMenu eq 'excused') { 2331: if ($record{'resource.'.$new_part.'.solved'} ne 'excused') { 2332: $newrecord{'resource.'.$new_part.'.solved'} = 'excused'; 2333: if (exists($record{'resource.'.$new_part.'.awarded'})) { 2334: $newrecord{'resource.'.$new_part.'.awarded'} = ''; 2335: } 2336: $newrecord{'resource.'.$new_part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}"; 2337: } 2338: } elsif ($dropMenu eq 'reset status' 2339: && exists($record{'resource.'.$new_part.'.solved'})) { #don't bother if no old records -> no attempts 2340: foreach my $key (keys (%record)) { 2341: if ($key=~/^resource\.\Q$new_part\E\./) { $newrecord{$key} = ''; } 2342: } 2343: $newrecord{'resource.'.$new_part.'.regrader'}= 2344: "$env{'user.name'}:$env{'user.domain'}"; 2345: my $totaltries = $record{'resource.'.$part.'.tries'}; 2346: 2347: my %last_resets = &get_last_resets($symb,$env{'request.course.id'}, 2348: [$new_part]); 2349: my $aggtries =$totaltries; 2350: if ($last_resets{$new_part}) { 2351: $aggtries = &get_num_tries(\%record,$last_resets{$new_part}, 2352: $new_part); 2353: } 2354: 2355: my $solvedstatus = $record{'resource.'.$new_part.'.solved'}; 2356: if ($aggtries > 0) { 2357: &decrement_aggs($symb,$new_part,\%aggregate,$aggtries,$totaltries,$solvedstatus); 2358: $aggregateflag = 1; 2359: } 2360: } elsif ($dropMenu eq '') { 2361: $pts = ($env{'form.GD_BOX'.$newflg.'_'.$new_part} ne '' ? 2362: $env{'form.GD_BOX'.$newflg.'_'.$new_part} : 2363: $env{'form.RADVAL'.$newflg.'_'.$new_part}); 2364: if ($pts eq '' && $env{'form.GD_SEL'.$newflg.'_'.$new_part} eq '') { 2365: next; 2366: } 2367: $wgt = $env{'form.WGT'.$newflg.'_'.$new_part} eq '' ? 1 : 2368: $env{'form.WGT'.$newflg.'_'.$new_part}; 2369: my $partial= $pts/$wgt; 2370: if ($partial eq $record{'resource.'.$new_part.'.awarded'}) { 2371: #do not update score for part if not changed. 2372: &handback_files($request,$symb,$stuname,$domain,$newflg,$new_part,\%newrecord); 2373: next; 2374: } else { 2375: push @parts_graded, $new_part; 2376: } 2377: if ($record{'resource.'.$new_part.'.awarded'} ne $partial) { 2378: $newrecord{'resource.'.$new_part.'.awarded'} = $partial; 2379: } 2380: my $reckey = 'resource.'.$new_part.'.solved'; 2381: if ($partial == 0) { 2382: if ($record{$reckey} ne 'incorrect_by_override') { 2383: $newrecord{$reckey} = 'incorrect_by_override'; 2384: } 2385: } else { 2386: if ($record{$reckey} ne 'correct_by_override') { 2387: $newrecord{$reckey} = 'correct_by_override'; 2388: } 2389: } 2390: if ($submitter && 2391: ($record{'resource.'.$new_part.'.submitted_by'} ne $submitter)) { 2392: $newrecord{'resource.'.$new_part.'.submitted_by'} = $submitter; 2393: } 2394: $newrecord{'resource.'.$new_part.'.regrader'}= 2395: "$env{'user.name'}:$env{'user.domain'}"; 2396: } 2397: # unless problem has been graded, set flag to version the submitted files 2398: unless ($record{'resource.'.$new_part.'.solved'} =~ /^correct_/ || 2399: $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override' || 2400: $dropMenu eq 'reset status') 2401: { 2402: push (@version_parts,$new_part); 2403: } 2404: } 2405: my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; 2406: my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; 2407: 2408: if (%newrecord) { 2409: if (@version_parts) { 2410: my @changed_keys = &version_portfiles(\%record, \@parts_graded, 2411: $env{'request.course.id'}, $symb, $domain, $stuname, \@version_parts); 2412: @newrecord{@changed_keys} = @record{@changed_keys}; 2413: foreach my $new_part (@version_parts) { 2414: &handback_files($request,$symb,$stuname,$domain,$newflg, 2415: $new_part,\%newrecord); 2416: } 2417: } 2418: &Apache::lonnet::cstore(\%newrecord,$symb, 2419: $env{'request.course.id'},$domain,$stuname); 2420: my @ungraded_parts; 2421: foreach my $part (@parts) { 2422: if ( !defined($record{'resource.'.$part.'.awarded'}) 2423: && !defined($newrecord{'resource.'.$part.'.awarded'}) ) { 2424: push(@ungraded_parts, $part); 2425: } 2426: } 2427: if ( !@ungraded_parts ) { 2428: &Apache::bridgetask::remove_from_queue('gradingqueue',$symb,$cdom, 2429: $cnum,$domain,$stuname); 2430: } 2431: } 2432: if ($aggregateflag) { 2433: &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate, 2434: $cdom,$cnum); 2435: } 2436: return ('',$pts,$wgt); 2437: } 2438: 2439: sub handback_files { 2440: my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_; 2441: my $portfolio_root = &propath($domain,$stuname).'/userfiles/portfolio'; 2442: my ($partlist,$handgrade,$responseType) = &response_type($symb); 2443: foreach my $part_resp (sort(keys(%$handgrade))) { 2444: my ($part_id, $resp_id) = split(/_/,$part_resp); 2445: if (($env{'form.'.$newflg.'_'.$part_resp.'_returndoc1'}) && ($new_part == $part_id)) { 2446: # if multiple files are uploaded names will be 'returndoc2','returndoc3' 2447: my $file_counter = 1; 2448: my $file_msg; 2449: while ($env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter}) { 2450: my $fname=$env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'.filename'}; 2451: my ($directory,$answer_file) = 2452: ($env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$file_counter} =~ /^(.*?)([^\/]*)$/); 2453: my ($answer_name,$answer_ver,$answer_ext) = 2454: &file_name_version_ext($answer_file); 2455: my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/); 2456: my @dir_list = &Apache::lonnet::dirlist($portfolio_path,$domain,$stuname,$portfolio_root); 2457: my $version = &get_next_version($answer_name, $answer_ext, \@dir_list); 2458: # fix file name 2459: my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/); 2460: my $result=&Apache::lonnet::finishuserfileupload($stuname,$domain, 2461: $newflg.'_'.$part_resp.'_returndoc'.$file_counter, 2462: $save_file_name); 2463: if ($result !~ m|^/uploaded/|) { 2464: $request->print('<font color="red"> An errror occured ('.$result. 2465: ') while trying to upload '.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'</font><br />'); 2466: } else { 2467: # mark the file as read only 2468: my @files = ($save_file_name); 2469: my @what = ($symb,$env{'request.course.id'},'handback'); 2470: &Apache::lonnet::mark_as_readonly($domain,$stuname,\@files,\@what); 2471: if (exists($$newrecord{"resource.$new_part.$resp_id.handback"})) { 2472: $$newrecord{"resource.$new_part.$resp_id.handback"}.=','; 2473: } 2474: $$newrecord{"resource.$new_part.$resp_id.handback"} .= $save_file_name; 2475: $file_msg.= "\n".'<br /><span class="LC_filename"><a href="/uploaded/'."$domain/$stuname/".$save_file_name.'">'.$save_file_name."</a></span><br />"; 2476: 2477: } 2478: $request->print("<br />".$fname." will be the uploaded file name"); 2479: $request->print(" ".$env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$file_counter}); 2480: $file_counter++; 2481: } 2482: my $subject = "File Handed Back by Instructor "; 2483: my $message = "A file has been returned that was originally submitted in reponse to: <br />"; 2484: $message .= "<strong>".&Apache::lonnet::gettitle($symb)."</strong><br />"; 2485: $message .= ' The returned file(s) are named: '. $file_msg; 2486: $message .= " and can be found in your portfolio space."; 2487: my $url = (&Apache::lonnet::decode_symb($symb))[2]; 2488: $url = &Apache::lonnet::declutter($url); 2489: my $msgstatus = &Apache::lonmsg::user_normal_msg($stuname,$domain, 2490: $subject.' (File Returned) ['.$url.']',$message); 2491: 2492: } 2493: } 2494: return; 2495: } 2496: 2497: sub get_submitted_files { 2498: my ($udom,$uname,$partid,$respid,$record) = @_; 2499: my @files; 2500: if ($$record{"resource.$partid.$respid.portfiles"}) { 2501: my $file_url = '/uploaded/'.$udom.'/'.$uname.'/portfolio'; 2502: foreach my $file (split(',',$$record{"resource.$partid.$respid.portfiles"})) { 2503: push(@files,$file_url.$file); 2504: } 2505: } 2506: if ($$record{"resource.$partid.$respid.uploadedurl"}) { 2507: push(@files,$$record{"resource.$partid.$respid.uploadedurl"}); 2508: } 2509: return (\@files); 2510: } 2511: 2512: # ----------- Provides number of tries since last reset. 2513: sub get_num_tries { 2514: my ($record,$last_reset,$part) = @_; 2515: my $timestamp = ''; 2516: my $num_tries = 0; 2517: if ($$record{'version'}) { 2518: for (my $version=$$record{'version'};$version>=1;$version--) { 2519: if (exists($$record{$version.':resource.'.$part.'.solved'})) { 2520: $timestamp = $$record{$version.':timestamp'}; 2521: if ($timestamp > $last_reset) { 2522: $num_tries ++; 2523: } else { 2524: last; 2525: } 2526: } 2527: } 2528: } 2529: return $num_tries; 2530: } 2531: 2532: # ----------- Determine decrements required in aggregate totals 2533: sub decrement_aggs { 2534: my ($symb,$part,$aggregate,$aggtries,$totaltries,$solvedstatus) = @_; 2535: my %decrement = ( 2536: attempts => 0, 2537: users => 0, 2538: correct => 0 2539: ); 2540: $decrement{'attempts'} = $aggtries; 2541: if ($solvedstatus =~ /^correct/) { 2542: $decrement{'correct'} = 1; 2543: } 2544: if ($aggtries == $totaltries) { 2545: $decrement{'users'} = 1; 2546: } 2547: foreach my $type (keys (%decrement)) { 2548: $$aggregate{$symb."\0".$part."\0".$type} = -$decrement{$type}; 2549: } 2550: return; 2551: } 2552: 2553: # ----------- Determine timestamps for last reset of aggregate totals for parts 2554: sub get_last_resets { 2555: my ($symb,$courseid,$partids) =@_; 2556: my %last_resets; 2557: my $cdom = $env{'course.'.$courseid.'.domain'}; 2558: my $cname = $env{'course.'.$courseid.'.num'}; 2559: my @keys; 2560: foreach my $part (@{$partids}) { 2561: push(@keys,"$symb\0$part\0resettime"); 2562: } 2563: my %results=&Apache::lonnet::get('nohist_resourcetracker',\@keys, 2564: $cdom,$cname); 2565: foreach my $part (@{$partids}) { 2566: $last_resets{$part}=$results{"$symb\0$part\0resettime"}; 2567: } 2568: return %last_resets; 2569: } 2570: 2571: # ----------- Handles creating versions for portfolio files as answers 2572: sub version_portfiles { 2573: my ($record, $parts_graded, $courseid, $symb, $domain, $stu_name, $v_flag) = @_; 2574: my $version_parts = join('|',@$v_flag); 2575: my @returned_keys; 2576: my $parts = join('|', @$parts_graded); 2577: my $portfolio_root = &propath($domain,$stu_name). 2578: '/userfiles/portfolio'; 2579: foreach my $key (keys(%$record)) { 2580: my $new_portfiles; 2581: if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) { 2582: my @versioned_portfiles; 2583: my @portfiles = split(/\s*,\s*/,$$record{$key}); 2584: foreach my $file (@portfiles) { 2585: &Apache::lonnet::unmark_as_readonly($domain,$stu_name,[$symb,$env{'request.course.id'}],$file); 2586: my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*)$/); 2587: my ($answer_name,$answer_ver,$answer_ext) = 2588: &file_name_version_ext($answer_file); 2589: my @dir_list = &Apache::lonnet::dirlist($directory,$domain,$stu_name,$portfolio_root); 2590: my $version = &get_next_version($answer_name, $answer_ext, \@dir_list); 2591: my $new_answer = &version_selected_portfile($domain, $stu_name, $directory, $answer_file, $version); 2592: if ($new_answer ne 'problem getting file') { 2593: push(@versioned_portfiles, $directory.$new_answer); 2594: &Apache::lonnet::mark_as_readonly($domain,$stu_name, 2595: [$directory.$new_answer], 2596: [$symb,$env{'request.course.id'},'graded']); 2597: } 2598: } 2599: $$record{$key} = join(',',@versioned_portfiles); 2600: push(@returned_keys,$key); 2601: } 2602: } 2603: return (@returned_keys); 2604: } 2605: 2606: sub get_next_version { 2607: my ($answer_name, $answer_ext, $dir_list) = @_; 2608: my $version; 2609: foreach my $row (@$dir_list) { 2610: my ($file) = split(/\&/,$row,2); 2611: my ($file_name,$file_version,$file_ext) = 2612: &file_name_version_ext($file); 2613: if (($file_name eq $answer_name) && 2614: ($file_ext eq $answer_ext)) { 2615: # gets here if filename and extension match, regardless of version 2616: if ($file_version ne '') { 2617: # a versioned file is found so save it for later 2618: if ($file_version > $version) { 2619: $version = $file_version; 2620: } 2621: } 2622: } 2623: } 2624: $version ++; 2625: return($version); 2626: } 2627: 2628: sub version_selected_portfile { 2629: my ($domain,$stu_name,$directory,$file_name,$version) = @_; 2630: my ($answer_name,$answer_ver,$answer_ext) = 2631: &file_name_version_ext($file_name); 2632: my $new_answer; 2633: $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stu_name/portfolio$directory$file_name"); 2634: if($env{'form.copy'} eq '-1') { 2635: &Apache::lonnet::logthis('problem getting file '.$file_name); 2636: $new_answer = 'problem getting file'; 2637: } else { 2638: $new_answer = $answer_name.'.'.$version.'.'.$answer_ext; 2639: my $copy_result = &Apache::lonnet::finishuserfileupload( 2640: $stu_name,$domain,'copy', 2641: '/portfolio'.$directory.$new_answer); 2642: } 2643: return ($new_answer); 2644: } 2645: 2646: sub file_name_version_ext { 2647: my ($file)=@_; 2648: my @file_parts = split(/\./, $file); 2649: my ($name,$version,$ext); 2650: if (@file_parts > 1) { 2651: $ext=pop(@file_parts); 2652: if (@file_parts > 1 && $file_parts[-1] =~ /^\d+$/) { 2653: $version=pop(@file_parts); 2654: } 2655: $name=join('.',@file_parts); 2656: } else { 2657: $name=join('.',@file_parts); 2658: } 2659: return($name,$version,$ext); 2660: } 2661: 2662: #-------------------------------------------------------------------------------------- 2663: # 2664: #-------------------------- Next few routines handles grading by section or whole class 2665: # 2666: #--- Javascript to handle grading by section or whole class 2667: sub viewgrades_js { 2668: my ($request) = shift; 2669: 2670: $request->print(<<VIEWJAVASCRIPT); 2671: <script type="text/javascript" language="javascript"> 2672: function writePoint(partid,weight,point) { 2673: var radioButton = document.classgrade["RADVAL_"+partid]; 2674: var textbox = document.classgrade["TEXTVAL_"+partid]; 2675: if (point == "textval") { 2676: point = document.classgrade["TEXTVAL_"+partid].value; 2677: if (isNaN(point) || parseFloat(point) < 0) { 2678: alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point)); 2679: var resetbox = false; 2680: for (var i=0; i<radioButton.length; i++) { 2681: if (radioButton[i].checked) { 2682: textbox.value = i; 2683: resetbox = true; 2684: } 2685: } 2686: if (!resetbox) { 2687: textbox.value = ""; 2688: } 2689: return; 2690: } 2691: if (parseFloat(point) > parseFloat(weight)) { 2692: var resp = confirm("You entered a value ("+parseFloat(point)+ 2693: ") greater than the weight for the part. Accept?"); 2694: if (resp == false) { 2695: textbox.value = ""; 2696: return; 2697: } 2698: } 2699: for (var i=0; i<radioButton.length; i++) { 2700: radioButton[i].checked=false; 2701: if (parseFloat(point) == i) { 2702: radioButton[i].checked=true; 2703: } 2704: } 2705: 2706: } else { 2707: textbox.value = parseFloat(point); 2708: } 2709: for (i=0;i<document.classgrade.total.value;i++) { 2710: var user = document.classgrade["ctr"+i].value; 2711: user = user.replace(new RegExp(':', 'g'),"_"); 2712: var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"]; 2713: var saveval = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value; 2714: var selname = document.classgrade["GD_"+user+"_"+partid+"_solved"]; 2715: if (saveval != "correct") { 2716: scorename.value = point; 2717: if (selname[0].selected != true) { 2718: selname[0].selected = true; 2719: } 2720: } 2721: } 2722: document.classgrade["SELVAL_"+partid][0].selected = true; 2723: } 2724: 2725: function writeRadText(partid,weight) { 2726: var selval = document.classgrade["SELVAL_"+partid]; 2727: var radioButton = document.classgrade["RADVAL_"+partid]; 2728: var override = document.classgrade["FORCE_"+partid].checked; 2729: var textbox = document.classgrade["TEXTVAL_"+partid]; 2730: if (selval[1].selected || selval[2].selected) { 2731: for (var i=0; i<radioButton.length; i++) { 2732: radioButton[i].checked=false; 2733: 2734: } 2735: textbox.value = ""; 2736: 2737: for (i=0;i<document.classgrade.total.value;i++) { 2738: var user = document.classgrade["ctr"+i].value; 2739: user = user.replace(new RegExp(':', 'g'),"_"); 2740: var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"]; 2741: var saveval = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value; 2742: var selname = document.classgrade["GD_"+user+"_"+partid+"_solved"]; 2743: if ((saveval != "correct") || override) { 2744: scorename.value = ""; 2745: if (selval[1].selected) { 2746: selname[1].selected = true; 2747: } else { 2748: selname[2].selected = true; 2749: if (Number(document.classgrade["GD_"+user+"_"+partid+"_tries"].value)) 2750: {document.classgrade["GD_"+user+"_"+partid+"_tries"].value = '0';} 2751: } 2752: } 2753: } 2754: } else { 2755: for (i=0;i<document.classgrade.total.value;i++) { 2756: var user = document.classgrade["ctr"+i].value; 2757: user = user.replace(new RegExp(':', 'g'),"_"); 2758: var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"]; 2759: var saveval = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value; 2760: var selname = document.classgrade["GD_"+user+"_"+partid+"_solved"]; 2761: if ((saveval != "correct") || override) { 2762: scorename.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value; 2763: selname[0].selected = true; 2764: } 2765: } 2766: } 2767: } 2768: 2769: function changeSelect(partid,user) { 2770: var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"]; 2771: var textbox = document.classgrade["GD_"+user+'_'+partid+"_awarded"]; 2772: var point = textbox.value; 2773: var weight = document.classgrade["weight_"+partid].value; 2774: 2775: if (isNaN(point) || parseFloat(point) < 0) { 2776: alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point)); 2777: textbox.value = ""; 2778: return; 2779: } 2780: if (parseFloat(point) > parseFloat(weight)) { 2781: var resp = confirm("You entered a value ("+parseFloat(point)+ 2782: ") greater than the weight of the part. Accept?"); 2783: if (resp == false) { 2784: textbox.value = ""; 2785: return; 2786: } 2787: } 2788: selval[0].selected = true; 2789: } 2790: 2791: function changeOneScore(partid,user) { 2792: var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"]; 2793: if (selval[1].selected || selval[2].selected) { 2794: document.classgrade["GD_"+user+'_'+partid+"_awarded"].value = ""; 2795: if (selval[2].selected) { 2796: document.classgrade["GD_"+user+'_'+partid+"_tries"].value = "0"; 2797: } 2798: } 2799: } 2800: 2801: function resetEntry(numpart) { 2802: for (ctpart=0;ctpart<numpart;ctpart++) { 2803: var partid = document.classgrade["partid_"+ctpart].value; 2804: var radioButton = document.classgrade["RADVAL_"+partid]; 2805: var textbox = document.classgrade["TEXTVAL_"+partid]; 2806: var selval = document.classgrade["SELVAL_"+partid]; 2807: for (var i=0; i<radioButton.length; i++) { 2808: radioButton[i].checked=false; 2809: 2810: } 2811: textbox.value = ""; 2812: selval[0].selected = true; 2813: 2814: for (i=0;i<document.classgrade.total.value;i++) { 2815: var user = document.classgrade["ctr"+i].value; 2816: user = user.replace(new RegExp(':', 'g'),"_"); 2817: var resetscore = document.classgrade["GD_"+user+"_"+partid+"_awarded"]; 2818: resetscore.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value; 2819: var resettries = document.classgrade["GD_"+user+"_"+partid+"_tries"]; 2820: resettries.value = document.classgrade["GD_"+user+"_"+partid+"_tries_s"].value; 2821: var saveselval = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value; 2822: var selname = document.classgrade["GD_"+user+"_"+partid+"_solved"]; 2823: if (saveselval == "excused") { 2824: if (selname[1].selected == false) { selname[1].selected = true;} 2825: } else { 2826: if (selname[0].selected == false) {selname[0].selected = true}; 2827: } 2828: } 2829: } 2830: } 2831: 2832: </script> 2833: VIEWJAVASCRIPT 2834: } 2835: 2836: #--- show scores for a section or whole class w/ option to change/update a score 2837: sub viewgrades { 2838: my ($request) = shift; 2839: &viewgrades_js($request); 2840: 2841: my ($symb) = &get_symb($request); 2842: #need to make sure we have the correct data for later EXT calls, 2843: #thus invalidate the cache 2844: &Apache::lonnet::devalidatecourseresdata( 2845: $env{'course.'.$env{'request.course.id'}.'.num'}, 2846: $env{'course.'.$env{'request.course.id'}.'.domain'}); 2847: &Apache::lonnet::clear_EXT_cache_status(); 2848: 2849: my $result='<h3><font color="#339933">'.&mt('Manual Grading').'</font></h3>'; 2850: $result.='<font size=+1><b>Current Resource: </b>'.$env{'form.probTitle'}.'</font>'."\n"; 2851: 2852: #view individual student submission form - called using Javascript viewOneStudent 2853: $result.=&jscriptNform($symb); 2854: 2855: #beginning of class grading form 2856: $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n". 2857: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 2858: '<input type="hidden" name="command" value="editgrades" />'."\n". 2859: '<input type="hidden" name="section" value="'.$env{'form.section'}.'" />'."\n". 2860: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n". 2861: '<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n". 2862: '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n"; 2863: 2864: my $sectionClass; 2865: if ($env{'form.section'} eq 'all') { 2866: $sectionClass='Class </h3>'; 2867: } elsif ($env{'form.section'} eq 'none') { 2868: $sectionClass='Students in no Section </h3>'; 2869: } else { 2870: $sectionClass='Students in Section '.$env{'form.section'}.'</h3>'; 2871: } 2872: $result.='<h3>Assign Common Grade To '.$sectionClass; 2873: $result.= '<table border=0><tr><td bgcolor="#777777">'."\n". 2874: '<table border=0><tr bgcolor="#ffffdd"><td>'; 2875: #radio buttons/text box for assigning points for a section or class. 2876: #handles different parts of a problem 2877: my ($partlist,$handgrade) = &response_type($symb); 2878: my %weight = (); 2879: my $ctsparts = 0; 2880: $result.='<table border="0">'; 2881: my %seen = (); 2882: for (sort keys(%$handgrade)) { 2883: my ($partid,$respid) = split (/_/,$_,2); 2884: next if $seen{$partid}; 2885: $seen{$partid}++; 2886: my $handgrade=$$handgrade{$_}; 2887: my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb); 2888: $weight{$partid} = $wgt eq '' ? '1' : $wgt; 2889: 2890: $result.='<input type="hidden" name="partid_'. 2891: $ctsparts.'" value="'.$partid.'" />'."\n"; 2892: $result.='<input type="hidden" name="weight_'. 2893: $partid.'" value="'.$weight{$partid}.'" />'."\n"; 2894: my $display_part=&get_display_part($partid,$symb); 2895: $result.='<tr><td><b>Part:</b> '.$display_part.' <b>Point:</b> </td><td>'; 2896: $result.='<table border="0"><tr>'; 2897: my $ctr = 0; 2898: while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across 2899: $result.= '<td><label><input type="radio" name="RADVAL_'.$partid.'" '. 2900: 'onclick="javascript:writePoint(\''.$partid.'\','.$weight{$partid}. 2901: ','.$ctr.')" />'.$ctr."</label></td>\n"; 2902: $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : ''); 2903: $ctr++; 2904: } 2905: $result.='</tr></table>'; 2906: $result.= '</td><td><b> or </b><input type="text" name="TEXTVAL_'. 2907: $partid.'" size="4" '.'onChange="javascript:writePoint(\''. 2908: $partid.'\','.$weight{$partid}.',\'textval\')" /> /'. 2909: $weight{$partid}.' (problem weight)</td>'."\n"; 2910: $result.= '</td><td><select name="SELVAL_'.$partid.'"'. 2911: 'onChange="javascript:writeRadText(\''.$partid.'\','. 2912: $weight{$partid}.')"> '. 2913: '<option selected="on"> </option>'. 2914: '<option>excused</option>'. 2915: '<option>reset status</option></select></td>'. 2916: '<td><label><input type="checkbox" name="FORCE_'.$partid.'" /> Override "Correct"</label></td></tr>'."\n"; 2917: $ctsparts++; 2918: } 2919: $result.='</table>'.'</td></tr></table>'.'</td></tr></table>'."\n". 2920: '<input type="hidden" name="totalparts" value="'.$ctsparts.'" />'; 2921: $result.='<input type="button" value="Reset" '. 2922: 'onClick="javascript:resetEntry('.$ctsparts.');" TARGET=_self>'; 2923: 2924: #table listing all the students in a section/class 2925: #header of table 2926: $result.= '<h3>Assign Grade to Specific Students in '.$sectionClass; 2927: $result.= '<table border=0><tr><td bgcolor="#777777">'."\n". 2928: '<table border=0><tr bgcolor="#deffff"><td> <b>No.</b> </td>'. 2929: '<td>'.&nameUserString('header')."</td>\n"; 2930: my (@parts) = sort(&getpartlist($symb)); 2931: my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb); 2932: my @partids = (); 2933: foreach my $part (@parts) { 2934: my $display=&Apache::lonnet::metadata($url,$part.'.display'); 2935: $display =~ s|^Number of Attempts|Tries<br />|; # makes the column narrower 2936: if (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); } 2937: my ($partid) = &split_part_type($part); 2938: push(@partids, $partid); 2939: my $display_part=&get_display_part($partid,$symb); 2940: if ($display =~ /^Partial Credit Factor/) { 2941: $result.='<td><b>Score Part:</b> '.$display_part. 2942: ' <br /><b>(weight = '.$weight{$partid}.')</b></td>'."\n"; 2943: next; 2944: } else { 2945: $display =~s/\[Part: \Q$partid\E\]/Part:<\/b> $display_part/; 2946: } 2947: $display =~ s|Problem Status|Grade Status<br />|; 2948: $result.='<td><b>'.$display.'</td>'."\n"; 2949: } 2950: $result.='</tr>'; 2951: 2952: my %last_resets = 2953: &get_last_resets($symb,$env{'request.course.id'},\@partids); 2954: 2955: #get info for each student 2956: #list all the students - with points and grade status 2957: my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1'); 2958: my $ctr = 0; 2959: foreach (sort 2960: { 2961: if (lc($$fullname{$a}) ne lc($$fullname{$b})) { 2962: return (lc($$fullname{$a}) cmp lc($$fullname{$b})); 2963: } 2964: return $a cmp $b; 2965: } (keys(%$fullname))) { 2966: $ctr++; 2967: $result.=&viewstudentgrade($symb,$env{'request.course.id'}, 2968: $_,$$fullname{$_},\@parts,\%weight,$ctr,\%last_resets); 2969: } 2970: $result.='</table></td></tr></table>'; 2971: $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n"; 2972: $result.='<input type="button" value="Save" '. 2973: 'onClick="javascript:submit();" TARGET=_self /></form>'."\n"; 2974: if (scalar(%$fullname) eq 0) { 2975: my $colspan=3+scalar(@parts); 2976: $result='<font color="red">There are no students in section "'.$env{'form.section'}. 2977: '" with enrollment status "'.$env{'form.Status'}.'" to modify or grade.</font>'; 2978: } 2979: $result.=&show_grading_menu_form($symb); 2980: return $result; 2981: } 2982: 2983: #--- call by previous routine to display each student 2984: sub viewstudentgrade { 2985: my ($symb,$courseid,$student,$fullname,$parts,$weight,$ctr,$last_resets) = @_; 2986: my ($uname,$udom) = split(/:/,$student); 2987: my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname); 2988: my %aggregates = (); 2989: my $result='<tr bgcolor="#ffffdd"><td align="right">'. 2990: '<input type="hidden" name="ctr'.($ctr-1).'" value="'.$student.'" />'. 2991: "\n".$ctr.' </td><td> '. 2992: '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom. 2993: '\')"; TARGET=_self>'.$fullname.'</a> '. 2994: '<font color="#999999">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</font></td>'."\n"; 2995: $student=~s/:/_/; # colon doen't work in javascript for names 2996: foreach my $apart (@$parts) { 2997: my ($part,$type) = &split_part_type($apart); 2998: my $score=$record{"resource.$part.$type"}; 2999: $result.='<td align="center">'; 3000: my ($aggtries,$totaltries); 3001: unless (exists($aggregates{$part})) { 3002: $totaltries = $record{'resource.'.$part.'.tries'}; 3003: 3004: $aggtries = $totaltries; 3005: if ($$last_resets{$part}) { 3006: $aggtries = &get_num_tries(\%record,$$last_resets{$part}, 3007: $part); 3008: } 3009: $result.='<input type="hidden" name="'. 3010: 'GD_'.$student.'_'.$part.'_aggtries" value="'.$aggtries.'" />'."\n"; 3011: $result.='<input type="hidden" name="'. 3012: 'GD_'.$student.'_'.$part.'_totaltries" value="'.$totaltries.'" />'."\n"; 3013: $aggregates{$part} = 1; 3014: } 3015: if ($type eq 'awarded') { 3016: my $pts = $score eq '' ? '' : &compute_points($score,$$weight{$part}); 3017: $result.='<input type="hidden" name="'. 3018: 'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n"; 3019: $result.='<input type="text" name="'. 3020: 'GD_'.$student.'_'.$part.'_awarded" '. 3021: 'onChange="javascript:changeSelect(\''.$part.'\',\''.$student. 3022: '\')" value="'.$pts.'" size="4" /></td>'."\n"; 3023: } elsif ($type eq 'solved') { 3024: my ($status,$foo)=split(/_/,$score,2); 3025: $status = 'nothing' if ($status eq ''); 3026: $result.='<input type="hidden" name="'.'GD_'.$student.'_'. 3027: $part.'_solved_s" value="'.$status.'" />'."\n"; 3028: $result.=' <select name="'. 3029: 'GD_'.$student.'_'.$part.'_solved" '. 3030: 'onChange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n"; 3031: $result.= (($status eq 'excused') ? '<option> </option><option selected="on">excused</option>' 3032: : '<option selected="on"> </option><option>excused</option>')."\n"; 3033: $result.='<option>reset status</option>'; 3034: $result.="</select> </td>\n"; 3035: } else { 3036: $result.='<input type="hidden" name="'. 3037: 'GD_'.$student.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'. 3038: "\n"; 3039: $result.='<input type="text" name="'. 3040: 'GD_'.$student.'_'.$part.'_'.$type.'" '. 3041: 'value="'.$score.'" size="4" /></td>'."\n"; 3042: } 3043: } 3044: $result.='</tr>'; 3045: return $result; 3046: } 3047: 3048: #--- change scores for all the students in a section/class 3049: # record does not get update if unchanged 3050: sub editgrades { 3051: my ($request) = @_; 3052: 3053: my $symb=&get_symb($request); 3054: my $title='<h3><font color="#339933">Current Grade Status</font></h3>'; 3055: $title.='<font size=+1><b>Current Resource: </b>'.$env{'form.probTitle'}.'</font><br />'."\n"; 3056: $title.='<font size=+1><b>Section: </b>'.$env{'form.section'}.'</font>'."\n"; 3057: 3058: my $result= '<table border="0"><tr><td bgcolor="#777777">'."\n"; 3059: $result.= '<table border="0"><tr bgcolor="#deffff">'. 3060: '<td rowspan=2 valign="center"> <b>No.</b> </td>'. 3061: '<td rowspan=2 valign="center">'.&nameUserString('header')."</td>\n"; 3062: 3063: my %scoreptr = ( 3064: 'correct' =>'correct_by_override', 3065: 'incorrect'=>'incorrect_by_override', 3066: 'excused' =>'excused', 3067: 'ungraded' =>'ungraded_attempted', 3068: 'nothing' => '', 3069: ); 3070: my ($classlist,undef,$fullname) = &getclasslist($env{'form.section'},'0'); 3071: 3072: my (@partid); 3073: my %weight = (); 3074: my %columns = (); 3075: my ($i,$ctr,$count,$rec_update) = (0,0,0,0); 3076: 3077: my (@parts) = sort(&getpartlist($symb)); 3078: my $header; 3079: while ($ctr < $env{'form.totalparts'}) { 3080: my $partid = $env{'form.partid_'.$ctr}; 3081: push @partid,$partid; 3082: $weight{$partid} = $env{'form.weight_'.$partid}; 3083: $ctr++; 3084: } 3085: my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb); 3086: foreach my $partid (@partid) { 3087: $header .= '<td align="center"> <b>Old Score</b> </td>'. 3088: '<td align="center"> <b>New Score</b> </td>'; 3089: $columns{$partid}=2; 3090: foreach my $stores (@parts) { 3091: my ($part,$type) = &split_part_type($stores); 3092: if ($part !~ m/^\Q$partid\E/) { next;} 3093: if ($type eq 'awarded' || $type eq 'solved') { next; } 3094: my $display=&Apache::lonnet::metadata($url,$stores.'.display'); 3095: $display =~ s/\[Part: (\w)+\]//; 3096: $display =~ s/Number of Attempts/Tries/; 3097: $header .= '<td align="center"> <b>Old '.$display.'</b> </td>'. 3098: '<td align="center"> <b>New '.$display.'</b> </td>'; 3099: $columns{$partid}+=2; 3100: } 3101: } 3102: foreach my $partid (@partid) { 3103: my $display_part=&get_display_part($partid,$symb); 3104: $result .= '<td colspan="'.$columns{$partid}. 3105: '" align="center"><b>Part:</b> '.$display_part. 3106: ' (Weight = '.$weight{$partid}.')</td>'; 3107: 3108: } 3109: $result .= '</tr><tr bgcolor="#deffff">'; 3110: $result .= $header; 3111: $result .= '</tr>'."\n"; 3112: my $noupdate; 3113: my ($updateCtr,$noupdateCtr) = (1,1); 3114: for ($i=0; $i<$env{'form.total'}; $i++) { 3115: my $line; 3116: my $user = $env{'form.ctr'.$i}; 3117: my ($uname,$udom)=split(/:/,$user); 3118: my %newrecord; 3119: my $updateflag = 0; 3120: $line .= '<td>'.&nameUserString(undef,$$fullname{$user},$uname,$udom).'</td>'; 3121: my $usec=$classlist->{"$uname:$udom"}[5]; 3122: if (!&canmodify($usec)) { 3123: my $numcols=scalar(@partid)*4+2; 3124: $noupdate.=$line."<td colspan=\"$numcols\"><font color=\"red\">Not allowed to modify student</font></td></tr>"; 3125: next; 3126: } 3127: my %aggregate = (); 3128: my $aggregateflag = 0; 3129: $user=~s/:/_/; # colon doen't work in javascript for names 3130: foreach (@partid) { 3131: my $old_aw = $env{'form.GD_'.$user.'_'.$_.'_awarded_s'}; 3132: my $old_part_pcr = $old_aw/($weight{$_} ne '0' ? $weight{$_}:1); 3133: my $old_part = $old_aw eq '' ? '' : $old_part_pcr; 3134: my $old_score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}}; 3135: my $awarded = $env{'form.GD_'.$user.'_'.$_.'_awarded'}; 3136: my $pcr = $awarded/($weight{$_} ne '0' ? $weight{$_} : 1); 3137: my $partial = $awarded eq '' ? '' : $pcr; 3138: my $score; 3139: if ($partial eq '') { 3140: $score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}}; 3141: } elsif ($partial > 0) { 3142: $score = 'correct_by_override'; 3143: } elsif ($partial == 0) { 3144: $score = 'incorrect_by_override'; 3145: } 3146: my $dropMenu = $env{'form.GD_'.$user.'_'.$_.'_solved'}; 3147: $score = 'excused' if (($dropMenu eq 'excused') && ($score ne 'excused')); 3148: 3149: $newrecord{'resource.'.$_.'.regrader'}= 3150: "$env{'user.name'}:$env{'user.domain'}"; 3151: if ($dropMenu eq 'reset status' && 3152: $old_score ne '') { # ignore if no previous attempts => nothing to reset 3153: $newrecord{'resource.'.$_.'.tries'} = ''; 3154: $newrecord{'resource.'.$_.'.solved'} = ''; 3155: $newrecord{'resource.'.$_.'.award'} = ''; 3156: $newrecord{'resource.'.$_.'.awarded'} = ''; 3157: $updateflag = 1; 3158: if ($env{'form.GD_'.$user.'_'.$_.'_aggtries'} > 0) { 3159: my $aggtries = $env{'form.GD_'.$user.'_'.$_.'_aggtries'}; 3160: my $totaltries = $env{'form.GD_'.$user.'_'.$_.'_totaltries'}; 3161: my $solvedstatus = $env{'form.GD_'.$user.'_'.$_.'_solved_s'}; 3162: &decrement_aggs($symb,$_,\%aggregate,$aggtries,$totaltries,$solvedstatus); 3163: $aggregateflag = 1; 3164: } 3165: } elsif (!($old_part eq $partial && $old_score eq $score)) { 3166: $updateflag = 1; 3167: $newrecord{'resource.'.$_.'.awarded'} = $partial if $partial ne ''; 3168: $newrecord{'resource.'.$_.'.solved'} = $score; 3169: $rec_update++; 3170: } 3171: 3172: $line .= '<td align="center">'.$old_aw.' </td>'. 3173: '<td align="center">'.$awarded. 3174: ($score eq 'excused' ? $score : '').' </td>'; 3175: 3176: 3177: my $partid=$_; 3178: foreach my $stores (@parts) { 3179: my ($part,$type) = &split_part_type($stores); 3180: if ($part !~ m/^\Q$partid\E/) { next;} 3181: if ($type eq 'awarded' || $type eq 'solved') { next; } 3182: my $old_aw = $env{'form.GD_'.$user.'_'.$part.'_'.$type.'_s'}; 3183: my $awarded = $env{'form.GD_'.$user.'_'.$part.'_'.$type}; 3184: if ($awarded ne '' && $awarded ne $old_aw) { 3185: $newrecord{'resource.'.$part.'.'.$type}= $awarded; 3186: $newrecord{'resource.'.$part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}"; 3187: $updateflag=1; 3188: } 3189: $line .= '<td align="center">'.$old_aw.' </td>'. 3190: '<td align="center">'.$awarded.' </td>'; 3191: } 3192: } 3193: $line.='</tr>'."\n"; 3194: 3195: my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; 3196: my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; 3197: 3198: if ($updateflag) { 3199: $count++; 3200: &Apache::lonnet::cstore(\%newrecord,$symb,$env{'request.course.id'}, 3201: $udom,$uname); 3202: 3203: if (&Apache::bridgetask::in_queue('gradingqueue',$symb,$cdom, 3204: $cnum,$udom,$uname)) { 3205: # need to figure out if should be in queue. 3206: my %record = 3207: &Apache::lonnet::restore($symb,$env{'request.course.id'}, 3208: $udom,$uname); 3209: my $all_graded = 1; 3210: my $none_graded = 1; 3211: foreach my $part (@parts) { 3212: if ( $record{'resource.'.$part.'.awarded'} eq '' ) { 3213: $all_graded = 0; 3214: } else { 3215: $none_graded = 0; 3216: } 3217: } 3218: 3219: if ($all_graded || $none_graded) { 3220: &Apache::bridgetask::remove_from_queue('gradingqueue', 3221: $symb,$cdom,$cnum, 3222: $udom,$uname); 3223: } 3224: } 3225: 3226: $result.='<tr bgcolor="#ffffde"><td align="right"> '.$updateCtr.' </td>'.$line; 3227: $updateCtr++; 3228: } else { 3229: $noupdate.='<tr bgcolor="#ffffde"><td align="right"> '.$noupdateCtr.' </td>'.$line; 3230: $noupdateCtr++; 3231: } 3232: if ($aggregateflag) { 3233: &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate, 3234: $cdom,$cnum); 3235: } 3236: } 3237: if ($noupdate) { 3238: # my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3; 3239: my $numcols=scalar(@partid)*4+2; 3240: $result .= '<tr bgcolor="#ffffff"><td align="center" colspan="'.$numcols.'">No Changes Occurred For the Students Below</td></tr><tr bgcolor="#ffffde">'.$noupdate; 3241: } 3242: $result .= '</table></td></tr></table>'."\n". 3243: &show_grading_menu_form ($symb); 3244: my $msg = '<br /><b>Number of records updated = '.$rec_update. 3245: ' for '.$count.' student'.($count <= 1 ? '' : 's').'.</b><br />'. 3246: '<b>Total number of students = '.$env{'form.total'}.'</b><br />'; 3247: return $title.$msg.$result; 3248: } 3249: 3250: sub split_part_type { 3251: my ($partstr) = @_; 3252: my ($temp,@allparts)=split(/_/,$partstr); 3253: my $type=pop(@allparts); 3254: my $part=join('.',@allparts); 3255: return ($part,$type); 3256: } 3257: 3258: #------------- end of section for handling grading by section/class --------- 3259: # 3260: #---------------------------------------------------------------------------- 3261: 3262: 3263: #---------------------------------------------------------------------------- 3264: # 3265: #-------------------------- Next few routines handles grading by csv upload 3266: # 3267: #--- Javascript to handle csv upload 3268: sub csvupload_javascript_reverse_associate { 3269: my $error1=&mt('You need to specify the username or ID'); 3270: my $error2=&mt('You need to specify at least one grading field'); 3271: return(<<ENDPICK); 3272: function verify(vf) { 3273: var foundsomething=0; 3274: var founduname=0; 3275: var foundID=0; 3276: for (i=0;i<=vf.nfields.value;i++) { 3277: tw=eval('vf.f'+i+'.selectedIndex'); 3278: if (i==0 && tw!=0) { foundID=1; } 3279: if (i==1 && tw!=0) { founduname=1; } 3280: if (i!=0 && i!=1 && i!=2 && tw!=0) { foundsomething=1; } 3281: } 3282: if (founduname==0 && foundID==0) { 3283: alert('$error1'); 3284: return; 3285: } 3286: if (foundsomething==0) { 3287: alert('$error2'); 3288: return; 3289: } 3290: vf.submit(); 3291: } 3292: function flip(vf,tf) { 3293: var nw=eval('vf.f'+tf+'.selectedIndex'); 3294: var i; 3295: for (i=0;i<=vf.nfields.value;i++) { 3296: //can not pick the same destination field for both name and domain 3297: if (((i ==0)||(i ==1)) && 3298: ((tf==0)||(tf==1)) && 3299: (i!=tf) && 3300: (eval('vf.f'+i+'.selectedIndex')==nw)) { 3301: eval('vf.f'+i+'.selectedIndex=0;') 3302: } 3303: } 3304: } 3305: ENDPICK 3306: } 3307: 3308: sub csvupload_javascript_forward_associate { 3309: my $error1=&mt('You need to specify the username or ID'); 3310: my $error2=&mt('You need to specify at least one grading field'); 3311: return(<<ENDPICK); 3312: function verify(vf) { 3313: var foundsomething=0; 3314: var founduname=0; 3315: var foundID=0; 3316: for (i=0;i<=vf.nfields.value;i++) { 3317: tw=eval('vf.f'+i+'.selectedIndex'); 3318: if (tw==1) { foundID=1; } 3319: if (tw==2) { founduname=1; } 3320: if (tw>3) { foundsomething=1; } 3321: } 3322: if (founduname==0 && foundID==0) { 3323: alert('$error1'); 3324: return; 3325: } 3326: if (foundsomething==0) { 3327: alert('$error2'); 3328: return; 3329: } 3330: vf.submit(); 3331: } 3332: function flip(vf,tf) { 3333: var nw=eval('vf.f'+tf+'.selectedIndex'); 3334: var i; 3335: //can not pick the same destination field twice 3336: for (i=0;i<=vf.nfields.value;i++) { 3337: if ((i!=tf) && (eval('vf.f'+i+'.selectedIndex')==nw)) { 3338: eval('vf.f'+i+'.selectedIndex=0;') 3339: } 3340: } 3341: } 3342: ENDPICK 3343: } 3344: 3345: sub csvuploadmap_header { 3346: my ($request,$symb,$datatoken,$distotal)= @_; 3347: my $javascript; 3348: if ($env{'form.upfile_associate'} eq 'reverse') { 3349: $javascript=&csvupload_javascript_reverse_associate(); 3350: } else { 3351: $javascript=&csvupload_javascript_forward_associate(); 3352: } 3353: 3354: my ($result) = &showResourceInfo($symb,$env{'form.probTitle'}); 3355: my $checked=(($env{'form.noFirstLine'})?' checked="checked"':''); 3356: my $ignore=&mt('Ignore First Line'); 3357: $request->print(<<ENDPICK); 3358: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload"> 3359: <h3><font color="#339933">Uploading Class Grades</font></h3> 3360: $result 3361: <hr /> 3362: <h3>Identify fields</h3> 3363: Total number of records found in file: $distotal <hr /> 3364: Enter as many fields as you can. The system will inform you and bring you back 3365: to this page if the data selected is insufficient to run your class.<hr /> 3366: <input type="button" value="Reverse Association" onClick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" /> 3367: <label><input type="checkbox" name="noFirstLine" $checked />$ignore</label> 3368: <input type="hidden" name="associate" value="" /> 3369: <input type="hidden" name="phase" value="three" /> 3370: <input type="hidden" name="datatoken" value="$datatoken" /> 3371: <input type="hidden" name="fileupload" value="$env{'form.fileupload'}" /> 3372: <input type="hidden" name="upfiletype" value="$env{'form.upfiletype'}" /> 3373: <input type="hidden" name="upfile_associate" 3374: value="$env{'form.upfile_associate'}" /> 3375: <input type="hidden" name="symb" value="$symb" /> 3376: <input type="hidden" name="saveState" value="$env{'form.saveState'}" /> 3377: <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" /> 3378: <input type="hidden" name="command" value="csvuploadoptions" /> 3379: <hr /> 3380: <script type="text/javascript" language="Javascript"> 3381: $javascript 3382: </script> 3383: ENDPICK 3384: return ''; 3385: 3386: } 3387: 3388: sub csvupload_fields { 3389: my ($symb) = @_; 3390: my (@parts) = &getpartlist($symb); 3391: my @fields=(['ID','Student ID'], 3392: ['username','Student Username'], 3393: ['domain','Student Domain']); 3394: my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb); 3395: foreach my $part (sort(@parts)) { 3396: my @datum; 3397: my $display=&Apache::lonnet::metadata($url,$part.'.display'); 3398: my $name=$part; 3399: if (!$display) { $display = $name; } 3400: @datum=($name,$display); 3401: if ($name=~/^stores_(.*)_awarded/) { 3402: push(@fields,['stores_'.$1.'_points',"Points [Part: $1]"]); 3403: } 3404: push(@fields,\@datum); 3405: } 3406: return (@fields); 3407: } 3408: 3409: sub csvuploadmap_footer { 3410: my ($request,$i,$keyfields) =@_; 3411: $request->print(<<ENDPICK); 3412: </table> 3413: <input type="hidden" name="nfields" value="$i" /> 3414: <input type="hidden" name="keyfields" value="$keyfields" /> 3415: <input type="button" onClick="javascript:verify(this.form)" value="Assign Grades" /><br /> 3416: </form> 3417: ENDPICK 3418: } 3419: 3420: sub checkforfile_js { 3421: my $result =<<CSVFORMJS; 3422: <script type="text/javascript" language="javascript"> 3423: function checkUpload(formname) { 3424: if (formname.upfile.value == "") { 3425: alert("Please use the browse button to select a file from your local directory."); 3426: return false; 3427: } 3428: formname.submit(); 3429: } 3430: </script> 3431: CSVFORMJS 3432: return $result; 3433: } 3434: 3435: sub upcsvScores_form { 3436: my ($request) = shift; 3437: my ($symb)=&get_symb($request); 3438: if (!$symb) {return '';} 3439: my $result=&checkforfile_js(); 3440: $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb); 3441: my ($table) = &showResourceInfo($symb,$env{'form.probTitle'}); 3442: $result.=$table; 3443: $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n"; 3444: $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n"; 3445: $result.=' <b>'.&mt('Specify a file containing the class scores for current resource'). 3446: '.</b></td></tr>'."\n"; 3447: $result.='<tr bgcolor=#ffffe6><td>'."\n"; 3448: my $upload=&mt("Upload Scores"); 3449: my $upfile_select=&Apache::loncommon::upfile_select_html(); 3450: my $ignore=&mt('Ignore First Line'); 3451: $result.=<<ENDUPFORM; 3452: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload"> 3453: <input type="hidden" name="symb" value="$symb" /> 3454: <input type="hidden" name="command" value="csvuploadmap" /> 3455: <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" /> 3456: <input type="hidden" name="saveState" value="$env{'form.saveState'}" /> 3457: $upfile_select 3458: <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="$upload" /> 3459: <label><input type="checkbox" name="noFirstLine" />$ignore</label> 3460: </form> 3461: ENDUPFORM 3462: $result.=&Apache::loncommon::help_open_topic("Course_Convert_To_CSV", 3463: &mt("How do I create a CSV file from a spreadsheet")) 3464: .'</td></tr></table>'."\n"; 3465: $result.='</td></tr></table><br /><br />'."\n"; 3466: $result.=&show_grading_menu_form($symb); 3467: return $result; 3468: } 3469: 3470: 3471: sub csvuploadmap { 3472: my ($request)= @_; 3473: my ($symb)=&get_symb($request); 3474: if (!$symb) {return '';} 3475: 3476: my $datatoken; 3477: if (!$env{'form.datatoken'}) { 3478: $datatoken=&Apache::loncommon::upfile_store($request); 3479: } else { 3480: $datatoken=$env{'form.datatoken'}; 3481: &Apache::loncommon::load_tmp_file($request); 3482: } 3483: my @records=&Apache::loncommon::upfile_record_sep(); 3484: if ($env{'form.noFirstLine'}) { shift(@records); } 3485: &csvuploadmap_header($request,$symb,$datatoken,$#records+1); 3486: my ($i,$keyfields); 3487: if (@records) { 3488: my @fields=&csvupload_fields($symb); 3489: 3490: if ($env{'form.upfile_associate'} eq 'reverse') { 3491: &Apache::loncommon::csv_print_samples($request,\@records); 3492: $i=&Apache::loncommon::csv_print_select_table($request,\@records, 3493: \@fields); 3494: foreach (@fields) { $keyfields.=$_->[0].','; } 3495: chop($keyfields); 3496: } else { 3497: unshift(@fields,['none','']); 3498: $i=&Apache::loncommon::csv_samples_select_table($request,\@records, 3499: \@fields); 3500: foreach my $rec (@records) { 3501: my %temp = &Apache::loncommon::record_sep($rec); 3502: if (%temp) { 3503: $keyfields=join(',',sort(keys(%temp))); 3504: last; 3505: } 3506: } 3507: } 3508: } 3509: &csvuploadmap_footer($request,$i,$keyfields); 3510: $request->print(&show_grading_menu_form($symb)); 3511: 3512: return ''; 3513: } 3514: 3515: sub csvuploadoptions { 3516: my ($request)= @_; 3517: my ($symb)=&get_symb($request); 3518: my $checked=(($env{'form.noFirstLine'})?'1':'0'); 3519: my $ignore=&mt('Ignore First Line'); 3520: $request->print(<<ENDPICK); 3521: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload"> 3522: <h3><font color="#339933">Uploading Class Grade Options</font></h3> 3523: <input type="hidden" name="command" value="csvuploadassign" /> 3524: <!-- 3525: <p> 3526: <label> 3527: <input type="checkbox" name="show_full_results" /> 3528: Show a table of all changes 3529: </label> 3530: </p> 3531: --> 3532: <p> 3533: <label> 3534: <input type="checkbox" name="overwite_scores" checked="checked" /> 3535: Overwrite any existing score 3536: </label> 3537: </p> 3538: ENDPICK 3539: my %fields=&get_fields(); 3540: if (!defined($fields{'domain'})) { 3541: my $domform = &Apache::loncommon::select_dom_form($env{'request.role.domain'},'default_domain'); 3542: $request->print("\n<p> Users are in domain: ".$domform."</p>\n"); 3543: } 3544: foreach my $key (sort(keys(%env))) { 3545: if ($key !~ /^form\.(.*)$/) { next; } 3546: my $cleankey=$1; 3547: if ($cleankey eq 'command') { next; } 3548: $request->print('<input type="hidden" name="'.$cleankey. 3549: '" value="'.$env{$key}.'" />'."\n"); 3550: } 3551: # FIXME do a check for any duplicated user ids... 3552: # FIXME do a check for any invalid user ids?... 3553: $request->print('<input type="submit" value="Assign Grades" /><br /> 3554: <hr /></form>'."\n"); 3555: $request->print(&show_grading_menu_form($symb)); 3556: return ''; 3557: } 3558: 3559: sub get_fields { 3560: my %fields; 3561: my @keyfields = split(/\,/,$env{'form.keyfields'}); 3562: for (my $i=0; $i<=$env{'form.nfields'}; $i++) { 3563: if ($env{'form.upfile_associate'} eq 'reverse') { 3564: if ($env{'form.f'.$i} ne 'none') { 3565: $fields{$keyfields[$i]}=$env{'form.f'.$i}; 3566: } 3567: } else { 3568: if ($env{'form.f'.$i} ne 'none') { 3569: $fields{$env{'form.f'.$i}}=$keyfields[$i]; 3570: } 3571: } 3572: } 3573: return %fields; 3574: } 3575: 3576: sub csvuploadassign { 3577: my ($request)= @_; 3578: my ($symb)=&get_symb($request); 3579: if (!$symb) {return '';} 3580: my $error_msg = ''; 3581: &Apache::loncommon::load_tmp_file($request); 3582: my @gradedata = &Apache::loncommon::upfile_record_sep(); 3583: if ($env{'form.noFirstLine'}) { shift(@gradedata); } 3584: my %fields=&get_fields(); 3585: $request->print('<h3>Assigning Grades</h3>'); 3586: my $courseid=$env{'request.course.id'}; 3587: my ($classlist) = &getclasslist('all',0); 3588: my @notallowed; 3589: my @skipped; 3590: my $countdone=0; 3591: foreach my $grade (@gradedata) { 3592: my %entries=&Apache::loncommon::record_sep($grade); 3593: my $domain; 3594: if ($entries{$fields{'domain'}}) { 3595: $domain=$entries{$fields{'domain'}}; 3596: } else { 3597: $domain=$env{'form.default_domain'}; 3598: } 3599: $domain=~s/\s//g; 3600: my $username=$entries{$fields{'username'}}; 3601: $username=~s/\s//g; 3602: if (!$username) { 3603: my $id=$entries{$fields{'ID'}}; 3604: $id=~s/\s//g; 3605: my %ids=&Apache::lonnet::idget($domain,$id); 3606: $username=$ids{$id}; 3607: } 3608: if (!exists($$classlist{"$username:$domain"})) { 3609: my $id=$entries{$fields{'ID'}}; 3610: $id=~s/\s//g; 3611: if ($id) { 3612: push(@skipped,"$id:$domain"); 3613: } else { 3614: push(@skipped,"$username:$domain"); 3615: } 3616: next; 3617: } 3618: my $usec=$classlist->{"$username:$domain"}[5]; 3619: if (!&canmodify($usec)) { 3620: push(@notallowed,"$username:$domain"); 3621: next; 3622: } 3623: my %points; 3624: my %grades; 3625: foreach my $dest (keys(%fields)) { 3626: if ($dest eq 'ID' || $dest eq 'username' || 3627: $dest eq 'domain') { next; } 3628: if ($entries{$fields{$dest}} =~ /^\s*$/) { next; } 3629: if ($dest=~/stores_(.*)_points/) { 3630: my $part=$1; 3631: my $wgt =&Apache::lonnet::EXT('resource.'.$part.'.weight', 3632: $symb,$domain,$username); 3633: if ($wgt) { 3634: $entries{$fields{$dest}}=~s/\s//g; 3635: my $pcr=$entries{$fields{$dest}} / $wgt; 3636: my $award='correct_by_override'; 3637: $grades{"resource.$part.awarded"}=$pcr; 3638: $grades{"resource.$part.solved"}=$award; 3639: $points{$part}=1; 3640: } else { 3641: $error_msg = "<br />" . 3642: &mt("Some point values were assigned" 3643: ." for problems with a weight " 3644: ."of zero. These values were " 3645: ."ignored."); 3646: } 3647: } else { 3648: if ($dest=~/stores_(.*)_awarded/) { if ($points{$1}) {next;} } 3649: if ($dest=~/stores_(.*)_solved/) { if ($points{$1}) {next;} } 3650: my $store_key=$dest; 3651: $store_key=~s/^stores/resource/; 3652: $store_key=~s/_/\./g; 3653: $grades{$store_key}=$entries{$fields{$dest}}; 3654: } 3655: } 3656: if (! %grades) { push(@skipped,"$username:$domain no data to store"); } 3657: $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}"; 3658: # &Apache::lonnet::logthis(" storing ".(join('-',%grades))); 3659: my $result=&Apache::lonnet::cstore(\%grades,$symb, 3660: $env{'request.course.id'}, 3661: $domain,$username); 3662: if ($result eq 'ok') { 3663: $request->print('.'); 3664: } else { 3665: $request->print("<p> 3666: <font color='red'> 3667: Failed to store student $username\@$domain. 3668: Message when trying to store was ($result) 3669: </font> 3670: </p>" ); 3671: } 3672: $request->rflush(); 3673: $countdone++; 3674: } 3675: $request->print("<br />Stored $countdone students\n"); 3676: if (@skipped) { 3677: $request->print('<p><font size="+1"><b>Skipped Students</b></font></p>'); 3678: foreach my $student (@skipped) { $request->print("$student<br />\n"); } 3679: } 3680: if (@notallowed) { 3681: $request->print('<p><font size="+1" color="red"><b>Students Not Allowed to Modify</b></font></p>'); 3682: foreach my $student (@notallowed) { $request->print("$student<br />\n"); } 3683: } 3684: $request->print("<br />\n"); 3685: $request->print(&show_grading_menu_form($symb)); 3686: return $error_msg; 3687: } 3688: #------------- end of section for handling csv file upload --------- 3689: # 3690: #------------------------------------------------------------------- 3691: # 3692: #-------------- Next few routines handle grading by page/sequence 3693: # 3694: #--- Select a page/sequence and a student to grade 3695: sub pickStudentPage { 3696: my ($request) = shift; 3697: 3698: $request->print(<<LISTJAVASCRIPT); 3699: <script type="text/javascript" language="javascript"> 3700: 3701: function checkPickOne(formname) { 3702: if (radioSelection(formname.student) == null) { 3703: alert("Please select the student you wish to grade."); 3704: return; 3705: } 3706: ptr = pullDownSelection(formname.selectpage); 3707: formname.page.value = formname["page"+ptr].value; 3708: formname.title.value = formname["title"+ptr].value; 3709: formname.submit(); 3710: } 3711: 3712: </script> 3713: LISTJAVASCRIPT 3714: &commonJSfunctions($request); 3715: my ($symb) = &get_symb($request); 3716: my $cdom = $env{"course.$env{'request.course.id'}.domain"}; 3717: my $cnum = $env{"course.$env{'request.course.id'}.num"}; 3718: my $getsec = $env{'form.section'} eq '' ? 'all' : $env{'form.section'}; 3719: 3720: my $result='<h3><font color="#339933"> '. 3721: 'Manual Grading by Page or Sequence</font></h3>'; 3722: 3723: $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n"; 3724: $result.=' <b>Problems from:</b> <select name="selectpage">'."\n"; 3725: my ($titles,$symbx) = &getSymbMap($request); 3726: my ($curpage) =&Apache::lonnet::decode_symb($symb); 3727: # my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb); 3728: # my $type=($curpage =~ /\.(page|sequence)/); 3729: my $ctr=0; 3730: foreach (@$titles) { 3731: my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/); 3732: $result.='<option value="'.$ctr.'" '. 3733: ($$symbx{$_} =~ /$curpage$/ ? 'selected="on"' : ''). 3734: '>'.$showtitle.'</option>'."\n"; 3735: $ctr++; 3736: } 3737: $result.= '</select>'."<br />\n"; 3738: $ctr=0; 3739: foreach (@$titles) { 3740: my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/); 3741: $result.='<input type="hidden" name="page'.$ctr.'" value="'.$$symbx{$_}.'" />'."\n"; 3742: $result.='<input type="hidden" name="title'.$ctr.'" value="'.$showtitle.'" />'."\n"; 3743: $ctr++; 3744: } 3745: $result.='<input type="hidden" name="page" />'."\n". 3746: '<input type="hidden" name="title" />'."\n"; 3747: 3748: $result.=' <b>View Problems Text: </b><label><input type="radio" name="vProb" value="no" checked="on" /> no </label>'."\n". 3749: '<label><input type="radio" name="vProb" value="yes" /> yes </label>'."<br />\n"; 3750: 3751: $result.=' <b>Submission Details: </b>'. 3752: '<label><input type="radio" name="lastSub" value="none" /> none</label>'."\n". 3753: '<label><input type="radio" name="lastSub" value="datesub" checked /> by dates and submissions</label>'."\n". 3754: '<label><input type="radio" name="lastSub" value="all" /> all details</label>'."\n"; 3755: 3756: $result.='<input type="hidden" name="section" value="'.$getsec.'" />'."\n". 3757: '<input type="hidden" name="Status" value="'.$env{'form.Status'}.'" />'."\n". 3758: '<input type="hidden" name="command" value="displayPage" />'."\n". 3759: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 3760: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n"; 3761: 3762: $result.=' <input type="button" '. 3763: 'onClick="javascript:checkPickOne(this.form);"value="Next->" /><br />'."\n"; 3764: 3765: $request->print($result); 3766: 3767: my $studentTable.=' <b>Select a student you wish to grade and then click on the Next button.</b><br />'. 3768: '<table border="0"><tr><td bgcolor="#777777">'. 3769: '<table border="0"><tr bgcolor="#e6ffff">'. 3770: '<td align="right"> <b>No.</b></td>'. 3771: '<td>'.&nameUserString('header').'</td>'. 3772: '<td align="right"> <b>No.</b></td>'. 3773: '<td>'.&nameUserString('header').'</td></tr>'; 3774: 3775: my (undef,undef,$fullname) = &getclasslist($getsec,'1'); 3776: my $ptr = 1; 3777: foreach my $student (sort 3778: { 3779: if (lc($$fullname{$a}) ne lc($$fullname{$b})) { 3780: return (lc($$fullname{$a}) cmp lc($$fullname{$b})); 3781: } 3782: return $a cmp $b; 3783: } (keys(%$fullname))) { 3784: my ($uname,$udom) = split(/:/,$student); 3785: $studentTable.=($ptr%2 == 1 ? '<tr bgcolor="#ffffe6">' : '</td>'); 3786: $studentTable.='<td align="right">'.$ptr.' </td>'; 3787: $studentTable.='<td> <label><input type="radio" name="student" value="'.$student.'" /> ' 3788: .&nameUserString(undef,$$fullname{$student},$uname,$udom)."</label>\n"; 3789: $studentTable.=($ptr%2 == 0 ? '</td></tr>' : ''); 3790: $ptr++; 3791: } 3792: $studentTable.='</td><td> </td><td> ' if ($ptr%2 == 0); 3793: $studentTable.='</td></tr></table></td></tr></table>'."\n"; 3794: $studentTable.='<input type="button" '. 3795: 'onClick="javascript:checkPickOne(this.form);"value="Next->" /></form>'."\n"; 3796: 3797: $studentTable.=&show_grading_menu_form($symb); 3798: $request->print($studentTable); 3799: 3800: return ''; 3801: } 3802: 3803: sub getSymbMap { 3804: my ($request) = @_; 3805: my $navmap = Apache::lonnavmaps::navmap->new(); 3806: 3807: my %symbx = (); 3808: my @titles = (); 3809: my $minder = 0; 3810: 3811: # Gather every sequence that has problems. 3812: my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); }, 3813: 1,0,1); 3814: for my $sequence ($navmap->getById('0.0'), @sequences) { 3815: if ($navmap->hasResource($sequence, sub { shift->is_problem(); }, 0) ) { 3816: my $title = $minder.'.'.$sequence->compTitle(); 3817: push @titles, $title; # minder in case two titles are identical 3818: $symbx{$title} = $sequence->symb(); 3819: $minder++; 3820: } 3821: } 3822: return \@titles,\%symbx; 3823: } 3824: 3825: # 3826: #--- Displays a page/sequence w/wo problems, w/wo submissions 3827: sub displayPage { 3828: my ($request) = shift; 3829: 3830: my ($symb) = &get_symb($request); 3831: my $cdom = $env{"course.$env{'request.course.id'}.domain"}; 3832: my $cnum = $env{"course.$env{'request.course.id'}.num"}; 3833: my $getsec = $env{'form.section'} eq '' ? 'all' : $env{'form.section'}; 3834: my $pageTitle = $env{'form.page'}; 3835: my ($classlist,undef,$fullname) = &getclasslist($getsec,'1'); 3836: my ($uname,$udom) = split(/:/,$env{'form.student'}); 3837: my $usec=$classlist->{$env{'form.student'}}[5]; 3838: 3839: #need to make sure we have the correct data for later EXT calls, 3840: #thus invalidate the cache 3841: &Apache::lonnet::devalidatecourseresdata( 3842: $env{'course.'.$env{'request.course.id'}.'.num'}, 3843: $env{'course.'.$env{'request.course.id'}.'.domain'}); 3844: &Apache::lonnet::clear_EXT_cache_status(); 3845: 3846: if (!&canview($usec)) { 3847: $request->print('<font color="red">Unable to view requested student.('.$env{'form.student'}.')</font>'); 3848: $request->print(&show_grading_menu_form($symb)); 3849: return; 3850: } 3851: my $result='<h3><font color="#339933"> '.$env{'form.title'}.'</font></h3>'; 3852: $result.='<h3> Student: '.&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom). 3853: '</h3>'."\n"; 3854: &sub_page_js($request); 3855: $request->print($result); 3856: 3857: my $navmap = Apache::lonnavmaps::navmap->new(); 3858: my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'}); 3859: my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps 3860: if (!$map) { 3861: $request->print('<font color="red">Unable to view requested sequence. ('.$resUrl.')</font>'); 3862: $request->print(&show_grading_menu_form($symb)); 3863: return; 3864: } 3865: my $iterator = $navmap->getIterator($map->map_start(), 3866: $map->map_finish()); 3867: 3868: my $studentTable='<form action="/adm/grades" method="post" name="gradePage">'."\n". 3869: '<input type="hidden" name="command" value="gradeByPage" />'."\n". 3870: '<input type="hidden" name="fullname" value="'.$$fullname{$env{'form.student'}}.'" />'."\n". 3871: '<input type="hidden" name="student" value="'.$env{'form.student'}.'" />'."\n". 3872: '<input type="hidden" name="page" value="'.$pageTitle.'" />'."\n". 3873: '<input type="hidden" name="title" value="'.$env{'form.title'}.'" />'."\n". 3874: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 3875: '<input type="hidden" name="overRideScore" value="no" />'."\n". 3876: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n"; 3877: 3878: my $checkIcon = '<img src="'.$request->dir_config('lonIconsURL'). 3879: '/check.gif" height="16" border="0" />'; 3880: 3881: $studentTable.=' <b>Note:</b> Problems graded correct by the computer are marked with a '.$checkIcon. 3882: ' symbol.'."\n". 3883: '<table border="0"><tr><td bgcolor="#777777">'. 3884: '<table border="0"><tr bgcolor="#e6ffff">'. 3885: '<td align="center"><b> Prob. </b></td>'. 3886: '<td><b> '.($env{'form.vProb'} eq 'no' ? 'Title' : 'Problem Text').'/Grade</b></td></tr>'; 3887: 3888: &Apache::lonxml::clear_problem_counter(); 3889: my ($depth,$question,$prob) = (1,1,1); 3890: $iterator->next(); # skip the first BEGIN_MAP 3891: my $curRes = $iterator->next(); # for "current resource" 3892: while ($depth > 0) { 3893: if($curRes == $iterator->BEGIN_MAP) { $depth++; } 3894: if($curRes == $iterator->END_MAP) { $depth--; } 3895: 3896: if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout) { 3897: my $parts = $curRes->parts(); 3898: my $title = $curRes->compTitle(); 3899: my $symbx = $curRes->symb(); 3900: $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$prob. 3901: (scalar(@{$parts}) == 1 ? '' : '<br />('.scalar(@{$parts}).' parts)').'</td>'; 3902: $studentTable.='<td valign="top">'; 3903: if ($env{'form.vProb'} eq 'yes' ) { 3904: $studentTable.=&show_problem($request,$symbx,$uname,$udom,1, 3905: undef,'both'); 3906: } else { 3907: my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'}); 3908: $companswer =~ s|<form(.*?)>||g; 3909: $companswer =~ s|</form>||g; 3910: # while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a> 3911: # $companswer =~ s/$1/ /ms; 3912: # $request->print('match='.$1."<br />\n"); 3913: # } 3914: # $companswer =~ s|<table border=\"1\">|<table border=\"0\">|g; 3915: $studentTable.=' <b>'.$title.'</b> <br /> <b>Correct answer:</b><br />'.$companswer; 3916: } 3917: 3918: my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname); 3919: 3920: if ($env{'form.lastSub'} eq 'datesub') { 3921: if ($record{'version'} eq '') { 3922: $studentTable.='<br /> <font color="red">No recorded submission for this problem</font><br />'; 3923: } else { 3924: my %responseType = (); 3925: foreach my $partid (@{$parts}) { 3926: my @responseIds =$curRes->responseIds($partid); 3927: my @responseType =$curRes->responseType($partid); 3928: my %responseIds; 3929: for (my $i=0;$i<=$#responseIds;$i++) { 3930: $responseIds{$responseIds[$i]}=$responseType[$i]; 3931: } 3932: $responseType{$partid} = \%responseIds; 3933: } 3934: $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom); 3935: 3936: } 3937: } elsif ($env{'form.lastSub'} eq 'all') { 3938: my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : ''); 3939: $studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom, 3940: $env{'request.course.id'}, 3941: '','.submission'); 3942: 3943: } 3944: if (&canmodify($usec)) { 3945: foreach my $partid (@{$parts}) { 3946: $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record); 3947: $studentTable.='<input type="hidden" name="q_'.$question.'" value="'.$partid.'" />'."\n"; 3948: $question++; 3949: } 3950: $prob++; 3951: } 3952: $studentTable.='</td></tr>'; 3953: 3954: } 3955: $curRes = $iterator->next(); 3956: } 3957: 3958: $studentTable.='</td></tr></table></td></tr></table>'."\n". 3959: '<input type="button" value="Save" '. 3960: 'onClick="javascript:checkSubmitPage(this.form,'.$question.');" TARGET=_self />'. 3961: '</form>'."\n"; 3962: $studentTable.=&show_grading_menu_form($symb); 3963: $request->print($studentTable); 3964: 3965: return ''; 3966: } 3967: 3968: sub displaySubByDates { 3969: my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_; 3970: my $isCODE=0; 3971: my $isTask = ($symb =~/\.task$/); 3972: if (exists($record->{'resource.CODE'})) { $isCODE=1; } 3973: my $studentTable='<table border="0" width="100%"><tr><td bgcolor="#777777">'. 3974: '<table border="0" width="100%"><tr bgcolor="#e6ffff">'. 3975: '<td><b>Date/Time</b></td>'. 3976: ($isCODE?'<td><b>CODE</b></td>':''). 3977: '<td><b>Submission</b></td>'. 3978: '<td><b>Status </b></td></tr>'; 3979: my ($version); 3980: my %mark; 3981: my %orders; 3982: $mark{'correct_by_student'} = $checkIcon; 3983: if (!exists($$record{'1:timestamp'})) { 3984: return '<br /> <font color="red">Nothing submitted - no attempts</font><br />'; 3985: } 3986: 3987: my $interaction; 3988: for ($version=1;$version<=$$record{'version'};$version++) { 3989: my $timestamp = scalar(localtime($$record{$version.':timestamp'})); 3990: if (exists($$record{$version.':resource.0.version'})) { 3991: $interaction = $$record{$version.':resource.0.version'}; 3992: } 3993: 3994: my $where = ($isTask ? "$version:resource.$interaction" 3995: : "$version:resource"); 3996: #&Apache::lonnet::logthis(" got $where"); 3997: $studentTable.='<tr bgcolor="#ffffff" valign="top"><td>'.$timestamp.'</td>'; 3998: if ($isCODE) { 3999: $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>'; 4000: } 4001: my @versionKeys = split(/\:/,$$record{$version.':keys'}); 4002: my @displaySub = (); 4003: foreach my $partid (@{$parts}) { 4004: my @matchKey = ($isTask ? sort(grep /^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys) 4005: : sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys)); 4006: 4007: 4008: # next if ($$record{"$version:resource.$partid.solved"} eq ''); 4009: my $display_part=&get_display_part($partid,$symb); 4010: foreach my $matchKey (@matchKey) { 4011: if (exists($$record{$version.':'.$matchKey}) && 4012: $$record{$version.':'.$matchKey} ne '') { 4013: 4014: my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/) 4015: : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/)); 4016: #&Apache::lonnet::logthis("match $matchKey $responseId (".$$record{$version.':'.$matchKey}); 4017: $displaySub[0].='<b>Part:</b> '.$display_part.' '; 4018: $displaySub[0].='<font color="#999999">(ID '. 4019: $responseId.')</font> <b>'; 4020: if ($$record{"$where.$partid.tries"} eq '') { 4021: $displaySub[0].='Trial not counted'; 4022: } else { 4023: $displaySub[0].='Trial '. 4024: $$record{"$where.$partid.tries"}; 4025: } 4026: my $responseType=($isTask ? 'Task' 4027: : $responseType->{$partid}->{$responseId}); 4028: if (!exists($orders{$partid})) { $orders{$partid}={}; } 4029: if (!exists($orders{$partid}->{$responseId})) { 4030: $orders{$partid}->{$responseId}= 4031: &get_order($partid,$responseId,$symb,$uname,$udom); 4032: } 4033: $displaySub[0].='</b> '. 4034: &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom).'<br />'; 4035: } 4036: } 4037: if (exists($$record{"$where.$partid.checkedin"})) { 4038: $displaySub[1].='Checked in by '. 4039: $$record{"$where.$partid.checkedin"}.' into slot '. 4040: $$record{"$where.$partid.checkedin.slot"}. 4041: '<br />'; 4042: } 4043: if (exists $$record{"$where.$partid.award"}) { 4044: $displaySub[1].='<b>Part:</b> '.$display_part.' '. 4045: lc($$record{"$where.$partid.award"}).' '. 4046: $mark{$$record{"$where.$partid.solved"}}. 4047: '<br />'; 4048: } 4049: if (exists $$record{"$where.$partid.regrader"}) { 4050: $displaySub[2].=$$record{"$where.$partid.regrader"}. 4051: ' (<b>'.&mt('Part').':</b> '.$display_part.')'; 4052: } elsif ($$record{"$version:resource.$partid.regrader"} =~ /\S/) { 4053: $displaySub[2].= 4054: $$record{"$version:resource.$partid.regrader"}. 4055: ' (<b>'.&mt('Part').':</b> '.$display_part.')'; 4056: } 4057: } 4058: # needed because old essay regrader has not parts info 4059: if (exists $$record{"$version:resource.regrader"}) { 4060: $displaySub[2].=$$record{"$version:resource.regrader"}; 4061: } 4062: $studentTable.='<td>'.$displaySub[0].' </td><td>'.$displaySub[1]; 4063: if ($displaySub[2]) { 4064: $studentTable.='Manually graded by '.$displaySub[2]; 4065: } 4066: $studentTable.=' </td></tr>'; 4067: 4068: } 4069: $studentTable.='</table></td></tr></table>'; 4070: return $studentTable; 4071: } 4072: 4073: sub updateGradeByPage { 4074: my ($request) = shift; 4075: 4076: my $cdom = $env{"course.$env{'request.course.id'}.domain"}; 4077: my $cnum = $env{"course.$env{'request.course.id'}.num"}; 4078: my $getsec = $env{'form.section'} eq '' ? 'all' : $env{'form.section'}; 4079: my $pageTitle = $env{'form.page'}; 4080: my ($classlist,undef,$fullname) = &getclasslist($getsec,'1'); 4081: my ($uname,$udom) = split(/:/,$env{'form.student'}); 4082: my $usec=$classlist->{$env{'form.student'}}[5]; 4083: if (!&canmodify($usec)) { 4084: $request->print('<font color="red">Unable to modify requested student.('.$env{'form.student'}.'</font>'); 4085: $request->print(&show_grading_menu_form($env{'form.symb'})); 4086: return; 4087: } 4088: my $result='<h3><font color="#339933"> '.$env{'form.title'}.'</font></h3>'; 4089: $result.='<h3> Student: '.&nameUserString(undef,$env{'form.fullname'},$uname,$udom). 4090: '</h3>'."\n"; 4091: 4092: $request->print($result); 4093: 4094: my $navmap = Apache::lonnavmaps::navmap->new(); 4095: my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'}); 4096: my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps 4097: if (!$map) { 4098: $request->print('<font color="red">Unable to grade requested sequence. ('.$resUrl.')</font>'); 4099: my ($symb)=&get_symb($request); 4100: $request->print(&show_grading_menu_form($symb)); 4101: return; 4102: } 4103: my $iterator = $navmap->getIterator($map->map_start(), 4104: $map->map_finish()); 4105: 4106: my $studentTable='<table border="0"><tr><td bgcolor="#777777">'. 4107: '<table border="0"><tr bgcolor="#e6ffff">'. 4108: '<td align="center"><b> Prob. </b></td>'. 4109: '<td><b> Title </b></td>'. 4110: '<td><b> Previous Score </b></td>'. 4111: '<td><b> New Score </b></td></tr>'; 4112: 4113: $iterator->next(); # skip the first BEGIN_MAP 4114: my $curRes = $iterator->next(); # for "current resource" 4115: my ($depth,$question,$prob,$changeflag)= (1,1,1,0); 4116: while ($depth > 0) { 4117: if($curRes == $iterator->BEGIN_MAP) { $depth++; } 4118: if($curRes == $iterator->END_MAP) { $depth--; } 4119: 4120: if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout) { 4121: my $parts = $curRes->parts(); 4122: my $title = $curRes->compTitle(); 4123: my $symbx = $curRes->symb(); 4124: $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$prob. 4125: (scalar(@{$parts}) == 1 ? '' : '<br />('.scalar(@{$parts}).' parts)').'</td>'; 4126: $studentTable.='<td valign="top"> <b>'.$title.'</b> </td>'; 4127: 4128: my %newrecord=(); 4129: my @displayPts=(); 4130: my %aggregate = (); 4131: my $aggregateflag = 0; 4132: foreach my $partid (@{$parts}) { 4133: my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid}; 4134: my $oldpts = $env{'form.oldpts'.$question.'_'.$partid}; 4135: 4136: my $wgt = $env{'form.WGT'.$question.'_'.$partid} != 0 ? 4137: $env{'form.WGT'.$question.'_'.$partid} : 1; 4138: my $partial = $newpts/$wgt; 4139: my $score; 4140: if ($partial > 0) { 4141: $score = 'correct_by_override'; 4142: } elsif ($newpts ne '') { #empty is taken as 0 4143: $score = 'incorrect_by_override'; 4144: } 4145: my $dropMenu = $env{'form.GD_SEL'.$question.'_'.$partid}; 4146: if ($dropMenu eq 'excused') { 4147: $partial = ''; 4148: $score = 'excused'; 4149: } elsif ($dropMenu eq 'reset status' 4150: && $env{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists 4151: $newrecord{'resource.'.$partid.'.tries'} = 0; 4152: $newrecord{'resource.'.$partid.'.solved'} = ''; 4153: $newrecord{'resource.'.$partid.'.award'} = ''; 4154: $newrecord{'resource.'.$partid.'.awarded'} = 0; 4155: $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}"; 4156: $changeflag++; 4157: $newpts = ''; 4158: 4159: my $aggtries = $env{'form.aggtries'.$question.'_'.$partid}; 4160: my $totaltries = $env{'form.totaltries'.$question.'_'.$partid}; 4161: my $solvedstatus = $env{'form.solved'.$question.'_'.$partid}; 4162: if ($aggtries > 0) { 4163: &decrement_aggs($symbx,$partid,\%aggregate,$aggtries,$totaltries,$solvedstatus); 4164: $aggregateflag = 1; 4165: } 4166: } 4167: my $display_part=&get_display_part($partid,$curRes->symb()); 4168: my $oldstatus = $env{'form.solved'.$question.'_'.$partid}; 4169: $displayPts[0].=' <b>Part:</b> '.$display_part.' = '. 4170: (($oldstatus eq 'excused') ? 'excused' : $oldpts). 4171: ' <br />'; 4172: $displayPts[1].=' <b>Part:</b> '.$display_part.' = '. 4173: (($score eq 'excused') ? 'excused' : $newpts). 4174: ' <br />'; 4175: 4176: $question++; 4177: next if ($dropMenu eq 'reset status' || ($newpts == $oldpts && $score ne 'excused')); 4178: 4179: $newrecord{'resource.'.$partid.'.awarded'} = $partial if $partial ne ''; 4180: $newrecord{'resource.'.$partid.'.solved'} = $score if $score ne ''; 4181: $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}" 4182: if (scalar(keys(%newrecord)) > 0); 4183: 4184: $changeflag++; 4185: } 4186: if (scalar(keys(%newrecord)) > 0) { 4187: &Apache::lonnet::cstore(\%newrecord,$symbx,$env{'request.course.id'}, 4188: $udom,$uname); 4189: } 4190: if ($aggregateflag) { 4191: &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate, 4192: $env{'course.'.$env{'request.course.id'}.'.domain'}, 4193: $env{'course.'.$env{'request.course.id'}.'.num'}); 4194: } 4195: 4196: $studentTable.='<td valign="top">'.$displayPts[0].'</td>'. 4197: '<td valign="top">'.$displayPts[1].'</td>'. 4198: '</tr>'; 4199: 4200: $prob++; 4201: } 4202: $curRes = $iterator->next(); 4203: } 4204: 4205: $studentTable.='</td></tr></table></td></tr></table>'; 4206: $studentTable.=&show_grading_menu_form($env{'form.symb'}); 4207: my $grademsg=($changeflag == 0 ? 'No score was changed or updated.' : 4208: 'The scores were changed for '. 4209: $changeflag.' problem'.($changeflag == 1 ? '.' : 's.')); 4210: $request->print($grademsg.$studentTable); 4211: 4212: return ''; 4213: } 4214: 4215: #-------- end of section for handling grading by page/sequence --------- 4216: # 4217: #------------------------------------------------------------------- 4218: 4219: #--------------------Scantron Grading----------------------------------- 4220: # 4221: #------ start of section for handling grading by page/sequence --------- 4222: 4223: sub defaultFormData { 4224: my ($symb)=@_; 4225: return ' 4226: <input type="hidden" name="symb" value="'.$symb.'" />'."\n". 4227: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n". 4228: '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n"; 4229: } 4230: 4231: sub getSequenceDropDown { 4232: my ($request,$symb)=@_; 4233: my $result='<select name="selectpage">'."\n"; 4234: my ($titles,$symbx) = &getSymbMap($request); 4235: my ($curpage)=&Apache::lonnet::decode_symb($symb); 4236: my $ctr=0; 4237: foreach (@$titles) { 4238: my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/); 4239: $result.='<option value="'.$$symbx{$_}.'" '. 4240: ($$symbx{$_} =~ /$curpage$/ ? 'selected="on"' : ''). 4241: '>'.$showtitle.'</option>'."\n"; 4242: $ctr++; 4243: } 4244: $result.= '</select>'; 4245: return $result; 4246: } 4247: 4248: sub scantron_filenames { 4249: my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'}; 4250: my $cname=$env{'course.'.$env{'request.course.id'}.'.num'}; 4251: my @files=&Apache::lonnet::dirlist('userfiles',$cdom,$cname, 4252: &propath($cdom,$cname)); 4253: my @possiblenames; 4254: foreach my $filename (sort(@files)) { 4255: ($filename)=split(/&/,$filename); 4256: if ($filename!~/^scantron_orig_/) { next ; } 4257: $filename=~s/^scantron_orig_//; 4258: push(@possiblenames,$filename); 4259: } 4260: return @possiblenames; 4261: } 4262: 4263: sub scantron_uploads { 4264: my ($file2grade) = @_; 4265: my $result= '<select name="scantron_selectfile">'; 4266: $result.="<option></option>"; 4267: foreach my $filename (sort(&scantron_filenames())) { 4268: $result.="<option".($filename eq $file2grade ? ' selected="on"':'').">$filename</option>\n"; 4269: } 4270: $result.="</select>"; 4271: return $result; 4272: } 4273: 4274: sub scantron_scantab { 4275: my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab'); 4276: my $result='<select name="scantron_format">'."\n"; 4277: $result.='<option></option>'."\n"; 4278: foreach my $line (<$fh>) { 4279: my ($name,$descrip)=split(/:/,$line); 4280: if ($name =~ /^\#/) { next; } 4281: $result.='<option value="'.$name.'">'.$descrip.'</option>'."\n"; 4282: } 4283: $result.='</select>'."\n"; 4284: 4285: return $result; 4286: } 4287: 4288: sub scantron_CODElist { 4289: my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; 4290: my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; 4291: my @names=&Apache::lonnet::getkeys('CODEs',$cdom,$cnum); 4292: my $namechoice='<option></option>'; 4293: foreach my $name (sort {uc($a) cmp uc($b)} @names) { 4294: if ($name =~ /^error: 2 /) { next; } 4295: if ($name =~ /^type\0/) { next; } 4296: $namechoice.='<option value="'.$name.'">'.$name.'</option>'; 4297: } 4298: $namechoice='<select name="scantron_CODElist">'.$namechoice.'</select>'; 4299: return $namechoice; 4300: } 4301: 4302: sub scantron_CODEunique { 4303: my $result='<nobr> 4304: <label><input type="radio" name="scantron_CODEunique" 4305: value="yes" checked="checked" /> Yes </label> 4306: </nobr> 4307: <nobr> 4308: <label><input type="radio" name="scantron_CODEunique" 4309: value="no" /> No </label> 4310: </nobr>'; 4311: return $result; 4312: } 4313: 4314: sub scantron_selectphase { 4315: my ($r,$file2grade) = @_; 4316: my ($symb)=&get_symb($r); 4317: if (!$symb) {return '';} 4318: my $sequence_selector=&getSequenceDropDown($r,$symb); 4319: my $default_form_data=&defaultFormData($symb); 4320: my $grading_menu_button=&show_grading_menu_form($symb); 4321: my $file_selector=&scantron_uploads($file2grade); 4322: my $format_selector=&scantron_scantab(); 4323: my $CODE_selector=&scantron_CODElist(); 4324: my $CODE_unique=&scantron_CODEunique(); 4325: my $result; 4326: #FIXME allow instructor to be able to download the scantron file 4327: # and to upload it, 4328: $result.= <<SCANTRONFORM; 4329: <table width="100%" border="0"> 4330: <tr> 4331: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process"> 4332: <td bgcolor="#777777"> 4333: <input type="hidden" name="command" value="scantron_warning" /> 4334: $default_form_data 4335: <table width="100%" border="0"> 4336: <tr bgcolor="#e6ffff"> 4337: <td colspan="2"> 4338: <b>Specify file and which Folder/Sequence to grade</b> 4339: </td> 4340: </tr> 4341: <tr bgcolor="#ffffe6"> 4342: <td> Sequence to grade: </td><td> $sequence_selector </td> 4343: </tr> 4344: <tr bgcolor="#ffffe6"> 4345: <td> Filename of scoring office file: </td><td> $file_selector </td> 4346: </tr> 4347: <tr bgcolor="#ffffe6"> 4348: <td> Format of data file: </td><td> $format_selector </td> 4349: </tr> 4350: <tr bgcolor="#ffffe6"> 4351: <td> Saved CODEs to validate against: </td><td> $CODE_selector</td> 4352: </tr> 4353: <tr bgcolor="#ffffe6"> 4354: <td> Each CODE is only to be used once:</td><td> $CODE_unique </td> 4355: </tr> 4356: <tr bgcolor="#ffffe6"> 4357: <td> Options: </td> 4358: <td> 4359: <label><input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> Do only previously skipped records</label> <br /> 4360: <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> Remove all exisiting corrections</label> <br /> 4361: <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> Skip hidden resources when grading</label> 4362: </td> 4363: </tr> 4364: <tr bgcolor="#ffffe6"> 4365: <td colspan="2"> 4366: <input type="submit" value="Grading: Validate Scantron Records" /> 4367: </td> 4368: </tr> 4369: </table> 4370: </td> 4371: </form> 4372: </tr> 4373: SCANTRONFORM 4374: 4375: $r->print($result); 4376: 4377: if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) || 4378: &Apache::lonnet::allowed('usc',$env{'request.course.id'})) { 4379: 4380: $r->print(<<SCANTRONFORM); 4381: <tr> 4382: <td bgcolor="#777777"> 4383: <table width="100%" border="0"> 4384: <tr bgcolor="#e6ffff"> 4385: <td> 4386: <b>Specify a Scantron data file to upload.</b> 4387: </td> 4388: </tr> 4389: <tr bgcolor="#ffffe6"> 4390: <td> 4391: SCANTRONFORM 4392: my $default_form_data=&defaultFormData(&get_symb($r,1)); 4393: my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'}; 4394: my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'}; 4395: $r->print(<<UPLOAD); 4396: <script type="text/javascript" language="javascript"> 4397: function checkUpload(formname) { 4398: if (formname.upfile.value == "") { 4399: alert("Please use the browse button to select a file from your local directory."); 4400: return false; 4401: } 4402: formname.submit(); 4403: } 4404: </script> 4405: 4406: <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'> 4407: $default_form_data 4408: <input name='courseid' type='hidden' value='$cnum' /> 4409: <input name='domainid' type='hidden' value='$cdom' /> 4410: <input name='command' value='scantronupload_save' type='hidden' /> 4411: File to upload:<input type="file" name="upfile" size="50" /> 4412: <br /> 4413: <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" /> 4414: </form> 4415: UPLOAD 4416: 4417: $r->print(<<SCANTRONFORM); 4418: </td> 4419: </tr> 4420: </table> 4421: </td> 4422: </tr> 4423: SCANTRONFORM 4424: } 4425: $r->print(<<SCANTRONFORM); 4426: <tr> 4427: <form action='/adm/grades' name='scantron_download'> 4428: <td bgcolor="#777777"> 4429: <input type="hidden" name="command" value="scantron_download" /> 4430: <table width="100%" border="0"> 4431: <tr bgcolor="#e6ffff"> 4432: <td colspan="2"> 4433: <b>Download a scoring office file</b> 4434: </td> 4435: </tr> 4436: <tr bgcolor="#ffffe6"> 4437: <td> Filename of scoring office file: </td><td> $file_selector </td> 4438: </tr> 4439: <tr bgcolor="#ffffe6"> 4440: <td colspan="2"> 4441: <input type="submit" value="Download: Show List of Associated Files" /> 4442: </td> 4443: </tr> 4444: </table> 4445: </td> 4446: </form> 4447: </tr> 4448: SCANTRONFORM 4449: 4450: $r->print(<<SCANTRONFORM); 4451: </table> 4452: $grading_menu_button 4453: SCANTRONFORM 4454: 4455: return 4456: } 4457: 4458: sub get_scantron_config { 4459: my ($which) = @_; 4460: my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab'); 4461: my %config; 4462: #FIXME probably should move to XML it has already gotten a bit much now 4463: foreach my $line (<$fh>) { 4464: my ($name,$descrip)=split(/:/,$line); 4465: if ($name ne $which ) { next; } 4466: chomp($line); 4467: my @config=split(/:/,$line); 4468: $config{'name'}=$config[0]; 4469: $config{'description'}=$config[1]; 4470: $config{'CODElocation'}=$config[2]; 4471: $config{'CODEstart'}=$config[3]; 4472: $config{'CODElength'}=$config[4]; 4473: $config{'IDstart'}=$config[5]; 4474: $config{'IDlength'}=$config[6]; 4475: $config{'Qstart'}=$config[7]; 4476: $config{'Qlength'}=$config[8]; 4477: $config{'Qoff'}=$config[9]; 4478: $config{'Qon'}=$config[10]; 4479: $config{'PaperID'}=$config[11]; 4480: $config{'PaperIDlength'}=$config[12]; 4481: $config{'FirstName'}=$config[13]; 4482: $config{'FirstNamelength'}=$config[14]; 4483: $config{'LastName'}=$config[15]; 4484: $config{'LastNamelength'}=$config[16]; 4485: last; 4486: } 4487: return %config; 4488: } 4489: 4490: sub username_to_idmap { 4491: my ($classlist)= @_; 4492: my %idmap; 4493: foreach my $student (keys(%$classlist)) { 4494: $idmap{$classlist->{$student}->[&Apache::loncoursedata::CL_ID]}= 4495: $student; 4496: } 4497: return %idmap; 4498: } 4499: 4500: sub scantron_fixup_scanline { 4501: my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_; 4502: if ($field eq 'ID') { 4503: if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) { 4504: return ($line,1,'New value too large'); 4505: } 4506: if (length($args->{'newid'}) < $$scantron_config{'IDlength'}) { 4507: $args->{'newid'}=sprintf('%-'.$$scantron_config{'IDlength'}.'s', 4508: $args->{'newid'}); 4509: } 4510: substr($line,$$scantron_config{'IDstart'}-1, 4511: $$scantron_config{'IDlength'})=$args->{'newid'}; 4512: if ($args->{'newid'}=~/^\s*$/) { 4513: &scan_data($scan_data,"$whichline.user", 4514: $args->{'username'}.':'.$args->{'domain'}); 4515: } 4516: } elsif ($field eq 'CODE') { 4517: if ($args->{'CODE_ignore_dup'}) { 4518: &scan_data($scan_data,"$whichline.CODE_ignore_dup",'1'); 4519: } 4520: &scan_data($scan_data,"$whichline.useCODE",'1'); 4521: if ($args->{'CODE'} ne 'use_unfound') { 4522: if (length($args->{'CODE'}) > $$scantron_config{'CODElength'}) { 4523: return ($line,1,'New CODE value too large'); 4524: } 4525: if (length($args->{'CODE'}) < $$scantron_config{'CODElength'}) { 4526: $args->{'CODE'}=sprintf('%-'.$$scantron_config{'CODElength'}.'s',$args->{'CODE'}); 4527: } 4528: substr($line,$$scantron_config{'CODEstart'}-1, 4529: $$scantron_config{'CODElength'})=$args->{'CODE'}; 4530: } 4531: } elsif ($field eq 'answer') { 4532: my $length=$scantron_config->{'Qlength'}; 4533: my $off=$scantron_config->{'Qoff'}; 4534: my $on=$scantron_config->{'Qon'}; 4535: my $answer=${off}x$length; 4536: if ($args->{'response'} eq 'none') { 4537: &scan_data($scan_data, 4538: "$whichline.no_bubble.".$args->{'question'},'1'); 4539: } else { 4540: if ($on eq 'letter') { 4541: my @alphabet=('A'..'Z'); 4542: $answer=$alphabet[$args->{'response'}]; 4543: } elsif ($on eq 'number') { 4544: $answer=$args->{'response'}+1; 4545: } else { 4546: substr($answer,$args->{'response'},1)=$on; 4547: } 4548: &scan_data($scan_data, 4549: "$whichline.no_bubble.".$args->{'question'},undef,'1'); 4550: } 4551: my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'}; 4552: substr($line,$where-1,$length)=$answer; 4553: } 4554: return $line; 4555: } 4556: 4557: sub scan_data { 4558: my ($scan_data,$key,$value,$delete)=@_; 4559: my $filename=$env{'form.scantron_selectfile'}; 4560: if (defined($value)) { 4561: $scan_data->{$filename.'_'.$key} = $value; 4562: } 4563: if ($delete) { delete($scan_data->{$filename.'_'.$key}); } 4564: return $scan_data->{$filename.'_'.$key}; 4565: } 4566: 4567: sub scantron_parse_scanline { 4568: my ($line,$whichline,$scantron_config,$scan_data,$justHeader)=@_; 4569: my %record; 4570: my $questions=substr($line,$$scantron_config{'Qstart'}-1); 4571: my $data=substr($line,0,$$scantron_config{'Qstart'}-1); 4572: if (!($$scantron_config{'CODElocation'} eq 0 || 4573: $$scantron_config{'CODElocation'} eq 'none')) { 4574: if ($$scantron_config{'CODElocation'} < 0 || 4575: $$scantron_config{'CODElocation'} eq 'letter' || 4576: $$scantron_config{'CODElocation'} eq 'number') { 4577: $record{'scantron.CODE'}=substr($data, 4578: $$scantron_config{'CODEstart'}-1, 4579: $$scantron_config{'CODElength'}); 4580: if (&scan_data($scan_data,"$whichline.useCODE")) { 4581: $record{'scantron.useCODE'}=1; 4582: } 4583: if (&scan_data($scan_data,"$whichline.CODE_ignore_dup")) { 4584: $record{'scantron.CODE_ignore_dup'}=1; 4585: } 4586: } else { 4587: #FIXME interpret first N questions 4588: } 4589: } 4590: $record{'scantron.ID'}=substr($data,$$scantron_config{'IDstart'}-1, 4591: $$scantron_config{'IDlength'}); 4592: $record{'scantron.PaperID'}= 4593: substr($data,$$scantron_config{'PaperID'}-1, 4594: $$scantron_config{'PaperIDlength'}); 4595: $record{'scantron.FirstName'}= 4596: substr($data,$$scantron_config{'FirstName'}-1, 4597: $$scantron_config{'FirstNamelength'}); 4598: $record{'scantron.LastName'}= 4599: substr($data,$$scantron_config{'LastName'}-1, 4600: $$scantron_config{'LastNamelength'}); 4601: if ($justHeader) { return \%record; } 4602: 4603: my @alphabet=('A'..'Z'); 4604: my $questnum=0; 4605: while ($questions) { 4606: $questnum++; 4607: my $currentquest=substr($questions,0,$$scantron_config{'Qlength'}); 4608: substr($questions,0,$$scantron_config{'Qlength'})=''; 4609: if (length($currentquest) < $$scantron_config{'Qlength'}) { next; } 4610: if ($$scantron_config{'Qon'} eq 'letter') { 4611: if ($currentquest eq '?' 4612: || $currentquest eq '*') { 4613: push(@{$record{'scantron.doubleerror'}},$questnum); 4614: $record{"scantron.$questnum.answer"}=''; 4615: } elsif (!$currentquest 4616: || $currentquest eq $$scantron_config{'Qoff'} 4617: || $currentquest !~ /^[A-Z]$/) { 4618: $record{"scantron.$questnum.answer"}=''; 4619: if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) { 4620: push(@{$record{"scantron.missingerror"}},$questnum); 4621: } 4622: } else { 4623: $record{"scantron.$questnum.answer"}=$currentquest; 4624: } 4625: } elsif ($$scantron_config{'Qon'} eq 'number') { 4626: if ($currentquest eq '?' 4627: || $currentquest eq '*') { 4628: push(@{$record{'scantron.doubleerror'}},$questnum); 4629: $record{"scantron.$questnum.answer"}=''; 4630: } elsif (!$currentquest 4631: || $currentquest eq $$scantron_config{'Qoff'} 4632: || $currentquest !~ /^\d$/) { 4633: $record{"scantron.$questnum.answer"}=''; 4634: if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) { 4635: push(@{$record{"scantron.missingerror"}},$questnum); 4636: } 4637: } else { 4638: # wrap zero back to J 4639: if ($currentquest eq '0') { 4640: $record{"scantron.$questnum.answer"}= 4641: $alphabet[9]; 4642: } else { 4643: $record{"scantron.$questnum.answer"}= 4644: $alphabet[$currentquest-1]; 4645: } 4646: } 4647: } else { 4648: my @array=split($$scantron_config{'Qon'},$currentquest,-1); 4649: if (length($array[0]) eq $$scantron_config{'Qlength'}) { 4650: $record{"scantron.$questnum.answer"}=''; 4651: if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) { 4652: push(@{$record{"scantron.missingerror"}},$questnum); 4653: } 4654: } else { 4655: $record{"scantron.$questnum.answer"}= 4656: $alphabet[length($array[0])]; 4657: } 4658: if (scalar(@array) gt 2) { 4659: push(@{$record{'scantron.doubleerror'}},$questnum); 4660: my @ans=@array; 4661: my $i=length($ans[0]);shift(@ans); 4662: while ($#ans) { 4663: $i+=length($ans[0])+1; 4664: $record{"scantron.$questnum.answer"}.=$alphabet[$i]; 4665: shift(@ans); 4666: } 4667: } 4668: } 4669: } 4670: $record{'scantron.maxquest'}=$questnum; 4671: return \%record; 4672: } 4673: 4674: sub scantron_add_delay { 4675: my ($delayqueue,$scanline,$errormessage,$errorcode)=@_; 4676: push(@$delayqueue, 4677: {'line' => $scanline, 'emsg' => $errormessage, 4678: 'ecode' => $errorcode } 4679: ); 4680: } 4681: 4682: sub scantron_find_student { 4683: my ($scantron_record,$scan_data,$idmap,$line)=@_; 4684: my $scanID=$$scantron_record{'scantron.ID'}; 4685: if ($scanID =~ /^\s*$/) { 4686: return &scan_data($scan_data,"$line.user"); 4687: } 4688: foreach my $id (keys(%$idmap)) { 4689: if (lc($id) eq lc($scanID)) { 4690: return $$idmap{$id}; 4691: } 4692: } 4693: return undef; 4694: } 4695: 4696: sub scantron_filter { 4697: my ($curres)=@_; 4698: 4699: if (ref($curres) && $curres->is_problem()) { 4700: # if the user has asked to not have either hidden 4701: # or 'randomout' controlled resources to be graded 4702: # don't include them 4703: if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden' 4704: && $curres->randomout) { 4705: return 0; 4706: } 4707: return 1; 4708: } 4709: return 0; 4710: } 4711: 4712: sub scantron_process_corrections { 4713: my ($r) = @_; 4714: my %scantron_config=&get_scantron_config($env{'form.scantron_format'}); 4715: my ($scanlines,$scan_data)=&scantron_getfile(); 4716: my $classlist=&Apache::loncoursedata::get_classlist(); 4717: my $which=$env{'form.scantron_line'}; 4718: my $line=&scantron_get_line($scanlines,$scan_data,$which); 4719: my ($skip,$err,$errmsg); 4720: if ($env{'form.scantron_skip_record'}) { 4721: $skip=1; 4722: } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) { 4723: my $newstudent=$env{'form.scantron_username'}.':'. 4724: $env{'form.scantron_domain'}; 4725: my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID]; 4726: ($line,$err,$errmsg)= 4727: &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which, 4728: 'ID',{'newid'=>$newid, 4729: 'username'=>$env{'form.scantron_username'}, 4730: 'domain'=>$env{'form.scantron_domain'}}); 4731: } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)CODE$/) { 4732: my $resolution=$env{'form.scantron_CODE_resolution'}; 4733: my $newCODE; 4734: my %args; 4735: if ($resolution eq 'use_unfound') { 4736: $newCODE='use_unfound'; 4737: } elsif ($resolution eq 'use_found') { 4738: $newCODE=$env{'form.scantron_CODE_selectedvalue'}; 4739: } elsif ($resolution eq 'use_typed') { 4740: $newCODE=$env{'form.scantron_CODE_newvalue'}; 4741: } elsif ($resolution =~ /^use_closest_(\d+)/) { 4742: $newCODE=$env{"form.scantron_CODE_closest_$1"}; 4743: } 4744: if ($env{'form.scantron_corrections'} eq 'duplicateCODE') { 4745: $args{'CODE_ignore_dup'}=1; 4746: } 4747: $args{'CODE'}=$newCODE; 4748: ($line,$err,$errmsg)= 4749: &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which, 4750: 'CODE',\%args); 4751: } elsif ($env{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) { 4752: foreach my $question (split(',',$env{'form.scantron_questions'})) { 4753: ($line,$err,$errmsg)= 4754: &scantron_fixup_scanline(\%scantron_config,$scan_data,$line, 4755: $which,'answer', 4756: { 'question'=>$question, 4757: 'response'=>$env{"form.scantron_correct_Q_$question"}}); 4758: if ($err) { last; } 4759: } 4760: } 4761: if ($err) { 4762: $r->print("<font color='red'>Unable to accept last correction, an error occurred :$errmsg:</font>"); 4763: } else { 4764: &scantron_put_line($scanlines,$scan_data,$which,$line,$skip); 4765: &scantron_putfile($scanlines,$scan_data); 4766: } 4767: } 4768: 4769: sub reset_skipping_status { 4770: my ($scanlines,$scan_data)=&scantron_getfile(); 4771: &scan_data($scan_data,'remember_skipping',undef,1); 4772: &scantron_putfile(undef,$scan_data); 4773: } 4774: 4775: sub allow_skipping { 4776: my ($scan_data,$i)=@_; 4777: my %remembered=split(':',&scan_data($scan_data,'remember_skipping')); 4778: delete($remembered{$i}); 4779: &scan_data($scan_data,'remember_skipping',join(':',%remembered)); 4780: } 4781: 4782: sub should_be_skipped { 4783: my ($scan_data,$i)=@_; 4784: if ($env{'form.scantron_options_redo'} !~ /^redo_/) { 4785: # not redoing old skips 4786: return 0; 4787: } 4788: my %remembered=split(':',&scan_data($scan_data,'remember_skipping')); 4789: if (exists($remembered{$i})) { return 0; } 4790: return 1; 4791: } 4792: 4793: sub remember_current_skipped { 4794: my ($scanlines,$scan_data)=&scantron_getfile(); 4795: my %to_remember; 4796: for (my $i=0;$i<=$scanlines->{'count'};$i++) { 4797: if ($scanlines->{'skipped'}[$i]) { 4798: $to_remember{$i}=1; 4799: } 4800: } 4801: &scan_data($scan_data,'remember_skipping',join(':',%to_remember)); 4802: &scantron_putfile(undef,$scan_data); 4803: } 4804: 4805: sub check_for_error { 4806: my ($r,$result)=@_; 4807: if ($result ne 'ok' && $result ne 'not_found' ) { 4808: $r->print("An error occured ($result) when trying to Remove the existing corrections."); 4809: } 4810: } 4811: 4812: sub scantron_warning_screen { 4813: my ($button_text)=@_; 4814: my $title=&Apache::lonnet::gettitle($env{'form.selectpage'}); 4815: my %scantron_config=&get_scantron_config($env{'form.scantron_format'}); 4816: my $CODElist="a"; 4817: if ($scantron_config{'CODElocation'} && 4818: $scantron_config{'CODEstart'} && 4819: $scantron_config{'CODElength'}) { 4820: $CODElist=$env{'form.scantron_CODElist'}; 4821: if ($CODElist eq '') { $CODElist='<font color="red">None</font>'; } 4822: $CODElist= 4823: '<tr><td><b>List of CODES to validate against:</b></td><td><tt>'. 4824: $CODElist.'</tt></td></tr>'; 4825: } 4826: return (<<STUFF); 4827: <p> 4828: <font color="red">Please double check the information 4829: below before clicking on '$button_text'</font> 4830: </p> 4831: <table> 4832: <tr><td><b>Sequence to be Graded:</b></td><td>$title</td></tr> 4833: <tr><td><b>Data File that will be used:</b></td><td><tt>$env{'form.scantron_selectfile'}</tt></td></tr> 4834: $CODElist 4835: </table> 4836: </font> 4837: <br /> 4838: <p> If this information is correct, please click on '$button_text'.</p> 4839: <p> If something is incorrect, please click the 'Grading Menu' button to start over.</p> 4840: 4841: <br /> 4842: STUFF 4843: } 4844: 4845: sub scantron_do_warning { 4846: my ($r)=@_; 4847: my ($symb)=&get_symb($r); 4848: if (!$symb) {return '';} 4849: my $default_form_data=&defaultFormData($symb); 4850: $r->print(&scantron_form_start().$default_form_data); 4851: if ( $env{'form.selectpage'} eq '' || 4852: $env{'form.scantron_selectfile'} eq '' || 4853: $env{'form.scantron_format'} eq '' ) { 4854: $r->print("<p>You have forgetten to specify some information. Please go Back and try again.</p>"); 4855: if ( $env{'form.selectpage'} eq '') { 4856: $r->print('<p><font color="red">You have not selected a Sequence to grade</font></p>'); 4857: } 4858: if ( $env{'form.scantron_selectfile'} eq '') { 4859: $r->print('<p><font color="red">You have not selected a file that contains the student\'s response data.</font></p>'); 4860: } 4861: if ( $env{'form.scantron_format'} eq '') { 4862: $r->print('<p><font color="red">You have not selected a the format of the student\'s response data.</font></p>'); 4863: } 4864: } else { 4865: my $warning=&scantron_warning_screen('Grading: Validate Records'); 4866: $r->print(<<STUFF); 4867: $warning 4868: <input type="submit" name="submit" value="Grading: Validate Records" /> 4869: <input type="hidden" name="command" value="scantron_validate" /> 4870: STUFF 4871: } 4872: $r->print("</form><br />".&show_grading_menu_form($symb)); 4873: return ''; 4874: } 4875: 4876: sub scantron_form_start { 4877: my ($max_bubble)=@_; 4878: my $result= <<SCANTRONFORM; 4879: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload"> 4880: <input type="hidden" name="selectpage" value="$env{'form.selectpage'}" /> 4881: <input type="hidden" name="scantron_format" value="$env{'form.scantron_format'}" /> 4882: <input type="hidden" name="scantron_selectfile" value="$env{'form.scantron_selectfile'}" /> 4883: <input type="hidden" name="scantron_maxbubble" value="$max_bubble" /> 4884: <input type="hidden" name="scantron_CODElist" value="$env{'form.scantron_CODElist'}" /> 4885: <input type="hidden" name="scantron_CODEunique" value="$env{'form.scantron_CODEunique'}" /> 4886: <input type="hidden" name="scantron_options_redo" value="$env{'form.scantron_options_redo'}" /> 4887: <input type="hidden" name="scantron_options_ignore" value="$env{'form.scantron_options_ignore'}" /> 4888: <input type="hidden" name="scantron_options_hidden" value="$env{'form.scantron_options_hidden'}" /> 4889: SCANTRONFORM 4890: return $result; 4891: } 4892: 4893: sub scantron_validate_file { 4894: my ($r) = @_; 4895: my ($symb)=&get_symb($r); 4896: if (!$symb) {return '';} 4897: my $default_form_data=&defaultFormData($symb); 4898: 4899: # do the detection of only doing skipped records first befroe we delete 4900: # them when doing the corrections reset 4901: if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') { 4902: &reset_skipping_status(); 4903: } 4904: if ($env{'form.scantron_options_redo'} eq 'redo_skipped') { 4905: &remember_current_skipped(); 4906: &scantron_remove_file('skipped'); 4907: $env{'form.scantron_options_redo'}='redo_skipped_ready'; 4908: } 4909: 4910: if ($env{'form.scantron_options_ignore'} eq 'ignore_corrections') { 4911: &check_for_error($r,&scantron_remove_file('corrected')); 4912: &check_for_error($r,&scantron_remove_file('skipped')); 4913: &check_for_error($r,&scantron_remove_scan_data()); 4914: $env{'form.scantron_options_ignore'}='done'; 4915: } 4916: 4917: if ($env{'form.scantron_corrections'}) { 4918: &scantron_process_corrections($r); 4919: } 4920: $r->print("<p>Gathering neccessary info.</p>");$r->rflush(); 4921: #get the student pick code ready 4922: $r->print(&Apache::loncommon::studentbrowser_javascript()); 4923: my $max_bubble=&scantron_get_maxbubble(); 4924: my $result=&scantron_form_start($max_bubble).$default_form_data; 4925: $r->print($result); 4926: 4927: my @validate_phases=( 'sequence', 4928: 'ID', 4929: 'CODE', 4930: 'doublebubble', 4931: 'missingbubbles'); 4932: if (!$env{'form.validatepass'}) { 4933: $env{'form.validatepass'} = 0; 4934: } 4935: my $currentphase=$env{'form.validatepass'}; 4936: 4937: my $stop=0; 4938: while (!$stop && $currentphase < scalar(@validate_phases)) { 4939: $r->print("<p> Validating ".$validate_phases[$currentphase]."</p>"); 4940: $r->rflush(); 4941: my $which="scantron_validate_".$validate_phases[$currentphase]; 4942: { 4943: no strict 'refs'; 4944: ($stop,$currentphase)=&$which($r,$currentphase); 4945: } 4946: } 4947: if (!$stop) { 4948: my $warning=&scantron_warning_screen('Start Grading'); 4949: $r->print(<<STUFF); 4950: Validation process complete.<br /> 4951: $warning 4952: <input type="submit" name="submit" value="Start Grading" /> 4953: <input type="hidden" name="command" value="scantron_process" /> 4954: STUFF 4955: 4956: } else { 4957: $r->print('<input type="hidden" name="command" value="scantron_validate" />'); 4958: $r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />"); 4959: } 4960: if ($stop) { 4961: if ($validate_phases[$currentphase] eq 'sequence') { 4962: $r->print('<input type="submit" name="submit" value="Ignore -> " />'); 4963: $r->print(' this error <br />'); 4964: 4965: $r->print(" <p>Or click the 'Grading Menu' button to start over.</p>"); 4966: } else { 4967: $r->print('<input type="submit" name="submit" value="Continue ->" />'); 4968: $r->print(' using corrected info <br />'); 4969: $r->print("<input type='submit' value='Skip' name='scantron_skip_record' />"); 4970: $r->print(" this scanline saving it for later."); 4971: } 4972: } 4973: $r->print(" </form><br />".&show_grading_menu_form($symb)); 4974: return ''; 4975: } 4976: 4977: sub scantron_remove_file { 4978: my ($which)=@_; 4979: my $cname=$env{'course.'.$env{'request.course.id'}.'.num'}; 4980: my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'}; 4981: my $file='scantron_'; 4982: if ($which eq 'corrected' || $which eq 'skipped') { 4983: $file.=$which.'_'; 4984: } else { 4985: return 'refused'; 4986: } 4987: $file.=$env{'form.scantron_selectfile'}; 4988: return &Apache::lonnet::removeuserfile($cname,$cdom,$file); 4989: } 4990: 4991: sub scantron_remove_scan_data { 4992: my $cname=$env{'course.'.$env{'request.course.id'}.'.num'}; 4993: my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'}; 4994: my @keys=&Apache::lonnet::getkeys('nohist_scantrondata',$cdom,$cname); 4995: my @todelete; 4996: my $filename=$env{'form.scantron_selectfile'}; 4997: foreach my $key (@keys) { 4998: if ($key=~/^\Q$filename\E_/) { 4999: if ($env{'form.scantron_options_redo'} eq 'redo_skipped_ready' && 5000: $key=~/remember_skipping/) { 5001: next; 5002: } 5003: push(@todelete,$key); 5004: } 5005: } 5006: my $result; 5007: if (@todelete) { 5008: $result=&Apache::lonnet::del('nohist_scantrondata',\@todelete,$cdom,$cname); 5009: } 5010: return $result; 5011: } 5012: 5013: sub scantron_getfile { 5014: #FIXME really would prefer a scantron directory 5015: my $cname=$env{'course.'.$env{'request.course.id'}.'.num'}; 5016: my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'}; 5017: my $lines; 5018: $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'. 5019: 'scantron_orig_'.$env{'form.scantron_selectfile'}); 5020: my %scanlines; 5021: $scanlines{'orig'}=[(split("\n",$lines,-1))]; 5022: my $temp=$scanlines{'orig'}; 5023: $scanlines{'count'}=$#$temp; 5024: 5025: $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'. 5026: 'scantron_corrected_'.$env{'form.scantron_selectfile'}); 5027: if ($lines eq '-1') { 5028: $scanlines{'corrected'}=[]; 5029: } else { 5030: $scanlines{'corrected'}=[(split("\n",$lines,-1))]; 5031: } 5032: $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'. 5033: 'scantron_skipped_'.$env{'form.scantron_selectfile'}); 5034: if ($lines eq '-1') { 5035: $scanlines{'skipped'}=[]; 5036: } else { 5037: $scanlines{'skipped'}=[(split("\n",$lines,-1))]; 5038: } 5039: my @tmp=&Apache::lonnet::dump('nohist_scantrondata',$cdom,$cname); 5040: if ($tmp[0] =~ /^(error:|no_such_host)/) { @tmp=(); } 5041: my %scan_data = @tmp; 5042: return (\%scanlines,\%scan_data); 5043: } 5044: 5045: sub lonnet_putfile { 5046: my ($contents,$filename)=@_; 5047: my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'}; 5048: my $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'}; 5049: $env{'form.sillywaytopassafilearound'}=$contents; 5050: &Apache::lonnet::finishuserfileupload($docuname,$docudom,'sillywaytopassafilearound',$filename); 5051: 5052: } 5053: 5054: sub scantron_putfile { 5055: my ($scanlines,$scan_data) = @_; 5056: #FIXME really would prefer a scantron directory 5057: my $cname=$env{'course.'.$env{'request.course.id'}.'.num'}; 5058: my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'}; 5059: if ($scanlines) { 5060: my $prefix='scantron_'; 5061: # no need to update orig, shouldn't change 5062: # &lonnet_putfile(join("\n",@{$scanlines->{'orig'}}),$prefix.'orig_'. 5063: # $env{'form.scantron_selectfile'}); 5064: &lonnet_putfile(join("\n",@{$scanlines->{'corrected'}}), 5065: $prefix.'corrected_'. 5066: $env{'form.scantron_selectfile'}); 5067: &lonnet_putfile(join("\n",@{$scanlines->{'skipped'}}), 5068: $prefix.'skipped_'. 5069: $env{'form.scantron_selectfile'}); 5070: } 5071: &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname); 5072: } 5073: 5074: sub scantron_get_line { 5075: my ($scanlines,$scan_data,$i)=@_; 5076: if (&should_be_skipped($scan_data,$i)) { return undef; } 5077: if ($scanlines->{'skipped'}[$i]) { return undef; } 5078: if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$i];} 5079: return $scanlines->{'orig'}[$i]; 5080: } 5081: 5082: sub get_todo_count { 5083: my ($scanlines,$scan_data)=@_; 5084: my $count=0; 5085: for (my $i=0;$i<=$scanlines->{'count'};$i++) { 5086: my $line=&scantron_get_line($scanlines,$scan_data,$i); 5087: if ($line=~/^[\s\cz]*$/) { next; } 5088: $count++; 5089: } 5090: return $count; 5091: } 5092: 5093: sub scantron_put_line { 5094: my ($scanlines,$scan_data,$i,$newline,$skip)=@_; 5095: if ($skip) { 5096: $scanlines->{'skipped'}[$i]=$newline; 5097: &allow_skipping($scan_data,$i); 5098: return; 5099: } 5100: $scanlines->{'corrected'}[$i]=$newline; 5101: } 5102: 5103: sub scantron_filter_not_exam { 5104: my ($curres)=@_; 5105: 5106: if (ref($curres) && $curres->is_problem() && !$curres->is_exam()) { 5107: # if the user has asked to not have either hidden 5108: # or 'randomout' controlled resources to be graded 5109: # don't include them 5110: if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden' 5111: && $curres->randomout) { 5112: return 0; 5113: } 5114: return 1; 5115: } 5116: return 0; 5117: } 5118: 5119: sub scantron_validate_sequence { 5120: my ($r,$currentphase) = @_; 5121: 5122: my $navmap=Apache::lonnavmaps::navmap->new(); 5123: my (undef,undef,$sequence)= 5124: &Apache::lonnet::decode_symb($env{'form.selectpage'}); 5125: 5126: my $map=$navmap->getResourceByUrl($sequence); 5127: 5128: $r->print('<input type="hidden" name="validate_sequence_exam" 5129: value="ignore" />'); 5130: if ($env{'form.validate_sequence_exam'} ne 'ignore') { 5131: my @resources= 5132: $navmap->retrieveResources($map,\&scantron_filter_not_exam,1,0); 5133: if (@resources) { 5134: $r->print("<p>".&mt('Some resources in the sequence currently are not set to exam mode. Grading these resources currently may not work correctly.')."</p>"); 5135: return (1,$currentphase); 5136: } 5137: } 5138: 5139: return (0,$currentphase+1); 5140: } 5141: 5142: sub scantron_validate_ID { 5143: my ($r,$currentphase) = @_; 5144: 5145: #get student info 5146: my $classlist=&Apache::loncoursedata::get_classlist(); 5147: my %idmap=&username_to_idmap($classlist); 5148: 5149: #get scantron line setup 5150: my %scantron_config=&get_scantron_config($env{'form.scantron_format'}); 5151: my ($scanlines,$scan_data)=&scantron_getfile(); 5152: 5153: my %found=('ids'=>{},'usernames'=>{}); 5154: for (my $i=0;$i<=$scanlines->{'count'};$i++) { 5155: my $line=&scantron_get_line($scanlines,$scan_data,$i); 5156: if ($line=~/^[\s\cz]*$/) { next; } 5157: my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, 5158: $scan_data); 5159: my $id=$$scan_record{'scantron.ID'}; 5160: my $found; 5161: foreach my $checkid (keys(%idmap)) { 5162: if (lc($checkid) eq lc($id)) { $found=$checkid;last; } 5163: } 5164: if ($found) { 5165: my $username=$idmap{$found}; 5166: if ($found{'ids'}{$found}) { 5167: &scantron_get_correction($r,$i,$scan_record,\%scantron_config, 5168: $line,'duplicateID',$found); 5169: return(1,$currentphase); 5170: } elsif ($found{'usernames'}{$username}) { 5171: &scantron_get_correction($r,$i,$scan_record,\%scantron_config, 5172: $line,'duplicateID',$username); 5173: return(1,$currentphase); 5174: } 5175: #FIXME store away line we previously saw the ID on to use above 5176: $found{'ids'}{$found}++; 5177: $found{'usernames'}{$username}++; 5178: } else { 5179: if ($id =~ /^\s*$/) { 5180: my $username=&scan_data($scan_data,"$i.user"); 5181: if (defined($username) && $found{'usernames'}{$username}) { 5182: &scantron_get_correction($r,$i,$scan_record, 5183: \%scantron_config, 5184: $line,'duplicateID',$username); 5185: return(1,$currentphase); 5186: } elsif (!defined($username)) { 5187: &scantron_get_correction($r,$i,$scan_record, 5188: \%scantron_config, 5189: $line,'incorrectID'); 5190: return(1,$currentphase); 5191: } 5192: $found{'usernames'}{$username}++; 5193: } else { 5194: &scantron_get_correction($r,$i,$scan_record,\%scantron_config, 5195: $line,'incorrectID'); 5196: return(1,$currentphase); 5197: } 5198: } 5199: } 5200: 5201: return (0,$currentphase+1); 5202: } 5203: 5204: sub scantron_get_correction { 5205: my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_; 5206: 5207: #FIXME in the case of a duplicated ID the previous line, probaly need 5208: #to show both the current line and the previous one and allow skipping 5209: #the previous one or the current one 5210: 5211: $r->print("<p><b>An error was detected ($error)</b>"); 5212: if ( $$scan_record{'scantron.PaperID'} =~ /\S/) { 5213: $r->print(" for PaperID <tt>". 5214: $$scan_record{'scantron.PaperID'}."</tt> \n"); 5215: } else { 5216: $r->print(" in scanline $i <pre>". 5217: $line."</pre> \n"); 5218: } 5219: my $message="<p>The ID on the form is <tt>". 5220: $$scan_record{'scantron.ID'}."</tt><br />\n". 5221: "The name on the paper is ". 5222: $$scan_record{'scantron.LastName'}.",". 5223: $$scan_record{'scantron.FirstName'}."</p>"; 5224: 5225: $r->print('<input type="hidden" name="scantron_corrections" value="'.$error.'" />'."\n"); 5226: $r->print('<input type="hidden" name="scantron_line" value="'.$i.'" />'."\n"); 5227: if ($error =~ /ID$/) { 5228: if ($error eq 'incorrectID') { 5229: $r->print("The encoded ID is not in the classlist</p>\n"); 5230: } elsif ($error eq 'duplicateID') { 5231: $r->print("The encoded ID has also been used by a previous paper $arg</p>\n"); 5232: } 5233: $r->print($message); 5234: $r->print("<p>How should I handle this? <br /> \n"); 5235: $r->print("\n<ul><li> "); 5236: #FIXME it would be nice if this sent back the user ID and 5237: #could do partial userID matches 5238: $r->print(&Apache::loncommon::selectstudent_link('scantronupload', 5239: 'scantron_username','scantron_domain')); 5240: $r->print(": <input type='text' name='scantron_username' value='' />"); 5241: $r->print("\n@". 5242: &Apache::loncommon::select_dom_form($env{'request.role.domain'},'scantron_domain')); 5243: 5244: $r->print('</li>'); 5245: } elsif ($error =~ /CODE$/) { 5246: if ($error eq 'incorrectCODE') { 5247: $r->print("</p><p>The encoded CODE is not in the list of possible CODEs</p>\n"); 5248: } elsif ($error eq 'duplicateCODE') { 5249: $r->print("</p><p>The encoded CODE has also been used by a previous paper ".join(', ',@{$arg}).", and CODEs are supposed to be unique</p>\n"); 5250: } 5251: $r->print("<p>The CODE on the form is <tt>'". 5252: $$scan_record{'scantron.CODE'}."'</tt><br />\n"); 5253: $r->print($message); 5254: $r->print("<p>How should I handle this? <br /> \n"); 5255: $r->print("\n<br /> "); 5256: my $i=0; 5257: if ($error eq 'incorrectCODE' 5258: && $$scan_record{'scantron.CODE'}=~/\S/ ) { 5259: my ($max,$closest)=&scantron_get_closely_matching_CODEs($arg,$$scan_record{'scantron.CODE'}); 5260: if ($closest > 0) { 5261: foreach my $testcode (@{$closest}) { 5262: my $checked=''; 5263: if (!$i) { $checked=' checked="on" '; } 5264: $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_closest_$i' $checked /> Use the similar CODE <b><tt>".$testcode."</tt></b> instead.</label><input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />"); 5265: $r->print("\n<br />"); 5266: $i++; 5267: } 5268: } 5269: } 5270: if ($$scan_record{'scantron.CODE'}=~/\S/ ) { 5271: my $checked; if (!$i) { $checked=' checked="on" '; } 5272: $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_unfound' $checked /> Use the CODE <b><tt>".$$scan_record{'scantron.CODE'}."</tt></b> that is was on the paper, ignoring the error.</label>"); 5273: $r->print("\n<br />"); 5274: } 5275: 5276: $r->print(<<ENDSCRIPT); 5277: <script type="text/javascript"> 5278: function change_radio(field) { 5279: var slct=document.scantronupload.scantron_CODE_resolution; 5280: var i; 5281: for (i=0;i<slct.length;i++) { 5282: if (slct[i].value==field) { slct[i].checked=true; } 5283: } 5284: } 5285: </script> 5286: ENDSCRIPT 5287: my $href="/adm/pickcode?". 5288: "form=".&escape("scantronupload"). 5289: "&scantron_format=".&escape($env{'form.scantron_format'}). 5290: "&scantron_CODElist=".&escape($env{'form.scantron_CODElist'}). 5291: "&curCODE=".&escape($$scan_record{'scantron.CODE'}). 5292: "&scantron_selectfile=".&escape($env{'form.scantron_selectfile'}); 5293: if ($env{'form.scantron_CODElist'} =~ /\S/) { 5294: $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_found' /> <a target='_blank' href='$href'>Select</a> a CODE from the list of all CODEs and use it.</label> Selected CODE is <input readonly='true' type='text' size='8' name='scantron_CODE_selectedvalue' onfocus=\"javascript:change_radio('use_found')\" onchange=\"javascript:change_radio('use_found')\" />"); 5295: $r->print("\n<br />"); 5296: } 5297: $r->print("<label><input type='radio' name='scantron_CODE_resolution' value='use_typed' /> Use </label><input type='text' size='8' name='scantron_CODE_newvalue' onfocus=\"javascript:change_radio('use_typed')\" onkeypress=\"javascript:change_radio('use_typed')\" /> as the CODE."); 5298: $r->print("\n<br /><br />"); 5299: } elsif ($error eq 'doublebubble') { 5300: $r->print("<p>There have been multiple bubbles scanned for a some question(s)</p>\n"); 5301: $r->print('<input type="hidden" name="scantron_questions" value="'. 5302: join(',',@{$arg}).'" />'); 5303: $r->print($message); 5304: $r->print("<p>Please indicate which bubble should be used for grading</p>"); 5305: foreach my $question (@{$arg}) { 5306: my $selected=$$scan_record{"scantron.$question.answer"}; 5307: &scantron_bubble_selector($r,$scan_config,$question,split('',$selected)); 5308: } 5309: } elsif ($error eq 'missingbubble') { 5310: $r->print("<p>There have been <b>no</b> bubbles scanned for some question(s)</p>\n"); 5311: $r->print($message); 5312: $r->print("<p>Please indicate which bubble should be used for grading</p>"); 5313: $r->print("Some questions have no scanned bubbles\n"); 5314: $r->print('<input type="hidden" name="scantron_questions" value="'. 5315: join(',',@{$arg}).'" />'); 5316: foreach my $question (@{$arg}) { 5317: my $selected=$$scan_record{"scantron.$question.answer"}; 5318: &scantron_bubble_selector($r,$scan_config,$question); 5319: } 5320: } else { 5321: $r->print("\n<ul>"); 5322: } 5323: $r->print("\n</li></ul>"); 5324: 5325: } 5326: 5327: sub scantron_bubble_selector { 5328: my ($r,$scan_config,$quest,@selected)=@_; 5329: my $max=$$scan_config{'Qlength'}; 5330: 5331: my $scmode=$$scan_config{'Qon'}; 5332: if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; } 5333: 5334: my @alphabet=('A'..'Z'); 5335: $r->print("<table border='1'><tr><td rowspan='2'>$quest</td>"); 5336: for (my $i=0;$i<$max+1;$i++) { 5337: $r->print("\n".'<td align="center">'); 5338: if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) } 5339: else { $r->print(' '); } 5340: $r->print('</td>'); 5341: } 5342: $r->print('</tr><tr>'); 5343: for (my $i=0;$i<$max;$i++) { 5344: $r->print("\n". 5345: '<td><label><input type="radio" name="scantron_correct_Q_'. 5346: $quest.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>"); 5347: } 5348: $r->print('<td><label><input type="radio" name="scantron_correct_Q_'. 5349: $quest.'" value="none" /> No bubble </label></td>'); 5350: $r->print('</tr></table>'); 5351: } 5352: 5353: sub num_matches { 5354: my ($orig,$code) = @_; 5355: my @code=split(//,$code); 5356: my @orig=split(//,$orig); 5357: my $same=0; 5358: for (my $i=0;$i<scalar(@code);$i++) { 5359: if ($code[$i] eq $orig[$i]) { $same++; } 5360: } 5361: return $same; 5362: } 5363: 5364: sub scantron_get_closely_matching_CODEs { 5365: my ($allcodes,$CODE)=@_; 5366: my @CODEs; 5367: foreach my $testcode (sort(keys(%{$allcodes}))) { 5368: push(@{$CODEs[&num_matches($CODE,$testcode)]},$testcode); 5369: } 5370: 5371: return ($#CODEs,$CODEs[-1]); 5372: } 5373: 5374: sub get_codes { 5375: my ($old_name, $cdom, $cnum) = @_; 5376: if (!$old_name) { 5377: $old_name=$env{'form.scantron_CODElist'}; 5378: } 5379: if (!$cdom) { 5380: $cdom =$env{'course.'.$env{'request.course.id'}.'.domain'}; 5381: } 5382: if (!$cnum) { 5383: $cnum =$env{'course.'.$env{'request.course.id'}.'.num'}; 5384: } 5385: my %result=&Apache::lonnet::get('CODEs',[$old_name,"type\0$old_name"], 5386: $cdom,$cnum); 5387: my %allcodes; 5388: if ($result{"type\0$old_name"} eq 'number') { 5389: %allcodes=map {($_,1)} split(',',$result{$old_name}); 5390: } else { 5391: %allcodes=map {(&Apache::lonprintout::num_to_letters($_),1)} split(',',$result{$old_name}); 5392: } 5393: return %allcodes; 5394: } 5395: 5396: sub scantron_validate_CODE { 5397: my ($r,$currentphase) = @_; 5398: my %scantron_config=&get_scantron_config($env{'form.scantron_format'}); 5399: if ($scantron_config{'CODElocation'} && 5400: $scantron_config{'CODEstart'} && 5401: $scantron_config{'CODElength'}) { 5402: if (!defined($env{'form.scantron_CODElist'})) { 5403: &FIXME_blow_up() 5404: } 5405: } else { 5406: return (0,$currentphase+1); 5407: } 5408: 5409: my %usedCODEs; 5410: 5411: my %allcodes=&get_codes(); 5412: 5413: my ($scanlines,$scan_data)=&scantron_getfile(); 5414: for (my $i=0;$i<=$scanlines->{'count'};$i++) { 5415: my $line=&scantron_get_line($scanlines,$scan_data,$i); 5416: if ($line=~/^[\s\cz]*$/) { next; } 5417: my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, 5418: $scan_data); 5419: my $CODE=$$scan_record{'scantron.CODE'}; 5420: my $error=0; 5421: if (!&Apache::lonnet::validCODE($CODE)) { 5422: &scantron_get_correction($r,$i,$scan_record, 5423: \%scantron_config, 5424: $line,'incorrectCODE',\%allcodes); 5425: return(1,$currentphase); 5426: } 5427: if (%allcodes && !exists($allcodes{$CODE}) 5428: && !$$scan_record{'scantron.useCODE'}) { 5429: &scantron_get_correction($r,$i,$scan_record, 5430: \%scantron_config, 5431: $line,'incorrectCODE',\%allcodes); 5432: return(1,$currentphase); 5433: } 5434: if (exists($usedCODEs{$CODE}) 5435: && $env{'form.scantron_CODEunique'} eq 'yes' 5436: && !$$scan_record{'scantron.CODE_ignore_dup'}) { 5437: &scantron_get_correction($r,$i,$scan_record, 5438: \%scantron_config, 5439: $line,'duplicateCODE',$usedCODEs{$CODE}); 5440: return(1,$currentphase); 5441: } 5442: push (@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'}); 5443: } 5444: return (0,$currentphase+1); 5445: } 5446: 5447: sub scantron_validate_doublebubble { 5448: my ($r,$currentphase) = @_; 5449: #get student info 5450: my $classlist=&Apache::loncoursedata::get_classlist(); 5451: my %idmap=&username_to_idmap($classlist); 5452: 5453: #get scantron line setup 5454: my %scantron_config=&get_scantron_config($env{'form.scantron_format'}); 5455: my ($scanlines,$scan_data)=&scantron_getfile(); 5456: for (my $i=0;$i<=$scanlines->{'count'};$i++) { 5457: my $line=&scantron_get_line($scanlines,$scan_data,$i); 5458: if ($line=~/^[\s\cz]*$/) { next; } 5459: my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, 5460: $scan_data); 5461: if (!defined($$scan_record{'scantron.doubleerror'})) { next; } 5462: &scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line, 5463: 'doublebubble', 5464: $$scan_record{'scantron.doubleerror'}); 5465: return (1,$currentphase); 5466: } 5467: return (0,$currentphase+1); 5468: } 5469: 5470: sub scantron_get_maxbubble { 5471: if (defined($env{'form.scantron_maxbubble'}) && 5472: $env{'form.scantron_maxbubble'}) { 5473: return $env{'form.scantron_maxbubble'}; 5474: } 5475: 5476: my $navmap=Apache::lonnavmaps::navmap->new(); 5477: my (undef,undef,$sequence)= 5478: &Apache::lonnet::decode_symb($env{'form.selectpage'}); 5479: 5480: my $map=$navmap->getResourceByUrl($sequence); 5481: my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0); 5482: 5483: &Apache::lonxml::clear_problem_counter(); 5484: 5485: foreach my $resource (@resources) { 5486: my $result=&Apache::lonnet::ssi($resource->src(), 5487: ('symb' => $resource->symb())); 5488: } 5489: &Apache::lonnet::delenv('scantron\.'); 5490: $env{'form.scantron_maxbubble'} = 5491: &Apache::lonxml::get_problem_counter()-1; 5492: 5493: return $env{'form.scantron_maxbubble'}; 5494: } 5495: 5496: sub scantron_validate_missingbubbles { 5497: my ($r,$currentphase) = @_; 5498: #get student info 5499: my $classlist=&Apache::loncoursedata::get_classlist(); 5500: my %idmap=&username_to_idmap($classlist); 5501: 5502: #get scantron line setup 5503: my %scantron_config=&get_scantron_config($env{'form.scantron_format'}); 5504: my ($scanlines,$scan_data)=&scantron_getfile(); 5505: my $max_bubble=&scantron_get_maxbubble(); 5506: if (!$max_bubble) { $max_bubble=2**31; } 5507: for (my $i=0;$i<=$scanlines->{'count'};$i++) { 5508: my $line=&scantron_get_line($scanlines,$scan_data,$i); 5509: if ($line=~/^[\s\cz]*$/) { next; } 5510: my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, 5511: $scan_data); 5512: if (!defined($$scan_record{'scantron.missingerror'})) { next; } 5513: my @to_correct; 5514: foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) { 5515: if ($missing > $max_bubble) { next; } 5516: push(@to_correct,$missing); 5517: } 5518: if (@to_correct) { 5519: &scantron_get_correction($r,$i,$scan_record,\%scantron_config, 5520: $line,'missingbubble',\@to_correct); 5521: return (1,$currentphase); 5522: } 5523: 5524: } 5525: return (0,$currentphase+1); 5526: } 5527: 5528: sub scantron_process_students { 5529: my ($r) = @_; 5530: my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'}); 5531: my ($symb)=&get_symb($r); 5532: if (!$symb) {return '';} 5533: my $default_form_data=&defaultFormData($symb); 5534: 5535: my %scantron_config=&get_scantron_config($env{'form.scantron_format'}); 5536: my ($scanlines,$scan_data)=&scantron_getfile(); 5537: my $classlist=&Apache::loncoursedata::get_classlist(); 5538: my %idmap=&username_to_idmap($classlist); 5539: my $navmap=Apache::lonnavmaps::navmap->new(); 5540: my $map=$navmap->getResourceByUrl($sequence); 5541: my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0); 5542: # $r->print("geto ".scalar(@resources)."<br />"); 5543: my $result= <<SCANTRONFORM; 5544: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload"> 5545: <input type="hidden" name="command" value="scantron_configphase" /> 5546: $default_form_data 5547: SCANTRONFORM 5548: $r->print($result); 5549: 5550: my @delayqueue; 5551: my %completedstudents; 5552: 5553: my $count=&get_todo_count($scanlines,$scan_data); 5554: my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron Status', 5555: 'Scantron Progress',$count, 5556: 'inline',undef,'scantronupload'); 5557: &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state, 5558: 'Processing first student'); 5559: my $start=&Time::HiRes::time(); 5560: my $i=-1; 5561: my ($uname,$udom,$started); 5562: while ($i<$scanlines->{'count'}) { 5563: ($uname,$udom)=('',''); 5564: $i++; 5565: my $line=&scantron_get_line($scanlines,$scan_data,$i); 5566: if ($line=~/^[\s\cz]*$/) { next; } 5567: if ($started) { 5568: &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state, 5569: 'last student'); 5570: } 5571: $started=1; 5572: my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, 5573: $scan_data); 5574: unless ($uname=&scantron_find_student($scan_record,$scan_data, 5575: \%idmap,$i)) { 5576: &scantron_add_delay(\@delayqueue,$line, 5577: 'Unable to find a student that matches',1); 5578: next; 5579: } 5580: if (exists $completedstudents{$uname}) { 5581: &scantron_add_delay(\@delayqueue,$line, 5582: 'Student '.$uname.' has multiple sheets',2); 5583: next; 5584: } 5585: ($uname,$udom)=split(/:/,$uname); 5586: 5587: &Apache::lonxml::clear_problem_counter(); 5588: &Apache::lonnet::appenv(%$scan_record); 5589: 5590: my $i=0; 5591: foreach my $resource (@resources) { 5592: $i++; 5593: my %form=('submitted' =>'scantron', 5594: 'grade_target' =>'grade', 5595: 'grade_username'=>$uname, 5596: 'grade_domain' =>$udom, 5597: 'grade_courseid'=>$env{'request.course.id'}, 5598: 'grade_symb' =>$resource->symb()); 5599: if (exists($scan_record->{'scantron.CODE'}) && 5600: $scan_record->{'scantron.CODE'}) { 5601: $form{'CODE'}=$scan_record->{'scantron.CODE'}; 5602: } else { 5603: $form{'CODE'}=''; 5604: } 5605: my $result=&Apache::lonnet::ssi($resource->src(),%form); 5606: if ($result ne '') { 5607: &Apache::lonnet::logthis("scantron grading error -> $result"); 5608: &Apache::lonnet::logthis("scantron grading error info name $uname domain $udom course $env{'request.course.id'} url ".$resource->src()); 5609: } 5610: if (&Apache::loncommon::connection_aborted($r)) { last; } 5611: } 5612: $completedstudents{$uname}={'line'=>$line}; 5613: if (&Apache::loncommon::connection_aborted($r)) { last; } 5614: } continue { 5615: &Apache::lonxml::clear_problem_counter(); 5616: &Apache::lonnet::delenv('scantron\.'); 5617: } 5618: &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); 5619: # my $lasttime = &Time::HiRes::time()-$start; 5620: # $r->print("<p>took $lasttime</p>"); 5621: 5622: $r->print("</form>"); 5623: $r->print(&show_grading_menu_form($symb)); 5624: return ''; 5625: } 5626: 5627: sub scantron_upload_scantron_data { 5628: my ($r)=@_; 5629: $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'})); 5630: my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid', 5631: 'domainid', 5632: 'coursename'); 5633: my $domsel=&Apache::loncommon::select_dom_form($env{'request.role.domain'}, 5634: 'domainid'); 5635: my $default_form_data=&defaultFormData(&get_symb($r,1)); 5636: $r->print(<<UPLOAD); 5637: <script type="text/javascript" language="javascript"> 5638: function checkUpload(formname) { 5639: if (formname.upfile.value == "") { 5640: alert("Please use the browse button to select a file from your local directory."); 5641: return false; 5642: } 5643: formname.submit(); 5644: } 5645: </script> 5646: 5647: <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'> 5648: $default_form_data 5649: <table> 5650: <tr><td>$select_link </td></tr> 5651: <tr><td>Course ID: </td><td><input name='courseid' type='text' /> </td></tr> 5652: <tr><td>Course Name: </td><td><input name='coursename' type='text' /></td></tr> 5653: <tr><td>Domain: </td><td>$domsel </td></tr> 5654: <tr><td>File to upload:</td><td><input type="file" name="upfile" size="50" /></td></tr> 5655: </table> 5656: <input name='command' value='scantronupload_save' type='hidden' /> 5657: <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" /> 5658: </form> 5659: UPLOAD 5660: return ''; 5661: } 5662: 5663: sub scantron_upload_scantron_data_save { 5664: my($r)=@_; 5665: my ($symb)=&get_symb($r,1); 5666: my $doanotherupload= 5667: '<br /><form action="/adm/grades" method="post">'."\n". 5668: '<input type="hidden" name="command" value="scantronupload" />'."\n". 5669: '<input type="submit" name="submit" value="Do Another Upload" />'."\n". 5670: '</form>'."\n"; 5671: if (!&Apache::lonnet::allowed('usc',$env{'form.domainid'}) && 5672: !&Apache::lonnet::allowed('usc', 5673: $env{'form.domainid'}.'_'.$env{'form.courseid'})) { 5674: $r->print("You are not allowed to upload Scantron data to the requested course.<br />"); 5675: if ($symb) { 5676: $r->print(&show_grading_menu_form($symb)); 5677: } else { 5678: $r->print($doanotherupload); 5679: } 5680: return ''; 5681: } 5682: my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'}); 5683: $r->print("Doing upload to ".$coursedata{'description'}." <br />"); 5684: my $fname=$env{'form.upfile.filename'}; 5685: #FIXME 5686: #copied from lonnet::userfileupload() 5687: #make that function able to target a specified course 5688: # Replace Windows backslashes by forward slashes 5689: $fname=~s/\\/\//g; 5690: # Get rid of everything but the actual filename 5691: $fname=~s/^.*\/([^\/]+)$/$1/; 5692: # Replace spaces by underscores 5693: $fname=~s/\s+/\_/g; 5694: # Replace all other weird characters by nothing 5695: $fname=~s/[^\w\.\-]//g; 5696: # See if there is anything left 5697: unless ($fname) { return 'error: no uploaded file'; } 5698: my $uploadedfile=$fname; 5699: $fname='scantron_orig_'.$fname; 5700: if (length($env{'form.upfile'}) < 2) { 5701: $r->print("<font color='red'>Error:</font> 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."); 5702: } else { 5703: my $result=&Apache::lonnet::finishuserfileupload($env{'form.courseid'},$env{'form.domainid'},'upfile',$fname); 5704: if ($result =~ m|^/uploaded/|) { 5705: $r->print("<font color='green'>Success:</font> Successfully uploaded ".(length($env{'form.upfile'})-1)." bytes of data into location <tt>".$result."</tt>"); 5706: } else { 5707: $r->print("<font color='red'>Error:</font> An error (".$result.") occurred when attempting to upload the file, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>"); 5708: } 5709: } 5710: if ($symb) { 5711: $r->print(&scantron_selectphase($r,$uploadedfile)); 5712: } else { 5713: $r->print($doanotherupload); 5714: } 5715: return ''; 5716: } 5717: 5718: sub valid_file { 5719: my ($requested_file)=@_; 5720: foreach my $filename (sort(&scantron_filenames())) { 5721: if ($requested_file eq $filename) { return 1; } 5722: } 5723: return 0; 5724: } 5725: 5726: sub scantron_download_scantron_data { 5727: my ($r)=@_; 5728: my $default_form_data=&defaultFormData(&get_symb($r,1)); 5729: my $cname=$env{'course.'.$env{'request.course.id'}.'.num'}; 5730: my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'}; 5731: my $file=$env{'form.scantron_selectfile'}; 5732: if (! &valid_file($file)) { 5733: $r->print(<<ERROR); 5734: <p> 5735: The requested file name was invalid. 5736: </p> 5737: ERROR 5738: $r->print(&show_grading_menu_form(&get_symb($r,1))); 5739: return; 5740: } 5741: my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file; 5742: my $corrected='/uploaded/'.$cdom.'/'.$cname.'/scantron_corrected_'.$file; 5743: my $skipped='/uploaded/'.$cdom.'/'.$cname.'/scantron_skipped_'.$file; 5744: &Apache::lonnet::allowuploaded('/adm/grades',$orig); 5745: &Apache::lonnet::allowuploaded('/adm/grades',$corrected); 5746: &Apache::lonnet::allowuploaded('/adm/grades',$skipped); 5747: $r->print(<<DOWNLOAD); 5748: <p> 5749: <a href="$orig">Original</a> file as uploaded by the scantron office. 5750: </p> 5751: <p> 5752: <a href="$corrected">Corrections</a>, a file of corrected records that were used in grading. 5753: </p> 5754: <p> 5755: <a href="$skipped">Skipped</a>, a file of records that were skipped. 5756: </p> 5757: DOWNLOAD 5758: $r->print(&show_grading_menu_form(&get_symb($r,1))); 5759: return ''; 5760: } 5761: 5762: #-------- end of section for handling grading scantron forms ------- 5763: # 5764: #------------------------------------------------------------------- 5765: 5766: #-------------------------- Menu interface ------------------------- 5767: # 5768: #--- Show a Grading Menu button - Calls the next routine --- 5769: sub show_grading_menu_form { 5770: my ($symb)=@_; 5771: my $result.='<br /><form action="/adm/grades" method="post">'."\n". 5772: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 5773: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n". 5774: '<input type="hidden" name="command" value="gradingmenu" />'."\n". 5775: '<input type="submit" name="submit" value="Grading Menu" />'."\n". 5776: '</form>'."\n"; 5777: return $result; 5778: } 5779: 5780: # -- Retrieve choices for grading form 5781: sub savedState { 5782: my %savedState = (); 5783: if ($env{'form.saveState'}) { 5784: foreach (split(/:/,$env{'form.saveState'})) { 5785: my ($key,$value) = split(/=/,$_,2); 5786: $savedState{$key} = $value; 5787: } 5788: } 5789: return \%savedState; 5790: } 5791: 5792: #--- Displays the main menu page ------- 5793: sub gradingmenu { 5794: my ($request) = @_; 5795: my ($symb)=&get_symb($request); 5796: if (!$symb) {return '';} 5797: my $probTitle = &Apache::lonnet::gettitle($symb); 5798: 5799: $request->print(<<GRADINGMENUJS); 5800: <script type="text/javascript" language="javascript"> 5801: function checkChoice(formname,val,cmdx) { 5802: if (val <= 2) { 5803: var cmd = radioSelection(formname.radioChoice); 5804: var cmdsave = cmd; 5805: } else { 5806: cmd = cmdx; 5807: cmdsave = 'submission'; 5808: } 5809: formname.command.value = cmd; 5810: formname.saveState.value = "saveCmd="+cmdsave+":saveSec="+pullDownSelection(formname.section)+ 5811: ":saveSub="+pullDownSelection(formname.submitonly)+":saveStatus="+pullDownSelection(formname.Status); 5812: if (val < 5) formname.submit(); 5813: if (val == 5) { 5814: if (!checkReceiptNo(formname,'notOK')) { return false;} 5815: formname.submit(); 5816: } 5817: if (val < 7) formname.submit(); 5818: } 5819: 5820: function checkReceiptNo(formname,nospace) { 5821: var receiptNo = formname.receipt.value; 5822: var checkOpt = false; 5823: if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;} 5824: if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;} 5825: if (checkOpt) { 5826: alert("Please enter a receipt number given by a student in the receipt box."); 5827: formname.receipt.value = ""; 5828: formname.receipt.focus(); 5829: return false; 5830: } 5831: return true; 5832: } 5833: </script> 5834: GRADINGMENUJS 5835: &commonJSfunctions($request); 5836: my $result='<h3> <font color="#339933">Manual Grading/View Submission</font></h3>'; 5837: my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle); 5838: $result.=$table; 5839: my (undef,$sections) = &getclasslist('all','0'); 5840: my $savedState = &savedState(); 5841: my $saveCmd = ($$savedState{'saveCmd'} eq '' ? 'submission' : $$savedState{'saveCmd'}); 5842: my $saveSec = ($$savedState{'saveSec'} eq '' ? 'all' : $$savedState{'saveSec'}); 5843: my $saveSub = ($$savedState{'saveSub'} eq '' ? 'all' : $$savedState{'saveSub'}); 5844: my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'}); 5845: 5846: $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n". 5847: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 5848: '<input type="hidden" name="handgrade" value="'.$hdgrade.'" />'."\n". 5849: '<input type="hidden" name="probTitle" value="'.$probTitle.'" />'."\n". 5850: '<input type="hidden" name="command" value="" />'."\n". 5851: '<input type="hidden" name="saveState" value="" />'."\n". 5852: '<input type="hidden" name="gradingMenu" value="1" />'."\n". 5853: '<input type="hidden" name="showgrading" value="yes" />'."\n"; 5854: 5855: $result.='<table width="100%" border="0"><tr><td bgcolor=#777777>'."\n". 5856: '<table width="100%" border="0"><tr bgcolor="#e6ffff"><td colspan="2">'."\n". 5857: ' <b>Select a Grading/Viewing Option</b></td></tr>'."\n". 5858: '<tr bgcolor="#ffffe6" valign="top"><td>'."\n"; 5859: 5860: $result.='<table width="100%" border="0">'; 5861: $result.='<tr bgcolor="#ffffe6" valign="top"><td>'."\n". 5862: ' '.&mt('Select Section').': <select name="section">'."\n"; 5863: if (ref($sections)) { 5864: foreach (sort (@$sections)) { 5865: $result.='<option value="'.$_.'" '. 5866: ($saveSec eq $_ ? 'selected="on"':'').'>'.$_.'</option>'."\n"; 5867: } 5868: } 5869: $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="on"' : ''). '>all</option></select> '; 5870: 5871: $result.=&mt('Student Status').':</b>'.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,undef); 5872: 5873: $result.='</td></tr>'; 5874: 5875: $result.='<tr bgcolor="#ffffe6"valign="top"><td><label>'. 5876: '<input type="radio" name="radioChoice" value="submission" '. 5877: ($saveCmd eq 'submission' ? 'checked' : '').' /> '.'<b>'.&mt('Current Resource').':</b> '.&mt('For one or more students'). 5878: '</label> <select name="submitonly">'. 5879: '<option value="yes" '. 5880: ($saveSub eq 'yes' ? 'selected="on"' : '').' />'.&mt('with submissions').'</option>'. 5881: '<option value="queued" '. 5882: ($saveSub eq 'queued' ? 'selected="on"' : '').' />'.&mt('in grading queue').'</option>'. 5883: '<option value="graded" '. 5884: ($saveSub eq 'graded' ? 'selected="on"' : '').' />'.&mt('with ungraded submissions').'</option>'. 5885: '<option value="incorrect" '. 5886: ($saveSub eq 'incorrect' ? 'selected="on"' : '').' />'.&mt('with incorrect submissions').'</option>'. 5887: '<option value="all" '. 5888: ($saveSub eq 'all' ? 'selected="on"' : '').' />'.&mt('with any status').'</option></select></td></tr>'."\n"; 5889: 5890: $result.='<tr bgcolor="#ffffe6"valign="top"><td>'. 5891: '<label><input type="radio" name="radioChoice" value="viewgrades" '. 5892: ($saveCmd eq 'viewgrades' ? 'checked' : '').' /> '. 5893: '<b>Current Resource:</b> For all students in selected section or course</label></td></tr>'."\n"; 5894: 5895: $result.='<tr bgcolor="#ffffe6" valign="top"><td>'. 5896: '<label><input type="radio" name="radioChoice" value="pickStudentPage" '. 5897: ($saveCmd eq 'pickStudentPage' ? 'checked' : '').' /> '. 5898: 'The <b>complete</b> set/page/sequence: For one student</label></td></tr>'."\n"; 5899: 5900: $result.='<tr bgcolor="#ffffe6"><td><br />'. 5901: '<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'. 5902: '</td></tr></table>'."\n"; 5903: 5904: $result.='</td><td valign="top">'; 5905: 5906: $result.='<table width="100%" border="0">'; 5907: $result.='<tr bgcolor="#ffffe6"><td>'. 5908: '<input type="button" onClick="javascript:checkChoice(this.form,\'3\',\'csvform\');" value="'.&mt('Upload').'" />'. 5909: ' '.&mt('scores from file').' </td></tr>'."\n"; 5910: 5911: $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'. 5912: '<input type="button" onClick="javascript:checkChoice(this.form,\'4\',\'scantron_selectphase\');'. 5913: '" value="'.&mt('Grade').'" /> scantron forms</td></tr>'."\n"; 5914: 5915: if ((&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) && ($symb)) { 5916: $result.='<tr bgcolor="#ffffe6"valign="top"><td>'. 5917: '<input type="button" onClick="javascript:checkChoice(this.form,\'5\',\'verify\');" value="'.&mt('Verify').'" />'. 5918: ' '.&mt('receipt').': '. 5919: &Apache::lonnet::recprefix($env{'request.course.id'}). 5920: '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />'. 5921: '</td></tr>'."\n"; 5922: } 5923: $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'. 5924: '<input type="button" onClick="javascript:this.form.action=\'/adm/helper/resettimes.helper\';this.form.submit();'. 5925: '" value="'.&mt('Manage').'" /> access times.</td></tr>'."\n"; 5926: $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'. 5927: '<input type="button" onClick="javascript:this.form.command.value=\'codelist\';this.form.action=\'/adm/pickcode\';this.form.submit();'. 5928: '" value="'.&mt('View').'" /> saved CODEs.</td></tr>'."\n"; 5929: 5930: $result.='</form></td></tr></table>'."\n". 5931: '</td></tr></table>'."\n". 5932: '</td></tr></table>'."\n"; 5933: return $result; 5934: } 5935: 5936: sub reset_perm { 5937: undef(%perm); 5938: } 5939: 5940: sub init_perm { 5941: &reset_perm(); 5942: foreach my $test_perm ('vgr','mgr','opa') { 5943: 5944: my $scope = $env{'request.course.id'}; 5945: if (!($perm{$test_perm}=&Apache::lonnet::allowed($test_perm,$scope))) { 5946: 5947: $scope .= '/'.$env{'request.course.sec'}; 5948: if ( $perm{$test_perm}= 5949: &Apache::lonnet::allowed($test_perm,$scope)) { 5950: $perm{$test_perm.'_section'}=$env{'request.course.sec'}; 5951: } else { 5952: delete($perm{$test_perm}); 5953: } 5954: } 5955: } 5956: } 5957: 5958: sub handler { 5959: my $request=$_[0]; 5960: 5961: &reset_perm(); 5962: if ($env{'browser.mathml'}) { 5963: &Apache::loncommon::content_type($request,'text/xml'); 5964: } else { 5965: &Apache::loncommon::content_type($request,'text/html'); 5966: } 5967: $request->send_http_header; 5968: return '' if $request->header_only; 5969: &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}); 5970: my $symb=&get_symb($request,1); 5971: my @commands=&Apache::loncommon::get_env_multiple('form.command'); 5972: my $command=$commands[0]; 5973: if ($#commands > 0) { 5974: &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands)); 5975: } 5976: $request->print(&Apache::loncommon::start_page('Grading')); 5977: if ($symb eq '' && $command eq '') { 5978: if ($env{'user.adv'}) { 5979: if (($env{'form.codeone'}) && ($env{'form.codetwo'}) && 5980: ($env{'form.codethree'})) { 5981: my $token=$env{'form.codeone'}.'*'.$env{'form.codetwo'}.'*'. 5982: $env{'form.codethree'}; 5983: my ($tsymb,$tuname,$tudom,$tcrsid)= 5984: &Apache::lonnet::checkin($token); 5985: if ($tsymb) { 5986: my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb); 5987: if (&Apache::lonnet::allowed('mgr',$tcrsid)) { 5988: $request->print(&Apache::lonnet::ssi_body('/res/'.$url, 5989: ('grade_username' => $tuname, 5990: 'grade_domain' => $tudom, 5991: 'grade_courseid' => $tcrsid, 5992: 'grade_symb' => $tsymb))); 5993: } else { 5994: $request->print('<h3>Not authorized: '.$token.'</h3>'); 5995: } 5996: } else { 5997: $request->print('<h3>Not a valid DocID: '.$token.'</h3>'); 5998: } 5999: } else { 6000: $request->print(&Apache::lonxml::tokeninputfield()); 6001: } 6002: } 6003: } else { 6004: &init_perm(); 6005: if ($command eq 'submission' && $perm{'vgr'}) { 6006: ($env{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0)); 6007: } elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) { 6008: &pickStudentPage($request); 6009: } elsif ($command eq 'displayPage' && $perm{'vgr'}) { 6010: &displayPage($request); 6011: } elsif ($command eq 'gradeByPage' && $perm{'mgr'}) { 6012: &updateGradeByPage($request); 6013: } elsif ($command eq 'processGroup' && $perm{'vgr'}) { 6014: &processGroup($request); 6015: } elsif ($command eq 'gradingmenu' && $perm{'vgr'}) { 6016: $request->print(&gradingmenu($request)); 6017: } elsif ($command eq 'viewgrades' && $perm{'vgr'}) { 6018: $request->print(&viewgrades($request)); 6019: } elsif ($command eq 'handgrade' && $perm{'mgr'}) { 6020: $request->print(&processHandGrade($request)); 6021: } elsif ($command eq 'editgrades' && $perm{'mgr'}) { 6022: $request->print(&editgrades($request)); 6023: } elsif ($command eq 'verify' && $perm{'vgr'}) { 6024: $request->print(&verifyreceipt($request)); 6025: } elsif ($command eq 'csvform' && $perm{'mgr'}) { 6026: $request->print(&upcsvScores_form($request)); 6027: } elsif ($command eq 'csvupload' && $perm{'mgr'}) { 6028: $request->print(&csvupload($request)); 6029: } elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) { 6030: $request->print(&csvuploadmap($request)); 6031: } elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) { 6032: if ($env{'form.associate'} ne 'Reverse Association') { 6033: $request->print(&csvuploadoptions($request)); 6034: } else { 6035: if ( $env{'form.upfile_associate'} ne 'reverse' ) { 6036: $env{'form.upfile_associate'} = 'reverse'; 6037: } else { 6038: $env{'form.upfile_associate'} = 'forward'; 6039: } 6040: $request->print(&csvuploadmap($request)); 6041: } 6042: } elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) { 6043: $request->print(&csvuploadassign($request)); 6044: } elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) { 6045: $request->print(&scantron_selectphase($request)); 6046: } elsif ($command eq 'scantron_warning' && $perm{'mgr'}) { 6047: $request->print(&scantron_do_warning($request)); 6048: } elsif ($command eq 'scantron_validate' && $perm{'mgr'}) { 6049: $request->print(&scantron_validate_file($request)); 6050: } elsif ($command eq 'scantron_process' && $perm{'mgr'}) { 6051: $request->print(&scantron_process_students($request)); 6052: } elsif ($command eq 'scantronupload' && 6053: (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})|| 6054: &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) { 6055: $request->print(&scantron_upload_scantron_data($request)); 6056: } elsif ($command eq 'scantronupload_save' && 6057: (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})|| 6058: &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) { 6059: $request->print(&scantron_upload_scantron_data_save($request)); 6060: } elsif ($command eq 'scantron_download' && 6061: &Apache::lonnet::allowed('usc',$env{'request.course.id'})) { 6062: $request->print(&scantron_download_scantron_data($request)); 6063: } elsif ($command) { 6064: $request->print("Access Denied ($command)"); 6065: } 6066: } 6067: $request->print(&Apache::loncommon::end_page()); 6068: return ''; 6069: } 6070: 6071: 1; 6072: 6073: __END__;