![]() ![]() | ![]() |
- consoladating the 3 different &propath()s
1: # The LearningOnline Network with CAPA 2: # The LON-CAPA Grading handler 3: # 4: # $Id: grades.pm,v 1.159 2003/11/12 21:37:07 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: # 2/9,2/13 Guy Albertelli 29: # 6/8 Gerd Kortemeyer 30: # 7/26 H.K. Ng 31: # 8/20 Gerd Kortemeyer 32: # Year 2002 33: # June-August H.K. Ng 34: # Year 2003 35: # February, March H.K. Ng 36: # July, H. K. Ng 37: # 38: 39: package Apache::grades; 40: use strict; 41: use Apache::style; 42: use Apache::lonxml; 43: use Apache::lonnet; 44: use Apache::loncommon; 45: use Apache::lonhtmlcommon; 46: use Apache::lonnavmaps; 47: use Apache::lonhomework; 48: use Apache::loncoursedata; 49: use Apache::lonmsg qw(:user_normal_msg); 50: use Apache::Constants qw(:common); 51: use String::Similarity; 52: 53: my %oldessays=(); 54: my %perm=(); 55: 56: # ----- These first few routines are general use routines.---- 57: # 58: # --- Retrieve the parts from the metadata file.--- 59: sub getpartlist { 60: my ($url,$symb) = @_; 61: my $partorder = &Apache::lonnet::metadata($url, 'partorder'); 62: my @parts; 63: if ($partorder) { 64: for my $part (split (/,/,$partorder)) { 65: if (!&Apache::loncommon::check_if_partid_hidden($part,$symb)) { 66: push(@parts, $part); 67: } 68: } 69: } else { 70: my $metadata = &Apache::lonnet::metadata($url, 'packages'); 71: foreach (split(/\,/,$metadata)) { 72: if ($_ =~ /^part_(.*)$/) { 73: if (!&Apache::loncommon::check_if_partid_hidden($1,$symb)) { 74: push(@parts, $1); 75: } 76: } 77: } 78: } 79: my @stores; 80: foreach my $part (@parts) { 81: my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys')); 82: foreach my $key (@metakeys) { 83: if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); } 84: } 85: } 86: return @stores; 87: } 88: 89: # --- Get the symbolic name of a problem and the url 90: sub get_symb_and_url { 91: my ($request) = @_; 92: (my $url=$ENV{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--; 93: my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url))); 94: if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; } 95: return ($symb,$url); 96: } 97: 98: # --- Retrieve the fullname for a user. Return lastname, first middle --- 99: # --- Generation is attached next to the lastname if it exists. --- 100: sub get_fullname { 101: my ($uname,$udom) = @_; 102: my %name=&Apache::lonnet::get('environment', ['lastname','generation', 103: 'firstname','middlename'], 104: $udom,$uname); 105: my $fullname; 106: my ($tmp) = keys(%name); 107: if ($tmp !~ /^(con_lost|error|no_such_host)/i) { 108: $fullname = &Apache::loncoursedata::ProcessFullName 109: (@name{qw/lastname generation firstname middlename/}); 110: } else { 111: &Apache::lonnet::logthis('grades.pm: no name data for '.$uname. 112: '@'.$udom.':'.$tmp); 113: } 114: return $fullname; 115: } 116: 117: #--- Format fullname, username:domain if different for display 118: #--- Use anywhere where the student names are listed 119: sub nameUserString { 120: my ($type,$fullname,$uname,$udom) = @_; 121: if ($type eq 'header') { 122: return '<b> Fullname </b><font color="#999999">(Username)</font> '; 123: } else { 124: return ' '.$fullname.'<font color="#999999"> ('.$uname. 125: ($ENV{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</font>'; 126: } 127: } 128: 129: #--- Get the partlist and the response type for a given problem. --- 130: #--- Indicate if a response type is coded handgraded or not. --- 131: sub response_type { 132: my ($url,$symb) = shift; 133: $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url))) if ($symb eq ''); 134: my $allkeys = &Apache::lonnet::metadata($url,'keys'); 135: my %vPart; 136: foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) { 137: $vPart{$partid}=1; 138: } 139: my %seen = (); 140: my (@partlist,%handgrade,%responseType); 141: foreach (split(/,/,&Apache::lonnet::metadata($url,'packages'))) { 142: if (/^\w+response_.*/) { 143: my ($responsetype,$part) = split(/_/,$_,2); 144: my ($partid,$respid) = split(/_/,$part); 145: if (&Apache::loncommon::check_if_partid_hidden($partid,$symb)) { 146: next; 147: } 148: if (%vPart && !exists($vPart{$partid})) { 149: next; 150: } 151: $responsetype =~ s/response$//; # make it compatible w/ navmaps - should move to that!! 152: my ($value) = &Apache::lonnet::EXT('resource.'.$part.'.handgrade',$symb); 153: $handgrade{$part} = ($value eq 'yes' ? 'yes' : 'no'); 154: if (!exists($responseType{$partid})) { $responseType{$partid}={}; } 155: $responseType{$partid}->{$respid}=$responsetype; 156: next if ($seen{$partid} > 0); 157: $seen{$partid}++; 158: push @partlist,$partid; 159: } 160: } 161: return \@partlist,\%handgrade,\%responseType; 162: } 163: 164: #--- Show resource title 165: #--- and parts and response type 166: sub showResourceInfo { 167: my ($url,$probTitle,$checkboxes) = @_; 168: my $col=3; 169: if ($checkboxes) { $col=4; } 170: my $result ='<table border="0">'. 171: '<tr><td colspan="'.$col.'"><font size="+1"><b>Current Resource: </b>'. 172: $probTitle.'</font></td></tr>'."\n"; 173: my ($partlist,$handgrade,$responseType) = &response_type($url); 174: my %resptype = (); 175: my $hdgrade='no'; 176: my %partsseen; 177: for my $part_resID (sort keys(%$handgrade)) { 178: my $handgrade=$$handgrade{$part_resID}; 179: my ($partID,$resID) = split(/_/,$part_resID); 180: my $responsetype = $responseType->{$partID}->{$resID}; 181: $hdgrade = $handgrade if ($handgrade eq 'yes'); 182: $result.='<tr>'; 183: if ($checkboxes) { 184: if (exists($partsseen{$partID})) { 185: $result.="<td> </td>"; 186: } else { 187: $result.="<td><input type='checkbox' name='vPart' value='$partID' checked='on' /></td>"; 188: } 189: $partsseen{$partID}=1; 190: } 191: $result.='<td><b>Part </b>'.$partID.' <font color="#999999">'. 192: $resID.'</font></td>'. 193: '<td><b>Type: </b>'.$responsetype.'</td></tr>'; 194: # '<td><b>Handgrade: </b>'.$handgrade.'</td></tr>'; 195: } 196: $result.='</table>'."\n"; 197: return $result,$responseType,$hdgrade,$partlist,$handgrade; 198: } 199: 200: 201: sub get_order { 202: my ($partid,$respid,$symb,$uname,$udom)=@_; 203: my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb); 204: $url=&Apache::lonnet::clutter($url); 205: my $subresult=&Apache::lonnet::ssi($url, 206: ('grade_target' => 'analyze'), 207: ('grade_domain' => $udom), 208: ('grade_symb' => $symb), 209: ('grade_courseid' => 210: $ENV{'request.course.id'}), 211: ('grade_username' => $uname)); 212: (undef,$subresult)=split(/_HASH_REF__/,$subresult,2); 213: my %analyze=&Apache::lonnet::str2hash($subresult); 214: return ($analyze{"$partid.$respid.shown"}); 215: } 216: #--- Clean response type for display 217: #--- Currently filters option/rank/radiobutton/match/essay response types only. 218: sub cleanRecord { 219: my ($answer,$response,$symb,$partid,$respid,$record,$order,$version) = @_; 220: my $grayFont = '<font color="#999999">'; 221: if ($response =~ /^(option|rank)$/) { 222: my %answer=&Apache::lonnet::str2hash($answer); 223: my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"}); 224: my ($toprow,$bottomrow); 225: foreach my $foil (@$order) { 226: if ($grading{$foil} == 1) { 227: $toprow.='<td><b>'.$answer{$foil}.' </b></td>'; 228: } else { 229: $toprow.='<td><i>'.$answer{$foil}.' </i></td>'; 230: } 231: $bottomrow.='<td>'.$grayFont.$foil.'</font> </td>'; 232: } 233: return '<blockquote><table border="1">'. 234: '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'. 235: '<tr valign="top"><td>'.$grayFont.'Option ID</font></td>'. 236: $grayFont.$bottomrow.'</tr>'.'</table></blockquote>'; 237: } elsif ($response eq 'match') { 238: my %answer=&Apache::lonnet::str2hash($answer); 239: my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"}); 240: my @items=&Apache::lonnet::str2array($record->{$version."resource.$partid.$respid.submissionitems"}); 241: my ($toprow,$middlerow,$bottomrow); 242: foreach my $foil (@$order) { 243: my $item=shift(@items); 244: if ($grading{$foil} == 1) { 245: $toprow.='<td><b>'.$item.' </b></td>'; 246: $middlerow.='<td><b>'.$grayFont.$answer{$foil}.' </font></b></td>'; 247: } else { 248: $toprow.='<td><i>'.$item.' </i></td>'; 249: $middlerow.='<td><i>'.$grayFont.$answer{$foil}.' </font></i></td>'; 250: } 251: $bottomrow.='<td>'.$grayFont.$foil.'</font> </td>'; 252: } 253: return '<blockquote><table border="1">'. 254: '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'. 255: '<tr valign="top"><td>'.$grayFont.'Item ID</font></td>'. 256: $middlerow.'</tr>'. 257: '<tr valign="top"><td>'.$grayFont.'Option ID</font></td>'. 258: $bottomrow.'</tr>'.'</table></blockquote>'; 259: } elsif ($response eq 'radiobutton') { 260: my %answer=&Apache::lonnet::str2hash($answer); 261: my ($toprow,$bottomrow); 262: my $correct=($order->[0])+1; 263: for (my $i=1;$i<=$#$order;$i++) { 264: my $foil=$order->[$i]; 265: if (exists($answer{$foil})) { 266: if ($i == $correct) { 267: $toprow.='<td><b>true</b></td>'; 268: } else { 269: $toprow.='<td><i>true</i></td>'; 270: } 271: } else { 272: $toprow.='<td>false</td>'; 273: } 274: $bottomrow.='<td>'.$grayFont.$foil.'</font> </td>'; 275: } 276: return '<blockquote><table border="1">'. 277: '<tr valign="top"><td>Answer</td>'.$toprow.'</tr>'. 278: '<tr valign="top"><td>'.$grayFont.'Option ID</font></td>'. 279: $grayFont.$bottomrow.'</tr>'.'</table></blockquote>'; 280: } elsif ($response eq 'essay') { 281: if (! exists ($ENV{'form.'.$symb})) { 282: my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade', 283: $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, 284: $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); 285: 286: my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'}; 287: $ENV{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : ''; 288: $ENV{'form.kwclr'} = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red'; 289: $ENV{'form.kwsize'} = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0'; 290: $ENV{'form.kwstyle'} = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : ''; 291: $ENV{'form.'.$symb} = 1; # so that we don't have to read it from disk for multiple sub of the same prob. 292: } 293: return '<br /><br /><blockquote><pre>'.&keywords_highlight($answer).'</pre></blockquote>'; 294: } 295: return $answer; 296: } 297: 298: #-- A couple of common js functions 299: sub commonJSfunctions { 300: my $request = shift; 301: $request->print(<<COMMONJSFUNCTIONS); 302: <script type="text/javascript" language="javascript"> 303: function radioSelection(radioButton) { 304: var selection=null; 305: if (radioButton.length > 1) { 306: for (var i=0; i<radioButton.length; i++) { 307: if (radioButton[i].checked) { 308: return radioButton[i].value; 309: } 310: } 311: } else { 312: if (radioButton.checked) return radioButton.value; 313: } 314: return selection; 315: } 316: 317: function pullDownSelection(selectOne) { 318: var selection=""; 319: if (selectOne.length > 1) { 320: for (var i=0; i<selectOne.length; i++) { 321: if (selectOne[i].selected) { 322: return selectOne[i].value; 323: } 324: } 325: } else { 326: // only one value it must be the selected one 327: return selectOne.value; 328: } 329: } 330: </script> 331: COMMONJSFUNCTIONS 332: } 333: 334: #--- Dumps the class list with usernames,list of sections, 335: #--- section, ids and fullnames for each user. 336: sub getclasslist { 337: my ($getsec,$filterlist) = @_; 338: $getsec = $getsec eq '' ? 'all' : $getsec; 339: my $classlist=&Apache::loncoursedata::get_classlist(); 340: # Bail out if we were unable to get the classlist 341: return if (! defined($classlist)); 342: # 343: my %sections; 344: my %fullnames; 345: foreach (keys(%$classlist)) { 346: # the following undefs are for 'domain', and 'username' respectively. 347: my (undef,undef,$end,$start,$id,$section,$fullname,$status)= 348: @{$classlist->{$_}}; 349: # filter students according to status selected 350: if ($filterlist && $ENV{'form.Status'} ne 'Any') { 351: if ($ENV{'form.Status'} ne $status) { 352: delete ($classlist->{$_}); 353: next; 354: } 355: } 356: $section = ($section ne '' ? $section : 'no'); 357: if (&canview($section)) { 358: if ($getsec eq 'all' || $getsec eq $section) { 359: $sections{$section}++; 360: $fullnames{$_}=$fullname; 361: } else { 362: delete($classlist->{$_}); 363: } 364: } else { 365: delete($classlist->{$_}); 366: } 367: } 368: my %seen = (); 369: my @sections = sort(keys(%sections)); 370: return ($classlist,\@sections,\%fullnames); 371: } 372: 373: sub canmodify { 374: my ($sec)=@_; 375: if ($perm{'mgr'}) { 376: if (!defined($perm{'mgr_section'})) { 377: # can modify whole class 378: return 1; 379: } else { 380: if ($sec eq $perm{'mgr_section'}) { 381: #can modify the requested section 382: return 1; 383: } else { 384: # can't modify the request section 385: return 0; 386: } 387: } 388: } 389: #can't modify 390: return 0; 391: } 392: 393: sub canview { 394: my ($sec)=@_; 395: if ($perm{'vgr'}) { 396: if (!defined($perm{'vgr_section'})) { 397: # can modify whole class 398: return 1; 399: } else { 400: if ($sec eq $perm{'vgr_section'}) { 401: #can modify the requested section 402: return 1; 403: } else { 404: # can't modify the request section 405: return 0; 406: } 407: } 408: } 409: #can't modify 410: return 0; 411: } 412: 413: #--- Retrieve the grade status of a student for all the parts 414: sub student_gradeStatus { 415: my ($url,$symb,$udom,$uname,$partlist) = @_; 416: my %record = &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname); 417: my %partstatus = (); 418: foreach (@$partlist) { 419: my ($status,undef) = split(/_/,$record{"resource.$_.solved"},2); 420: $status = 'nothing' if ($status eq ''); 421: $partstatus{$_} = $status; 422: my $subkey = "resource.$_.submitted_by"; 423: $partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne ''); 424: } 425: return %partstatus; 426: } 427: 428: # hidden form and javascript that calls the form 429: # Use by verifyscript and viewgrades 430: # Shows a student's view of problem and submission 431: sub jscriptNform { 432: my ($url,$symb) = @_; 433: my $jscript='<script type="text/javascript" language="javascript">'."\n". 434: ' function viewOneStudent(user,domain) {'."\n". 435: ' document.onestudent.student.value = user;'."\n". 436: ' document.onestudent.userdom.value = domain;'."\n". 437: ' document.onestudent.submit();'."\n". 438: ' }'."\n". 439: '</script>'."\n"; 440: $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\n". 441: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 442: '<input type="hidden" name="url" value="'.$url.'" />'."\n". 443: '<input type="hidden" name="saveState" value="'.$ENV{'form.saveState'}.'" />'."\n". 444: '<input type="hidden" name="probTitle" value="'.$ENV{'form.probTitle'}.'" />'."\n". 445: '<input type="hidden" name="Status" value="'.$ENV{'form.Status'}.'" />'."\n". 446: '<input type="hidden" name="command" value="submission" />'."\n". 447: '<input type="hidden" name="student" value="" />'."\n". 448: '<input type="hidden" name="userdom" value="" />'."\n". 449: '</form>'."\n"; 450: return $jscript; 451: } 452: 453: #------------------ End of general use routines -------------------- 454: 455: # 456: # Find most similar essay 457: # 458: 459: sub most_similar { 460: my ($uname,$udom,$uessay)=@_; 461: 462: # ignore spaces and punctuation 463: 464: $uessay=~s/\W+/ /gs; 465: 466: # these will be returned. Do not care if not at least 50 percent similar 467: my $limit=0.6; 468: my $sname=''; 469: my $sdom=''; 470: my $scrsid=''; 471: my $sessay=''; 472: # go through all essays ... 473: foreach my $tkey (keys %oldessays) { 474: my ($tname,$tdom,$tcrsid)=split(/\./,$tkey); 475: # ... except the same student 476: if (($tname ne $uname) || ($tdom ne $udom)) { 477: my $tessay=$oldessays{$tkey}; 478: $tessay=~s/\W+/ /gs; 479: # String similarity gives up if not even limit 480: my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit); 481: # Found one 482: if ($tsimilar>$limit) { 483: $limit=$tsimilar; 484: $sname=$tname; 485: $sdom=$tdom; 486: $scrsid=$tcrsid; 487: $sessay=$oldessays{$tkey}; 488: } 489: } 490: } 491: if ($limit>0.6) { 492: return ($sname,$sdom,$scrsid,$sessay,$limit); 493: } else { 494: return ('','','','',0); 495: } 496: } 497: 498: #------------------------------------------------------------------- 499: 500: #------------------------------------ Receipt Verification Routines 501: # 502: #--- Check whether a receipt number is valid.--- 503: sub verifyreceipt { 504: my $request = shift; 505: 506: my $courseid = $ENV{'request.course.id'}; 507: my $receipt = unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'}).'-'. 508: $ENV{'form.receipt'}; 509: $receipt =~ s/[^\-\d]//g; 510: my $url = $ENV{'form.url'}; 511: my $symb = $ENV{'form.symb'}; 512: unless ($symb) { 513: $symb = &Apache::lonnet::symbread($url); 514: } 515: 516: my $title.='<h3><font color="#339933">Verifying Submission Receipt '. 517: $receipt.'</h3></font>'."\n". 518: '<font size=+1><b>Resource: </b>'.$ENV{'form.probTitle'}.'</font><br><br>'."\n"; 519: 520: my ($string,$contents,$matches) = ('','',0); 521: my (undef,undef,$fullname) = &getclasslist('all','0'); 522: 523: foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) { 524: my ($uname,$udom)=split(/\:/); 525: if ($receipt eq 526: &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb)) { 527: $contents.='<tr bgcolor="#ffffe6"><td> '."\n". 528: '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom. 529: '\')"; TARGET=_self>'.$$fullname{$_}.'</a> </td>'."\n". 530: '<td> '.$uname.' </td>'. 531: '<td> '.$udom.' </td></tr>'."\n"; 532: 533: $matches++; 534: } 535: } 536: if ($matches == 0) { 537: $string = $title.'No match found for the above receipt.'; 538: } else { 539: $string = &jscriptNform($url,$symb).$title. 540: 'The above receipt matches the following student'. 541: ($matches <= 1 ? '.' : 's.')."\n". 542: '<table border="0"><tr><td bgcolor="#777777">'."\n". 543: '<table border="0"><tr bgcolor="#e6ffff">'."\n". 544: '<td><b> Fullname </b></td>'."\n". 545: '<td><b> Username </b></td>'."\n". 546: '<td><b> Domain </b></td></tr>'."\n". 547: $contents. 548: '</table></td></tr></table>'."\n"; 549: } 550: return $string.&show_grading_menu_form($symb,$url); 551: } 552: 553: #--- This is called by a number of programs. 554: #--- Called from the Grading Menu - View/Grade an individual student 555: #--- Also called directly when one clicks on the subm button 556: # on the problem page. 557: sub listStudents { 558: my ($request) = shift; 559: 560: my ($symb,$url) = &get_symb_and_url($request); 561: my $cdom = $ENV{"course.$ENV{'request.course.id'}.domain"}; 562: my $cnum = $ENV{"course.$ENV{'request.course.id'}.num"}; 563: my $getsec = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'}; 564: my $submitonly= $ENV{'form.submitonly'} eq '' ? 'all' : $ENV{'form.submitonly'}; 565: 566: my $viewgrade = $ENV{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View'; 567: $ENV{'form.probTitle'} = $ENV{'form.probTitle'} eq '' ? 568: &Apache::lonnet::gettitle($symb) : $ENV{'form.probTitle'}; 569: 570: my $result='<h3><font color="#339933"> '.$viewgrade. 571: ' Submissions for a Student or a Group of Students</font></h3>'; 572: 573: my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($url,$ENV{'form.probTitle'},($ENV{'form.showgrading'} eq 'yes')); 574: 575: $request->print(<<LISTJAVASCRIPT); 576: <script type="text/javascript" language="javascript"> 577: function checkSelect(checkBox) { 578: var ctr=0; 579: var sense=""; 580: if (checkBox.length > 1) { 581: for (var i=0; i<checkBox.length; i++) { 582: if (checkBox[i].checked) { 583: ctr++; 584: } 585: } 586: sense = "a student or group of students"; 587: } else { 588: if (checkBox.checked) { 589: ctr = 1; 590: } 591: sense = "the student"; 592: } 593: if (ctr == 0) { 594: alert("Please select "+sense+" before clicking on the Next button."); 595: return false; 596: } 597: document.gradesub.submit(); 598: } 599: 600: function reLoadList(formname) { 601: if (formname.saveStatusOld.value == pullDownSelection(formname.Status)) {return;} 602: formname.command.value = 'submission'; 603: formname.submit(); 604: } 605: </script> 606: LISTJAVASCRIPT 607: 608: &commonJSfunctions($request); 609: $request->print($result); 610: 611: my $checkhdgrade = ($ENV{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1 ) ? 'checked' : ''; 612: my $checklastsub = $checkhdgrade eq '' ? 'checked' : ''; 613: my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'. 614: "\n".$table. 615: ' <b>View Problem Text: </b><input type="radio" name="vProb" value="no" checked="on" /> no '."\n". 616: '<input type="radio" name="vProb" value="yes" /> one student '."\n". 617: '<input type="radio" name="vProb" value="all" /> all students <br />'."\n". 618: ' <b>View Answer: </b><input type="radio" name="vAns" value="no" /> no '."\n". 619: '<input type="radio" name="vAns" value="yes" /> one student '."\n". 620: '<input type="radio" name="vAns" value="all" checked="on" /> all students <br />'."\n". 621: ' <b>Submissions: </b>'."\n"; 622: if ($ENV{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) { 623: $gradeTable.='<input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> essay part only'."\n"; 624: } 625: 626: my $saveStatus = $ENV{'form.Status'} eq '' ? 'Active' : $ENV{'form.Status'}; 627: $ENV{'form.Status'} = $saveStatus; 628: 629: $gradeTable.='<input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> last submission only'."\n". 630: '<input type="radio" name="lastSub" value="last" /> last submission & parts info'."\n". 631: '<input type="radio" name="lastSub" value="datesub" /> by dates and submissions'."\n". 632: '<input type="radio" name="lastSub" value="all" /> all details'."\n". 633: '<input type="hidden" name="section" value="'.$getsec.'" />'."\n". 634: '<input type="hidden" name="submitonly" value="'.$submitonly.'" />'."\n". 635: '<input type="hidden" name="handgrade" value="'.$ENV{'form.handgrade'}.'" /><br />'."\n". 636: '<input type="hidden" name="showgrading" value="'.$ENV{'form.showgrading'}.'" /><br />'."\n". 637: '<input type="hidden" name="saveState" value="'.$ENV{'form.saveState'}.'" />'."\n". 638: '<input type="hidden" name="probTitle" value="'.$ENV{'form.probTitle'}.'" />'."\n". 639: '<input type="hidden" name="url" value="'.$url.'" />'."\n". 640: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 641: '<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n"; 642: 643: if (exists($ENV{'form.gradingMenu'}) && exists($ENV{'form.Status'})) { 644: $gradeTable.='<input type="hidden" name="Status" value="'.$ENV{'form.Status'}.'" />'."\n"; 645: } else { 646: $gradeTable.='<b>Student Status:</b> '. 647: &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);').'<br />'; 648: } 649: 650: $gradeTable.='To '.lc($viewgrade).' a submission or a group of submissions, click on the check box(es) '. 651: 'next to the student\'s name(s). Then click on the Next button.<br />'."\n". 652: '<input type="hidden" name="command" value="processGroup" />'."\n"; 653: $gradeTable.='<input type="button" '."\n". 654: 'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n". 655: 'value="Next->" />'."\n"; 656: $gradeTable.='<input type="checkbox" name="checkPlag" checked="on">Check For Plagiarism</input>'; 657: my (undef, undef, $fullname) = &getclasslist($getsec,'1'); 658: $gradeTable.='<table border="0"><tr><td bgcolor="#777777">'. 659: '<table border="0"><tr bgcolor="#e6ffff">'; 660: my $loop = 0; 661: while ($loop < 2) { 662: $gradeTable.='<td><b> No.</b> </td><td><b> Select </b></td>'. 663: '<td>'.&nameUserString('header').'</td>'; 664: if ($ENV{'form.showgrading'} eq 'yes' && $submitonly ne 'all') { 665: foreach (sort(@$partlist)) { 666: $gradeTable.='<td><b> Part '.(split(/_/))[0].' Status </b></td>'; 667: } 668: } 669: $loop++; 670: # $gradeTable.='<td></td>' if ($loop%2 ==1); 671: } 672: $gradeTable.='</tr>'."\n"; 673: 674: my $ctr = 0; 675: foreach my $student (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) { 676: my ($uname,$udom) = split(/:/,$student); 677: my %status = (); 678: if ($ENV{'form.showgrading'} eq 'yes' && $submitonly ne 'all') { 679: (%status) =&student_gradeStatus($url,$symb,$udom,$uname,$partlist); 680: my $submitted = 0; 681: my $graded = 1; 682: foreach (keys(%status)) { 683: $submitted = 1 if ($status{$_} ne 'nothing'); 684: $graded = 0 if ($status{$_} =~ /^correct/); 685: my ($foo,$partid,$foo1) = split(/\./,$_); 686: if ($status{'resource.'.$partid.'.submitted_by'} ne '') { 687: $submitted = 0; 688: my ($part)=split(/\./,$partid); 689: $gradeTable.='<input type="hidden" name="'. 690: $student.':'.$part.':submitted_by" value="'. 691: $status{'resource.'.$partid.'.submitted_by'}.'" />'; 692: } 693: } 694: next if (!$submitted && ($submitonly eq 'yes' || 695: $submitonly eq 'incorrect' || 696: $submitonly eq 'graded')); 697: next if (!$graded && ($submitonly eq 'graded' || 698: $submitonly eq 'incorrect')); 699: } 700: 701: $ctr++; 702: if ( $perm{'vgr'} eq 'F' ) { 703: $gradeTable.='<tr bgcolor="#ffffe6">' if ($ctr%2 ==1); 704: $gradeTable.='<td align="right">'.$ctr.' </td>'. 705: '<td align="center"><input type=checkbox name="stuinfo" value="'. 706: $student.':'.$$fullname{$student}.' "></td>'."\n". 707: '<td>'.&nameUserString(undef,$$fullname{$student},$uname,$udom).'</td>'."\n"; 708: 709: if ($ENV{'form.showgrading'} eq 'yes' && $submitonly ne 'all') { 710: foreach (sort keys(%status)) { 711: next if (/^resource.*?submitted_by$/); 712: $gradeTable.='<td align="middle"> '.$status{$_}.' </td>'."\n"; 713: } 714: } 715: # $gradeTable.='<td></td>' if ($ctr%2 ==1); 716: $gradeTable.='</tr>'."\n" if ($ctr%2 ==0); 717: } 718: } 719: if ($ctr%2 ==1) { 720: $gradeTable.='<td> </td><td> </td><td> </td>'; 721: if ($ENV{'form.showgrading'} eq 'yes' && $submitonly ne 'all') { 722: foreach (@$partlist) { 723: $gradeTable.='<td> </td>'; 724: } 725: } 726: $gradeTable.='</tr>'; 727: } 728: 729: $gradeTable.='</table></td></tr></table>'. 730: '<input type="button" '. 731: 'onClick="javascript:checkSelect(this.form.stuinfo);" '. 732: 'value="Next->" /></form>'."\n"; 733: if ($ctr == 0) { 734: my $num_students=(scalar(keys(%$fullname))); 735: if ($num_students eq 0) { 736: $gradeTable='<br /> <font color="red">There are no students currently enrolled.</font>'; 737: } else { 738: $gradeTable='<br /> <font color="red">'. 739: 'No submissions found for this resource for any students. ('.$num_students. 740: ' checked for submissions)</font><br />'; 741: } 742: } elsif ($ctr == 1) { 743: $gradeTable =~ s/type=checkbox/type=checkbox checked/; 744: } 745: $gradeTable.=&show_grading_menu_form($symb,$url); 746: $request->print($gradeTable); 747: return ''; 748: } 749: 750: #---- Called from the listStudents routine 751: # Displays the submissions for one student or a group of students 752: sub processGroup { 753: my ($request) = shift; 754: my $ctr = 0; 755: my @stuchecked = &Apache::loncommon::get_env_multiple('form.stuinfo'); 756: my $total = scalar(@stuchecked)-1; 757: 758: foreach (@stuchecked) { 759: my ($uname,$udom,$fullname) = split(/:/); 760: $ENV{'form.student'} = $uname; 761: $ENV{'form.userdom'} = $udom; 762: $ENV{'form.fullname'} = $fullname; 763: &submission($request,$ctr,$total); 764: $ctr++; 765: } 766: return ''; 767: } 768: 769: #------------------------------------------------------------------------------------ 770: # 771: #-------------------------- Next few routines handles grading by student, essentially 772: # handles essay response type problem/part 773: # 774: #--- Javascript to handle the submission page functionality --- 775: sub sub_page_js { 776: my $request = shift; 777: $request->print(<<SUBJAVASCRIPT); 778: <script type="text/javascript" language="javascript"> 779: function updateRadio(formname,id,weight) { 780: var gradeBox = formname["GD_BOX"+id]; 781: var radioButton = formname["RADVAL"+id]; 782: var oldpts = formname["oldpts"+id].value; 783: var pts = checkSolved(formname,id) == 'update' ? gradeBox.value : oldpts; 784: gradeBox.value = pts; 785: var resetbox = false; 786: if (isNaN(pts) || pts < 0) { 787: alert("A number equal or greater than 0 is expected. Entered value = "+pts); 788: for (var i=0; i<radioButton.length; i++) { 789: if (radioButton[i].checked) { 790: gradeBox.value = i; 791: resetbox = true; 792: } 793: } 794: if (!resetbox) { 795: formtextbox.value = ""; 796: } 797: return; 798: } 799: 800: if (pts > weight) { 801: var resp = confirm("You entered a value ("+pts+ 802: ") greater than the weight for the part. Accept?"); 803: if (resp == false) { 804: gradeBox.value = oldpts; 805: return; 806: } 807: } 808: 809: for (var i=0; i<radioButton.length; i++) { 810: radioButton[i].checked=false; 811: if (pts == i && pts != "") { 812: radioButton[i].checked=true; 813: } 814: } 815: updateSelect(formname,id); 816: formname["stores"+id].value = "0"; 817: } 818: 819: function writeBox(formname,id,pts) { 820: var gradeBox = formname["GD_BOX"+id]; 821: if (checkSolved(formname,id) == 'update') { 822: gradeBox.value = pts; 823: } else { 824: var oldpts = formname["oldpts"+id].value; 825: gradeBox.value = oldpts; 826: var radioButton = formname["RADVAL"+id]; 827: for (var i=0; i<radioButton.length; i++) { 828: radioButton[i].checked=false; 829: if (i == oldpts) { 830: radioButton[i].checked=true; 831: } 832: } 833: } 834: formname["stores"+id].value = "0"; 835: updateSelect(formname,id); 836: return; 837: } 838: 839: function clearRadBox(formname,id) { 840: if (checkSolved(formname,id) == 'noupdate') { 841: updateSelect(formname,id); 842: return; 843: } 844: gradeSelect = formname["GD_SEL"+id]; 845: for (var i=0; i<gradeSelect.length; i++) { 846: if (gradeSelect[i].selected) { 847: var selectx=i; 848: } 849: } 850: var stores = formname["stores"+id]; 851: if (selectx == stores.value) { return }; 852: var gradeBox = formname["GD_BOX"+id]; 853: gradeBox.value = ""; 854: var radioButton = formname["RADVAL"+id]; 855: for (var i=0; i<radioButton.length; i++) { 856: radioButton[i].checked=false; 857: } 858: stores.value = selectx; 859: } 860: 861: function checkSolved(formname,id) { 862: if (formname["solved"+id].value == "correct_by_student" && formname.overRideScore.value == 'no') { 863: var reply = confirm("This problem has been graded correct by the computer. Do you want to change the score?"); 864: if (!reply) {return "noupdate";} 865: formname.overRideScore.value = 'yes'; 866: } 867: return "update"; 868: } 869: 870: function updateSelect(formname,id) { 871: formname["GD_SEL"+id][0].selected = true; 872: return; 873: } 874: 875: //=========== Check that a point is assigned for all the parts ============ 876: function checksubmit(formname,val,total,parttot) { 877: formname.gradeOpt.value = val; 878: if (val == "Save & Next") { 879: for (i=0;i<=total;i++) { 880: for (j=0;j<parttot;j++) { 881: var partid = formname["partid"+i+"_"+j].value; 882: if (formname["GD_SEL"+i+"_"+partid][0].selected) { 883: var points = formname["GD_BOX"+i+"_"+partid].value; 884: if (points == "") { 885: var name = formname["name"+i].value; 886: var studentID = (name != '' ? name : formname["unamedom"+i].value); 887: var resp = confirm("You did not assign a score for "+studentID+ 888: ", part "+partid+". Continue?"); 889: if (resp == false) { 890: formname["GD_BOX"+i+"_"+partid].focus(); 891: return false; 892: } 893: } 894: } 895: 896: } 897: } 898: 899: } 900: if (val == "Grade Student") { 901: formname.showgrading.value = "yes"; 902: if (formname.Status.value == "") { 903: formname.Status.value = "Active"; 904: } 905: formname.studentNo.value = total; 906: } 907: formname.submit(); 908: } 909: 910: //======= Check that a score is assigned for all the problems (page/sequence grading only) ========= 911: function checkSubmitPage(formname,total) { 912: noscore = new Array(100); 913: var ptr = 0; 914: for (i=1;i<total;i++) { 915: var partid = formname["q_"+i].value; 916: if (formname["GD_SEL"+i+"_"+partid][0].selected) { 917: var points = formname["GD_BOX"+i+"_"+partid].value; 918: var status = formname["solved"+i+"_"+partid].value; 919: if (points == "" && status != "correct_by_student") { 920: noscore[ptr] = i; 921: ptr++; 922: } 923: } 924: } 925: if (ptr != 0) { 926: var sense = ptr == 1 ? ": " : "s: "; 927: var prolist = ""; 928: if (ptr == 1) { 929: prolist = noscore[0]; 930: } else { 931: var i = 0; 932: while (i < ptr-1) { 933: prolist += noscore[i]+", "; 934: i++; 935: } 936: prolist += "and "+noscore[i]; 937: } 938: var resp = confirm("You did not assign any score for the following problem"+sense+prolist+". Continue?"); 939: if (resp == false) { 940: return false; 941: } 942: } 943: 944: formname.submit(); 945: } 946: </script> 947: SUBJAVASCRIPT 948: } 949: 950: #--- javascript for essay type problem -- 951: sub sub_page_kw_js { 952: my $request = shift; 953: my $iconpath = $request->dir_config('lonIconsURL'); 954: &commonJSfunctions($request); 955: $request->print(<<SUBJAVASCRIPT); 956: <script type="text/javascript" language="javascript"> 957: 958: //===================== Show list of keywords ==================== 959: function keywords(formname) { 960: var nret = prompt("Keywords list, separated by a space. Add/delete to list if desired.",formname.keywords.value); 961: if (nret==null) return; 962: formname.keywords.value = nret; 963: 964: if (formname.keywords.value != "") { 965: formname.refresh.value = "on"; 966: formname.submit(); 967: } 968: return; 969: } 970: 971: //===================== Script to view submitted by ================== 972: function viewSubmitter(submitter) { 973: document.SCORE.refresh.value = "on"; 974: document.SCORE.NCT.value = "1"; 975: document.SCORE.unamedom0.value = submitter; 976: document.SCORE.submit(); 977: return; 978: } 979: 980: //===================== Script to add keyword(s) ================== 981: function getSel() { 982: if (document.getSelection) txt = document.getSelection(); 983: else if (document.selection) txt = document.selection.createRange().text; 984: else return; 985: var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," "); 986: if (cleantxt=="") { 987: alert("Please select a word or group of words from document and then click this link."); 988: return; 989: } 990: var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt); 991: if (nret==null) return; 992: document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret; 993: if (document.SCORE.keywords.value != "") { 994: document.SCORE.refresh.value = "on"; 995: document.SCORE.submit(); 996: } 997: return; 998: } 999: 1000: //====================== Script for composing message ============== 1001: // preload images 1002: img1 = new Image(); 1003: img1.src = "$iconpath/mailbkgrd.gif"; 1004: img2 = new Image(); 1005: img2.src = "$iconpath/mailto.gif"; 1006: 1007: function msgCenter(msgform,usrctr,fullname) { 1008: var Nmsg = msgform.savemsgN.value; 1009: savedMsgHeader(Nmsg,usrctr,fullname); 1010: var subject = msgform.msgsub.value; 1011: var msgchk = document.SCORE["includemsg"+usrctr].value; 1012: re = /msgsub/; 1013: var shwsel = ""; 1014: if (re.test(msgchk)) { shwsel = "checked" } 1015: subject = (document.SCORE.shownSub.value == 0 ? checkEntities(subject) : subject); 1016: displaySubject(checkEntities(subject),shwsel); 1017: for (var i=1; i<=Nmsg; i++) { 1018: var testmsg = "savemsg"+i+","; 1019: re = new RegExp(testmsg,"g"); 1020: shwsel = ""; 1021: if (re.test(msgchk)) { shwsel = "checked" } 1022: var message = document.SCORE["savemsg"+i].value; 1023: message = (document.SCORE["shownOnce"+i].value == 0 ? checkEntities(message) : message); 1024: displaySavedMsg(i,message,shwsel); //I do not get it. w/o checkEntities on saved messages, 1025: //any < is already converted to <, etc. However, only once!! 1026: } 1027: newmsg = document.SCORE["newmsg"+usrctr].value; 1028: shwsel = ""; 1029: re = /newmsg/; 1030: if (re.test(msgchk)) { shwsel = "checked" } 1031: newMsg(newmsg,shwsel); 1032: msgTail(); 1033: return; 1034: } 1035: 1036: function checkEntities(strx) { 1037: if (strx.length == 0) return strx; 1038: var orgStr = ["&", "<", ">", '"']; 1039: var newStr = ["&", "<", ">", """]; 1040: var counter = 0; 1041: while (counter < 4) { 1042: strx = strReplace(strx,orgStr[counter],newStr[counter]); 1043: counter++; 1044: } 1045: return strx; 1046: } 1047: 1048: function strReplace(strx, orgStr, newStr) { 1049: return strx.split(orgStr).join(newStr); 1050: } 1051: 1052: function savedMsgHeader(Nmsg,usrctr,fullname) { 1053: var height = 70*Nmsg+250; 1054: var scrollbar = "no"; 1055: if (height > 600) { 1056: height = 600; 1057: scrollbar = "yes"; 1058: } 1059: var xpos = (screen.width-600)/2; 1060: xpos = (xpos < 0) ? '0' : xpos; 1061: var ypos = (screen.height-height)/2-30; 1062: ypos = (ypos < 0) ? '0' : ypos; 1063: 1064: pWin = window.open('', 'MessageCenter', 'toolbar=no,location=no,scrollbars='+scrollbar+',screenx='+xpos+',screeny='+ypos+',width=600,height='+height); 1065: pWin.focus(); 1066: pDoc = pWin.document; 1067: pDoc.open('text/html','replace'); 1068: pDoc.write("<html><head>"); 1069: pDoc.write("<title>Message Central</title>"); 1070: 1071: pDoc.write("<script language=javascript>"); 1072: pDoc.write("function checkInput() {"); 1073: pDoc.write(" opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value);"); 1074: pDoc.write(" var nmsg = opener.document.SCORE.savemsgN.value;"); 1075: pDoc.write(" var usrctr = document.msgcenter.usrctr.value;"); 1076: pDoc.write(" var newval = opener.document.SCORE[\\"newmsg\\"+usrctr];"); 1077: pDoc.write(" newval.value = opener.checkEntities(document.msgcenter.newmsg.value);"); 1078: 1079: pDoc.write(" var msgchk = \\"\\";"); 1080: pDoc.write(" if (document.msgcenter.subchk.checked) {"); 1081: pDoc.write(" msgchk = \\"msgsub,\\";"); 1082: pDoc.write(" }"); 1083: pDoc.write(" var includemsg = 0;"); 1084: pDoc.write(" for (var i=1; i<=nmsg; i++) {"); 1085: pDoc.write(" var opnmsg = opener.document.SCORE[\\"savemsg\\"+i];"); 1086: pDoc.write(" var frmmsg = document.msgcenter[\\"msg\\"+i];"); 1087: pDoc.write(" opnmsg.value = opener.checkEntities(frmmsg.value);"); 1088: pDoc.write(" var showflg = opener.document.SCORE[\\"shownOnce\\"+i];"); 1089: pDoc.write(" showflg.value = \\"1\\";"); 1090: pDoc.write(" var chkbox = document.msgcenter[\\"msgn\\"+i];"); 1091: pDoc.write(" if (chkbox.checked) {"); 1092: pDoc.write(" msgchk += \\"savemsg\\"+i+\\",\\";"); 1093: pDoc.write(" includemsg = 1;"); 1094: pDoc.write(" }"); 1095: pDoc.write(" }"); 1096: pDoc.write(" if (document.msgcenter.newmsgchk.checked) {"); 1097: pDoc.write(" msgchk += \\"newmsg\\"+usrctr;"); 1098: pDoc.write(" includemsg = 1;"); 1099: pDoc.write(" }"); 1100: pDoc.write(" imgformname = opener.document.SCORE[\\"mailicon\\"+usrctr];"); 1101: pDoc.write(" imgformname.src = \\"$iconpath/\\"+((includemsg) ? \\"mailto.gif\\" : \\"mailbkgrd.gif\\");"); 1102: pDoc.write(" var includemsg = opener.document.SCORE[\\"includemsg\\"+usrctr];"); 1103: pDoc.write(" includemsg.value = msgchk;"); 1104: 1105: pDoc.write(" self.close()"); 1106: 1107: pDoc.write("}"); 1108: 1109: pDoc.write("<"); 1110: pDoc.write("/script>"); 1111: 1112: pDoc.write("</head><body bgcolor=white>"); 1113: 1114: pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">"); 1115: pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">"); 1116: pDoc.write("<font color=\\"green\\" size=+1> Compose Message for \"+fullname+\"</font><br><br>"); 1117: 1118: pDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">"); 1119: pDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">"); 1120: pDoc.write("<td><b>Type</b></td><td><b>Include</b></td><td><b>Message</td></tr>"); 1121: } 1122: function displaySubject(msg,shwsel) { 1123: pDoc = pWin.document; 1124: pDoc.write("<tr bgcolor=\\"#ffffdd\\">"); 1125: pDoc.write("<td>Subject</td>"); 1126: pDoc.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"></td>"); 1127: pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"60\\" maxlength=\\"80\\"></td></tr>"); 1128: } 1129: 1130: function displaySavedMsg(ctr,msg,shwsel) { 1131: pDoc = pWin.document; 1132: pDoc.write("<tr bgcolor=\\"#ffffdd\\">"); 1133: pDoc.write("<td align=\\"center\\">"+ctr+"</td>"); 1134: pDoc.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"></td>"); 1135: pDoc.write("<td><textarea name=\\"msg"+ctr+"\\" cols=\\"60\\" rows=\\"3\\">"+msg+"</textarea></td></tr>"); 1136: } 1137: 1138: function newMsg(newmsg,shwsel) { 1139: pDoc = pWin.document; 1140: pDoc.write("<tr bgcolor=\\"#ffffdd\\">"); 1141: pDoc.write("<td align=\\"center\\">New</td>"); 1142: pDoc.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"></td>"); 1143: pDoc.write("<td><textarea name=\\"newmsg\\" cols=\\"60\\" rows=\\"3\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" >"+newmsg+"</textarea></td></tr>"); 1144: } 1145: 1146: function msgTail() { 1147: pDoc = pWin.document; 1148: pDoc.write("</table>"); 1149: pDoc.write("</td></tr></table> "); 1150: pDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\"> "); 1151: pDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br><br>"); 1152: pDoc.write("</form>"); 1153: pDoc.write("</body></html>"); 1154: pDoc.close(); 1155: } 1156: 1157: //====================== Script for keyword highlight options ============== 1158: function kwhighlight() { 1159: var kwclr = document.SCORE.kwclr.value; 1160: var kwsize = document.SCORE.kwsize.value; 1161: var kwstyle = document.SCORE.kwstyle.value; 1162: var redsel = ""; 1163: var grnsel = ""; 1164: var blusel = ""; 1165: if (kwclr=="red") {var redsel="checked"}; 1166: if (kwclr=="green") {var grnsel="checked"}; 1167: if (kwclr=="blue") {var blusel="checked"}; 1168: var sznsel = ""; 1169: var sz1sel = ""; 1170: var sz2sel = ""; 1171: if (kwsize=="0") {var sznsel="checked"}; 1172: if (kwsize=="+1") {var sz1sel="checked"}; 1173: if (kwsize=="+2") {var sz2sel="checked"}; 1174: var synsel = ""; 1175: var syisel = ""; 1176: var sybsel = ""; 1177: if (kwstyle=="") {var synsel="checked"}; 1178: if (kwstyle=="<i>") {var syisel="checked"}; 1179: if (kwstyle=="<b>") {var sybsel="checked"}; 1180: highlightCentral(); 1181: highlightbody('red','red',redsel,'0','normal',sznsel,'','normal',synsel); 1182: highlightbody('green','green',grnsel,'+1','+1',sz1sel,'<i>','italic',syisel); 1183: highlightbody('blue','blue',blusel,'+2','+2',sz2sel,'<b>','bold',sybsel); 1184: highlightend(); 1185: return; 1186: } 1187: 1188: function highlightCentral() { 1189: // if (window.hwdWin) window.hwdWin.close(); 1190: var xpos = (screen.width-400)/2; 1191: xpos = (xpos < 0) ? '0' : xpos; 1192: var ypos = (screen.height-330)/2-30; 1193: ypos = (ypos < 0) ? '0' : ypos; 1194: 1195: hwdWin = window.open('', 'KeywordHighlightCentral', 'toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx='+xpos+',screeny='+ypos); 1196: hwdWin.focus(); 1197: var hDoc = hwdWin.document; 1198: hDoc.open('text/html','replace'); 1199: hDoc.write("<html><head>"); 1200: hDoc.write("<title>Highlight Central</title>"); 1201: 1202: hDoc.write("<script language=javascript>"); 1203: hDoc.write("function updateChoice(flag) {"); 1204: hDoc.write(" opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr);"); 1205: hDoc.write(" opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize);"); 1206: hDoc.write(" opener.document.SCORE.kwstyle.value = opener.radioSelection(document.hlCenter.kwdstyle);"); 1207: hDoc.write(" opener.document.SCORE.refresh.value = \\"on\\";"); 1208: hDoc.write(" if (opener.document.SCORE.keywords.value!=\\"\\"){"); 1209: hDoc.write(" opener.document.SCORE.submit();"); 1210: hDoc.write(" }"); 1211: hDoc.write(" self.close()"); 1212: hDoc.write("}"); 1213: 1214: hDoc.write("<"); 1215: hDoc.write("/script>"); 1216: 1217: hDoc.write("</head><body bgcolor=white>"); 1218: 1219: hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">"); 1220: hDoc.write("<font color=\\"green\\" size=+1> Keyword Highlight Options</font><br><br>"); 1221: 1222: hDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">"); 1223: hDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">"); 1224: hDoc.write("<td><b>Text Color</b></td><td><b>Font Size</b></td><td><b>Font Style</td></tr>"); 1225: } 1226: 1227: function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) { 1228: var hDoc = hwdWin.document; 1229: hDoc.write("<tr bgcolor=\\"#ffffdd\\">"); 1230: hDoc.write("<td align=\\"left\\">"); 1231: hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+"> "+clrtxt+"</td>"); 1232: hDoc.write("<td align=\\"left\\">"); 1233: hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+"> "+sztxt+"</td>"); 1234: hDoc.write("<td align=\\"left\\">"); 1235: hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+"> "+sytxt+"</td>"); 1236: hDoc.write("</tr>"); 1237: } 1238: 1239: function highlightend() { 1240: var hDoc = hwdWin.document; 1241: hDoc.write("</table>"); 1242: hDoc.write("</td></tr></table> "); 1243: hDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:updateChoice(1)\\"> "); 1244: hDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br><br>"); 1245: hDoc.write("</form>"); 1246: hDoc.write("</body></html>"); 1247: hDoc.close(); 1248: } 1249: 1250: </script> 1251: SUBJAVASCRIPT 1252: } 1253: 1254: #--- displays the grading box, used in essay type problem and grading by page/sequence 1255: sub gradeBox { 1256: my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_; 1257: 1258: my $checkIcon = '<img src="'.$request->dir_config('lonIconsURL'). 1259: '/check.gif" height="16" border="0" />'; 1260: 1261: my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname); 1262: my $wgtmsg = ($wgt > 0 ? '(problem weight)' : 1263: '<font color="red">problem weight assigned by computer</font>'); 1264: $wgt = ($wgt > 0 ? $wgt : '1'); 1265: my $score = ($$record{'resource.'.$partid.'.awarded'} eq '' ? 1266: '' : $$record{'resource.'.$partid.'.awarded'}*$wgt); 1267: my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n"; 1268: 1269: $result.='<table border="0"><tr><td>'. 1270: '<b>Part </b>'.$partid.' <b>Points: </b></td><td>'."\n"; 1271: 1272: my $ctr = 0; 1273: $result.='<table border="0"><tr>'."\n"; # display radio buttons in a nice table 10 across 1274: while ($ctr<=$wgt) { 1275: $result.= '<td><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '. 1276: 'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','. 1277: $ctr.')" value="'.$ctr.'" '. 1278: ($score eq $ctr ? 'checked':'').' /> '.$ctr."</td>\n"; 1279: $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : ''); 1280: $ctr++; 1281: } 1282: $result.='</tr></table>'; 1283: 1284: $result.='</td><td> <b>or</b> </td>'."\n"; 1285: $result.='<td><input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'. 1286: ($score ne ''? ' value = "'.$score.'"':'').' size="4" '. 1287: 'onChange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','. 1288: $wgt.')" /></td>'."\n"; 1289: $result.='<td>/'.$wgt.' '.$wgtmsg. 1290: ($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? ' '.$checkIcon : ''). 1291: ' </td><td>'."\n"; 1292: 1293: $result.='<select name="GD_SEL'.$counter.'_'.$partid.'" '. 1294: 'onChange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n"; 1295: if ($$record{'resource.'.$partid.'.solved'} eq 'excused') { 1296: $result.='<option> </option>'. 1297: '<option selected="on">excused</option>'; 1298: } else { 1299: $result.='<option selected="on"> </option>'. 1300: '<option>excused</option>'; 1301: } 1302: $result.='<option>reset status</option></select>'."\n"; 1303: $result.="  \n"; 1304: $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n". 1305: '<input type="hidden" name="oldpts'.$counter.'_'.$partid.'" value="'.$score.'" />'."\n". 1306: '<input type="hidden" name="solved'.$counter.'_'.$partid.'" value="'. 1307: $$record{'resource.'.$partid.'.solved'}.'" />'."\n"; 1308: $result.='</td></tr></table>'."\n"; 1309: return $result; 1310: } 1311: 1312: sub show_problem { 1313: my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode) = @_; 1314: my $rendered; 1315: if ($mode eq 'both' or $mode eq 'text') { 1316: $rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom, 1317: $ENV{'request.course.id'}); 1318: } 1319: if ($removeform) { 1320: $rendered=~s|<form(.*?)>||g; 1321: $rendered=~s|</form>||g; 1322: $rendered=~s|name="submit"|name="would_have_been_submit"|g; 1323: } 1324: my $companswer; 1325: if ($mode eq 'both' or $mode eq 'answer') { 1326: $companswer=&Apache::loncommon::get_student_answers($symb,$uname,$udom, 1327: $ENV{'request.course.id'}); 1328: } 1329: if ($removeform) { 1330: $companswer=~s|<form(.*?)>||g; 1331: $companswer=~s|</form>||g; 1332: $companswer=~s|name="submit"|name="would_have_been_submit"|g; 1333: } 1334: my $result.='<table border="0" width="100%"><tr><td bgcolor="#777777">'; 1335: $result.='<table border="0" width="100%">'; 1336: if ($viewon) { 1337: $result.='<tr><td bgcolor="#e6ffff"><b> '; 1338: if ($mode eq 'both' or $mode eq 'text') { 1339: $result.='View of the problem - '; 1340: } else { 1341: $result.='Correct answer: '; 1342: } 1343: $result.=$ENV{'form.fullname'}.'</b></td></tr>'; 1344: } 1345: if ($mode eq 'both') { 1346: $result.='<tr><td bgcolor="#ffffff">'.$rendered.'<br />'; 1347: $result.='<b>Correct answer:</b><br />'.$companswer; 1348: } elsif ($mode eq 'text') { 1349: $result.='<tr><td bgcolor="#ffffff">'.$rendered; 1350: } elsif ($mode eq 'answer') { 1351: $result.='<tr><td bgcolor="#ffffff">'.$companswer; 1352: } 1353: $result.='</td></tr></table>'; 1354: $result.='</td></tr></table><br />'; 1355: return $result; 1356: } 1357: 1358: # --------------------------- show submissions of a student, option to grade 1359: sub submission { 1360: my ($request,$counter,$total) = @_; 1361: 1362: (my $url=$ENV{'form.url'})=~s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--; 1363: my ($uname,$udom) = ($ENV{'form.student'},$ENV{'form.userdom'}); 1364: $udom = ($udom eq '' ? $ENV{'user.domain'} : $udom); #has form.userdom changed for a student? 1365: my $usec = &Apache::lonnet::getsection($udom,$uname,$ENV{'request.course.id'}); 1366: $ENV{'form.fullname'} = &get_fullname ($uname,$udom) if $ENV{'form.fullname'} eq ''; 1367: 1368: my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url))); 1369: if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; } 1370: 1371: if (!&canview($usec)) { 1372: $request->print('<font color="red">Unable to view requested student.('. 1373: $uname.$udom.$usec.$ENV{'request.course.id'}.')</font>'); 1374: $request->print(&show_grading_menu_form($symb,$url)); 1375: return; 1376: } 1377: 1378: $ENV{'form.lastSub'} = ($ENV{'form.lastSub'} eq '' ? 'datesub' : $ENV{'form.lastSub'}); 1379: my $last = ($ENV{'form.lastSub'} eq 'last' ? 'last' : ''); 1380: my $checkIcon = '<img src="'.$request->dir_config('lonIconsURL'). 1381: '/check.gif" height="16" border="0" />'; 1382: 1383: # header info 1384: if ($counter == 0) { 1385: &sub_page_js($request); 1386: &sub_page_kw_js($request) if ($ENV{'form.handgrade'} eq 'yes'); 1387: $ENV{'form.probTitle'} = $ENV{'form.probTitle'} eq '' ? 1388: &Apache::lonnet::gettitle($symb) : $ENV{'form.probTitle'}; 1389: 1390: $request->print('<h3> <font color="#339933">Submission Record</font></h3>'."\n". 1391: '<font size=+1> <b>Resource: </b>'.$ENV{'form.probTitle'}.'</font>'."\n"); 1392: 1393: if ($ENV{'form.handgrade'} eq 'no') { 1394: my $checkMark='<br /><br /> <b>Note:</b> Part(s) graded correct by the computer is marked with a '. 1395: $checkIcon.' symbol.'."\n"; 1396: $request->print($checkMark); 1397: } 1398: 1399: # option to display problem, only once else it cause problems 1400: # with the form later since the problem has a form. 1401: if ($ENV{'form.vProb'} eq 'yes' or $ENV{'form.vAns'} eq 'yes') { 1402: my $mode; 1403: if ($ENV{'form.vProb'} eq 'yes' && $ENV{'form.vAns'} eq 'yes') { 1404: $mode='both'; 1405: } elsif ($ENV{'form.vProb'} eq 'yes') { 1406: $mode='text'; 1407: } elsif ($ENV{'form.vAns'} eq 'yes') { 1408: $mode='answer'; 1409: } 1410: $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode)); 1411: } 1412: 1413: # kwclr is the only variable that is guaranteed to be non blank 1414: # if this subroutine has been called once. 1415: my %keyhash = (); 1416: if ($ENV{'form.kwclr'} eq '' && $ENV{'form.handgrade'} eq 'yes') { 1417: %keyhash = &Apache::lonnet::dump('nohist_handgrade', 1418: $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, 1419: $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); 1420: 1421: my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'}; 1422: $ENV{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : ''; 1423: $ENV{'form.kwclr'} = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red'; 1424: $ENV{'form.kwsize'} = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0'; 1425: $ENV{'form.kwstyle'} = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : ''; 1426: $ENV{'form.msgsub'} = $keyhash{$symb.'_subject'} ne '' ? 1427: $keyhash{$symb.'_subject'} : $ENV{'form.probTitle'}; 1428: $ENV{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0'; 1429: } 1430: my $overRideScore = $ENV{'form.overRideScore'} eq '' ? 'no' : $ENV{'form.overRideScore'}; 1431: 1432: $request->print('<form action="/adm/grades" method="post" name="SCORE">'."\n". 1433: '<input type="hidden" name="command" value="handgrade" />'."\n". 1434: '<input type="hidden" name="saveState" value="'.$ENV{'form.saveState'}.'" />'."\n". 1435: '<input type="hidden" name="Status" value="'.$ENV{'form.Status'}.'" />'."\n". 1436: '<input type="hidden" name="overRideScore" value="'.$overRideScore.'" />'."\n". 1437: '<input type="hidden" name="probTitle" value="'.$ENV{'form.probTitle'}.'" />'."\n". 1438: '<input type="hidden" name="refresh" value="off" />'."\n". 1439: '<input type="hidden" name="studentNo" value="" />'."\n". 1440: '<input type="hidden" name="gradeOpt" value="" />'."\n". 1441: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 1442: '<input type="hidden" name="url" value="'.$url.'" />'."\n". 1443: '<input type="hidden" name="showgrading" value="'.$ENV{'form.showgrading'}.'" />'."\n". 1444: '<input type="hidden" name="vProb" value="'.$ENV{'form.vProb'}.'" />'."\n". 1445: '<input type="hidden" name="vAns" value="'.$ENV{'form.vAns'}.'" />'."\n". 1446: '<input type="hidden" name="lastSub" value="'.$ENV{'form.lastSub'}.'" />'."\n". 1447: '<input type="hidden" name="section" value="'.$ENV{'form.section'}.'">'."\n". 1448: '<input type="hidden" name="submitonly" value="'.$ENV{'form.submitonly'}.'">'."\n". 1449: '<input type="hidden" name="handgrade" value="'.$ENV{'form.handgrade'}.'">'."\n". 1450: '<input type="hidden" name="NCT"'. 1451: ' value="'.($ENV{'form.NTSTU'} ne '' ? $ENV{'form.NTSTU'} : $total+1).'" />'."\n"); 1452: if ($ENV{'form.handgrade'} eq 'yes') { 1453: $request->print('<input type="hidden" name="keywords" value="'.$ENV{'form.keywords'}.'" />'."\n". 1454: '<input type="hidden" name="kwclr" value="'.$ENV{'form.kwclr'}.'" />'."\n". 1455: '<input type="hidden" name="kwsize" value="'.$ENV{'form.kwsize'}.'" />'."\n". 1456: '<input type="hidden" name="kwstyle" value="'.$ENV{'form.kwstyle'}.'" />'."\n". 1457: '<input type="hidden" name="msgsub" value="'.$ENV{'form.msgsub'}.'" />'."\n". 1458: '<input type="hidden" name="shownSub" value="0" />'."\n". 1459: '<input type="hidden" name="savemsgN" value="'.$ENV{'form.savemsgN'}.'" />'."\n"); 1460: foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) { 1461: $request->print('<input type="hidden" name="vPart" value="'.$partid.'" />'."\n"); 1462: } 1463: } 1464: 1465: my ($cts,$prnmsg) = (1,''); 1466: while ($cts <= $ENV{'form.savemsgN'}) { 1467: $prnmsg.='<input type="hidden" name="savemsg'.$cts.'" value="'. 1468: (!exists($keyhash{$symb.'_savemsg'.$cts}) ? 1469: &Apache::lonfeedback::clear_out_html($ENV{'form.savemsg'.$cts}) : 1470: &Apache::lonfeedback::clear_out_html($keyhash{$symb.'_savemsg'.$cts})). 1471: '" />'."\n". 1472: '<input type="hidden" name="shownOnce'.$cts.'" value="0" />'."\n"; 1473: $cts++; 1474: } 1475: $request->print($prnmsg); 1476: 1477: if ($ENV{'form.handgrade'} eq 'yes' && $ENV{'form.showgrading'} eq 'yes') { 1478: # 1479: # Print out the keyword options line 1480: # 1481: $request->print(<<KEYWORDS); 1482: <b>Keyword Options:</b> 1483: <a href="javascript:keywords(document.SCORE)"; TARGET=_self>List</a> 1484: <a href="#" onMouseDown="javascript:getSel(); return false" 1485: CLASS="page">Paste Selection to List</a> 1486: <a href="javascript:kwhighlight()"; TARGET=_self>Highlight Attribute</a><br /><br /> 1487: KEYWORDS 1488: # 1489: # Load the other essays for similarity check 1490: # 1491: my $essayurl=&Apache::lonnet::declutter($url); 1492: my ($adom,$aname,$apath)=($essayurl=~/^(\w+)\/(\w+)\/(.*)$/); 1493: $apath=&Apache::lonnet::escape($apath); 1494: $apath=~s/\W/\_/gs; 1495: %oldessays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname); 1496: } 1497: } 1498: 1499: if ($ENV{'form.vProb'} eq 'all' or $ENV{'form.vAns'} eq 'all') { 1500: $request->print('<br /><br /><br />') if ($counter > 0); 1501: my $mode; 1502: if ($ENV{'form.vProb'} eq 'all' && $ENV{'form.vAns'} eq 'all') { 1503: $mode='both'; 1504: } elsif ($ENV{'form.vProb'} eq 'all' ) { 1505: $mode='text'; 1506: } elsif ($ENV{'form.vAns'} eq 'all') { 1507: $mode='answer'; 1508: } 1509: $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode)); 1510: } 1511: 1512: my %record = &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname); 1513: 1514: my ($partlist,$handgrade,$responseType) = &response_type($url,$symb); 1515: 1516: # Display student info 1517: $request->print(($counter == 0 ? '' : '<br />')); 1518: my $result='<table border="0" width=100%><tr><td bgcolor="#777777">'."\n". 1519: '<table border="0" width=100%><tr bgcolor="#edffff"><td>'."\n"; 1520: 1521: $result.='<b>Fullname: </b>'.&nameUserString(undef,$ENV{'form.fullname'},$uname,$udom).'<br />'."\n"; 1522: $result.='<input type="hidden" name="name'.$counter. 1523: '" value="'.$ENV{'form.fullname'}.'" />'."\n"; 1524: 1525: # If any part of the problem is an essay-response (handgraded), then check for collaborators 1526: my @col_fullnames; 1527: my ($classlist,$fullname); 1528: if ($ENV{'form.handgrade'} eq 'yes') { 1529: ($classlist,undef,$fullname) = &getclasslist('all','0'); 1530: for (keys (%$handgrade)) { 1531: my $ncol = &Apache::lonnet::EXT('resource.'.$_. 1532: '.maxcollaborators', 1533: $symb,$udom,$uname); 1534: next if ($ncol <= 0); 1535: s/\_/\./g; 1536: next if ($record{'resource.'.$_.'.collaborators'} eq ''); 1537: my @goodcollaborators = (); 1538: my @badcollaborators = (); 1539: foreach (split(/,?\s+/,$record{'resource.'.$_.'.collaborators'})) { 1540: $_ =~ s/[\$\^\(\)]//g; 1541: next if ($_ eq ''); 1542: my ($co_name,$co_dom) = split /\@|:/,$_; 1543: $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i); 1544: next if ($co_name eq $uname && $co_dom eq $udom); 1545: # Doing this grep allows 'fuzzy' specification 1546: my @Matches = grep /^$co_name:$co_dom$/i,keys %$classlist; 1547: if (! scalar(@Matches)) { 1548: push @badcollaborators,$_; 1549: } else { 1550: push @goodcollaborators, @Matches; 1551: } 1552: } 1553: if (scalar(@goodcollaborators) != 0) { 1554: $result.='<b>Collaborators: </b>'; 1555: foreach (@goodcollaborators) { 1556: my ($lastname,$givenn) = split(/,/,$$fullname{$_}); 1557: push @col_fullnames, $givenn.' '.$lastname; 1558: $result.=$$fullname{$_}.' '; 1559: } 1560: $result.='<br />'."\n"; 1561: my ($part)=split(/\./,$_); 1562: $result.='<input type="hidden" name="collaborator'.$counter. 1563: '" value="'.$part.':'.(join ':',@goodcollaborators).'" />'. 1564: "\n"; 1565: } 1566: if (scalar(@badcollaborators) > 0) { 1567: $result.='<table border="0"><tr bgcolor="#ffbbbb"><td>'; 1568: $result.='This student has submitted '; 1569: $result.=(scalar(@badcollaborators) == 1) ? 'an invalid collaborator' : 'invalid collaborators'; 1570: $result .= ': '.join(', ',@badcollaborators); 1571: $result .= '</td></tr></table>'; 1572: } 1573: if (scalar(@badcollaborators > $ncol)) { 1574: $result .= '<table border="0"><tr bgcolor="#ffbbbb"><td>'; 1575: $result .= 'This student has submitted too many '. 1576: 'collaborators. Maximum is '.$ncol.'.'; 1577: $result .= '</td></tr></table>'; 1578: } 1579: } 1580: } 1581: $request->print($result."\n"); 1582: 1583: # print student answer/submission 1584: # Options are (1) Handgaded submission only 1585: # (2) Last submission, includes submission that is not handgraded 1586: # (for multi-response type part) 1587: # (3) Last submission plus the parts info 1588: # (4) The whole record for this student 1589: if ($ENV{'form.lastSub'} =~ /^(lastonly|hdgrade)$/) { 1590: my ($string,$timestamp)= &get_last_submission(\%record); 1591: my $lastsubonly=''. 1592: ($$timestamp eq '' ? '' : '<b>Date Submitted:</b> '. 1593: $$timestamp)."</td></tr>\n"; 1594: if ($$timestamp eq '') { 1595: $lastsubonly.='<tr><td bgcolor="#ffffe6">'.$$string[0]; 1596: } else { 1597: my %seenparts; 1598: for my $part (sort keys(%$handgrade)) { 1599: my ($partid,$respid) = split(/_/,$part); 1600: if ($ENV{"form.$uname:$udom:$partid:submitted_by"}) { 1601: if (exists($seenparts{$partid})) { next; } 1602: $seenparts{$partid}=1; 1603: my $submitby='<b>Part '.$partid. 1604: ' Collaborative submission by: </b>'. 1605: '<a href="javascript:viewSubmitter(\''. 1606: $ENV{"form.$uname:$udom:$partid:submitted_by"}. 1607: '\')"; TARGET=_self>'. 1608: $$fullname{$ENV{"form.$uname:$udom:$partid:submitted_by"}}.'</a><br />'; 1609: $request->print($submitby); 1610: next; 1611: } 1612: my $responsetype = $responseType->{$partid}->{$respid}; 1613: if (!exists($record{"resource.$partid.$respid.submission"})) { 1614: $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part '. 1615: $partid.'</b> <font color="#999999">( ID '.$respid. 1616: ' )</font> '. 1617: '<font color="red">Nothing submitted - no attempts</font><br /><br />'; 1618: next; 1619: } 1620: foreach (@$string) { 1621: my ($partid,$respid) = /^resource\.([^\.]*)\.([^\.]*)\.submission/; 1622: if ($part ne ($partid.'_'.$respid)) { next; } 1623: my ($ressub,$subval) = split(/:/,$_,2); 1624: # Similarity check 1625: my $similar=''; 1626: if($ENV{'form.checkPlag'}){ 1627: my ($oname,$odom,$ocrsid,$oessay,$osim)= 1628: &most_similar($uname,$udom,$subval); 1629: if ($osim) { 1630: $osim=int($osim*100.0); 1631: $similar="<hr /><h3><font color=\"#FF0000\">Essay". 1632: " is $osim% similar to an essay by ". 1633: &Apache::loncommon::plainname($oname,$odom). 1634: '</font></h3><blockquote><i>'. 1635: &keywords_highlight($oessay). 1636: '</i></blockquote><hr />'; 1637: } 1638: } 1639: my $order=&get_order($partid,$respid,$symb,$uname,$udom); 1640: if ($ENV{'form.lastSub'} eq 'lastonly' || 1641: ($ENV{'form.lastSub'} eq 'hdgrade' && 1642: $$handgrade{$part} eq 'yes')) { 1643: $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part '. 1644: $partid.'</b> <font color="#999999">( ID '.$respid. 1645: ' )</font> '; 1646: if ($record{"resource.$partid.$respid.uploadedurl"}) { 1647: $lastsubonly.='<a href="'.&Apache::lonnet::tokenwrapper($record{"resource.$partid.$respid.uploadedurl"}).'"><img src="/adm/lonIcons/unknown.gif" border=0"> File uploaded by student</a> <font color="red" size="1">Like all files provided by users, this file may contain virusses</font><br />'; 1648: } 1649: $lastsubonly.='<b>Submitted Answer: </b>'. 1650: &cleanRecord($subval,$responsetype,$symb,$partid, 1651: $respid,\%record,$order); 1652: if ($similar) {$lastsubonly.="<br /><br />$similar\n";} 1653: } 1654: } 1655: } 1656: } 1657: $lastsubonly.='</td></tr><tr bgcolor="#ffffff"><td>'."\n"; 1658: $request->print($lastsubonly); 1659: } elsif ($ENV{'form.lastSub'} eq 'datesub') { 1660: my (undef,$responseType,undef,$parts) = &showResourceInfo($url); 1661: $request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom)); 1662: } elsif ($ENV{'form.lastSub'} =~ /^(last|all)$/) { 1663: $request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom, 1664: $ENV{'request.course.id'}, 1665: $last,'.submission', 1666: 'Apache::grades::keywords_highlight')); 1667: } 1668: 1669: $request->print('<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':' 1670: .$udom.'" />'."\n"); 1671: 1672: # return if view submission with no grading option 1673: if ($ENV{'form.showgrading'} eq '' || (!&canmodify($usec))) { 1674: my $toGrade.='<input type="button" value="Grade Student" '. 1675: 'onClick="javascript:checksubmit(this.form,\'Grade Student\',\'' 1676: .$counter.'\');" TARGET=_self> '."\n" if (&canmodify($usec)); 1677: $toGrade.='</td></tr></table></td></tr></table></form>'."\n"; 1678: $toGrade.=&show_grading_menu_form($symb,$url) 1679: if (($ENV{'form.command'} eq 'submission') || 1680: ($ENV{'form.command'} eq 'processGroup' && $counter == $total)); 1681: $request = print($toGrade); 1682: return; 1683: } 1684: 1685: # essay grading message center 1686: if ($ENV{'form.handgrade'} eq 'yes') { 1687: my ($lastname,$givenn) = split(/,/,$ENV{'form.fullname'}); 1688: my $msgfor = $givenn.' '.$lastname; 1689: if (scalar(@col_fullnames) > 0) { 1690: my $lastone = pop @col_fullnames; 1691: $msgfor .= ', '.(join ', ',@col_fullnames).' and '.$lastone.'.'; 1692: } 1693: $msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript 1694: $result='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n". 1695: '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n"; 1696: $result.=' <a href="javascript:msgCenter(document.SCORE,'.$counter. 1697: ',\''.$msgfor.'\')"; TARGET=_self>'. 1698: 'Compose Message to student'.(scalar(@col_fullnames) >= 1 ? 's' : '').'</a> '. 1699: '<img src="'.$request->dir_config('lonIconsURL'). 1700: '/mailbkgrd.gif" width="14" height="10" name="mailicon'.$counter.'" />'."\n". 1701: '<br /> (Message will be sent when you click on Save & Next below.)'."\n" 1702: if ($ENV{'form.handgrade'} eq 'yes'); 1703: $request->print($result); 1704: } 1705: 1706: my %seen = (); 1707: my @partlist; 1708: my @gradePartRespid; 1709: for (sort keys(%$handgrade)) { 1710: my ($partid,$respid) = split(/_/); 1711: next if ($seen{$partid} > 0); 1712: $seen{$partid}++; 1713: next if ($$handgrade{$_} =~ /:no$/ && $ENV{'form.lastSub'} =~ /^(hdgrade)$/); 1714: push @partlist,$partid; 1715: push @gradePartRespid,$partid.'.'.$respid; 1716: 1717: $request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record)); 1718: } 1719: $result='<input type="hidden" name="partlist'.$counter. 1720: '" value="'.(join ":",@partlist).'" />'."\n"; 1721: $result.='<input type="hidden" name="gradePartRespid'. 1722: '" value="'.(join ":",@gradePartRespid).'" />'."\n" if ($counter == 0); 1723: my $ctr = 0; 1724: while ($ctr < scalar(@partlist)) { 1725: $result.='<input type="hidden" name="partid'.$counter.'_'.$ctr.'" value="'. 1726: $partlist[$ctr].'" />'."\n"; 1727: $ctr++; 1728: } 1729: $request->print($result.'</td></tr></table></td></tr></table>'."\n"); 1730: 1731: # print end of form 1732: if ($counter == $total) { 1733: my $endform='<table border="0"><tr><td>'."\n"; 1734: $endform.='<input type="button" value="Save & Next" '. 1735: 'onClick="javascript:checksubmit(this.form,\'Save & Next\','. 1736: $total.','.scalar(@partlist).');" TARGET=_self> '."\n"; 1737: my $ntstu ='<select name="NTSTU">'. 1738: '<option>1</option><option>2</option>'. 1739: '<option>3</option><option>5</option>'. 1740: '<option>7</option><option>10</option></select>'."\n"; 1741: my $nsel = ($ENV{'form.NTSTU'} ne '' ? $ENV{'form.NTSTU'} : '1'); 1742: $ntstu =~ s/<option>$nsel</<option selected="on">$nsel</; 1743: $endform.=$ntstu.'student(s) '; 1744: $endform.='<input type="button" value="Previous" '. 1745: 'onClick="javascript:checksubmit(this.form,\'Previous\');" TARGET=_self> '."\n". 1746: '<input type="button" value="Next" '. 1747: 'onClick="javascript:checksubmit(this.form,\'Next\');" TARGET=_self> '; 1748: $endform.='(Next and Previous (student) do not save the scores.)'."\n" ; 1749: $endform.='</td><tr></table></form>'; 1750: $endform.=&show_grading_menu_form($symb,$url); 1751: $request->print($endform); 1752: } 1753: return ''; 1754: } 1755: 1756: #--- Retrieve the last submission for all the parts 1757: sub get_last_submission { 1758: my ($returnhash)=@_; 1759: my (@string,$timestamp); 1760: if ($$returnhash{'version'}) { 1761: my %lasthash=(); 1762: my ($version); 1763: for ($version=1;$version<=$$returnhash{'version'};$version++) { 1764: foreach (sort(split(/\:/,$$returnhash{$version.':keys'}))) { 1765: $lasthash{$_}=$$returnhash{$version.':'.$_}; 1766: $timestamp = scalar(localtime($$returnhash{$version.':timestamp'})); 1767: } 1768: } 1769: foreach ((keys %lasthash)) { 1770: if ($_ =~ /\.submission$/) { 1771: my ($partid,$foo) = split(/submission$/,$_); 1772: my $draft = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ? 1773: '<font color="red">Draft Copy</font> ' : ''; 1774: push @string, (join(':',$_,$draft.$lasthash{$_})); 1775: } 1776: } 1777: } 1778: @string = $string[0] eq '' ? '<font color="red">Nothing submitted - no attempts.</font>' : @string; 1779: return \@string,\$timestamp; 1780: } 1781: 1782: #--- High light keywords, with style choosen by user. 1783: sub keywords_highlight { 1784: my $string = shift; 1785: my $size = $ENV{'form.kwsize'} eq '0' ? '' : 'size='.$ENV{'form.kwsize'}; 1786: my $styleon = $ENV{'form.kwstyle'} eq '' ? '' : $ENV{'form.kwstyle'}; 1787: (my $styleoff = $styleon) =~ s/\</\<\//; 1788: my @keylist = split(/[,\s+]/,$ENV{'form.keywords'}); 1789: foreach (@keylist) { 1790: $string =~ s/\b\Q$_\E(\b|\.)/<font color\=$ENV{'form.kwclr'} $size\>$styleon$_$styleoff<\/font>/gi; 1791: } 1792: return $string; 1793: } 1794: 1795: #--- Called from submission routine 1796: sub processHandGrade { 1797: my ($request) = shift; 1798: my $url = $ENV{'form.url'}; 1799: my $symb = $ENV{'form.symb'}; 1800: my $button = $ENV{'form.gradeOpt'}; 1801: my $ngrade = $ENV{'form.NCT'}; 1802: my $ntstu = $ENV{'form.NTSTU'}; 1803: if ($button eq 'Save & Next') { 1804: my $ctr = 0; 1805: while ($ctr < $ngrade) { 1806: my ($uname,$udom) = split(/:/,$ENV{'form.unamedom'.$ctr}); 1807: my ($errorflag,$pts,$wgt) = &saveHandGrade($request,$url,$symb,$uname,$udom,$ctr); 1808: if ($errorflag eq 'no_score') { 1809: $ctr++; 1810: next; 1811: } 1812: if ($errorflag eq 'not_allowed') { 1813: $request->print("<font color=\"red\">Not allowed to modify grades for $uname:$udom</font>"); 1814: $ctr++; 1815: next; 1816: } 1817: my $includemsg = $ENV{'form.includemsg'.$ctr}; 1818: my ($subject,$message,$msgstatus) = ('','',''); 1819: if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) { 1820: $subject = $ENV{'form.msgsub'} if ($includemsg =~ /^msgsub/); 1821: my (@msgnum) = split(/,/,$includemsg); 1822: foreach (@msgnum) { 1823: $message.=$ENV{'form.'.$_} if ($_ =~ /savemsg|newmsg/ && $_ ne ''); 1824: } 1825: $message =&Apache::lonfeedback::clear_out_html($message); 1826: $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt; 1827: $message.=" for <a href=\"". 1828: &Apache::lonnet::clutter($url). 1829: "?symb=$symb\">$ENV{'form.probTitle'}</a>"; 1830: $msgstatus = &Apache::lonmsg::user_normal_msg ($uname,$udom, 1831: $ENV{'form.msgsub'},$message); 1832: } 1833: if ($ENV{'form.collaborator'.$ctr}) { 1834: my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr"); 1835: foreach my $collabstr (@collabstrs) { 1836: my ($part,@collaborators) = split(/:/,$collabstr); 1837: foreach (@collaborators) { 1838: my ($errorflag,$pts,$wgt) = 1839: &saveHandGrade($request,$url,$symb,$_,$udom,$ctr, 1840: $ENV{'form.unamedom'.$ctr},$part); 1841: if ($errorflag eq 'not_allowed') { 1842: $request->print("<font color=\"red\">Not allowed to modify grades for $_:$udom</font>"); 1843: next; 1844: } else { 1845: if ($message ne '') { 1846: $msgstatus = &Apache::lonmsg::user_normal_msg($_,$udom,$ENV{'form.msgsub'},$message); 1847: } 1848: 1849: } 1850: } 1851: } 1852: } 1853: $ctr++; 1854: } 1855: } 1856: 1857: if ($ENV{'form.handgrade'} eq 'yes') { 1858: # Keywords sorted in alphabatical order 1859: my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'}; 1860: my %keyhash = (); 1861: $ENV{'form.keywords'} =~ s/,\s{0,}|\s+/ /g; 1862: $ENV{'form.keywords'} =~ s/^\s+|\s+$//; 1863: my (@keywords) = sort(split(/\s+/,$ENV{'form.keywords'})); 1864: $ENV{'form.keywords'} = join(' ',@keywords); 1865: $keyhash{$symb.'_keywords'} = $ENV{'form.keywords'}; 1866: $keyhash{$symb.'_subject'} = $ENV{'form.msgsub'}; 1867: $keyhash{$loginuser.'_kwclr'} = $ENV{'form.kwclr'}; 1868: $keyhash{$loginuser.'_kwsize'} = $ENV{'form.kwsize'}; 1869: $keyhash{$loginuser.'_kwstyle'} = $ENV{'form.kwstyle'}; 1870: 1871: # message center - Order of message gets changed. Blank line is eliminated. 1872: # New messages are saved in ENV for the next student. 1873: # All messages are saved in nohist_handgrade.db 1874: my ($ctr,$idx) = (1,1); 1875: while ($ctr <= $ENV{'form.savemsgN'}) { 1876: if ($ENV{'form.savemsg'.$ctr} ne '') { 1877: $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.savemsg'.$ctr}; 1878: $idx++; 1879: } 1880: $ctr++; 1881: } 1882: $ctr = 0; 1883: while ($ctr < $ngrade) { 1884: if ($ENV{'form.newmsg'.$ctr} ne '') { 1885: $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.newmsg'.$ctr}; 1886: $ENV{'form.savemsg'.$idx} = $ENV{'form.newmsg'.$ctr}; 1887: $idx++; 1888: } 1889: $ctr++; 1890: } 1891: $ENV{'form.savemsgN'} = --$idx; 1892: $keyhash{$symb.'_savemsgN'} = $ENV{'form.savemsgN'}; 1893: my $putresult = &Apache::lonnet::put 1894: ('nohist_handgrade',\%keyhash, 1895: $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, 1896: $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); 1897: } 1898: # Called by Save & Refresh from Highlight Attribute Window 1899: my (undef,undef,$fullname) = &getclasslist($ENV{'form.section'},'1'); 1900: if ($ENV{'form.refresh'} eq 'on') { 1901: my ($ctr,$total) = (0,0); 1902: while ($ctr < $ngrade) { 1903: $total++ if $ENV{'form.unamedom'.$ctr} ne ''; 1904: $ctr++; 1905: } 1906: $ENV{'form.NTSTU'}=$ngrade; 1907: $ctr = 0; 1908: while ($ctr < $total) { 1909: my $processUser = $ENV{'form.unamedom'.$ctr}; 1910: ($ENV{'form.student'},$ENV{'form.userdom'}) = split(/:/,$processUser); 1911: $ENV{'form.fullname'} = $$fullname{$processUser}; 1912: &submission($request,$ctr,$total-1); 1913: $ctr++; 1914: } 1915: return ''; 1916: } 1917: 1918: # Go directly to grade student - from submission or link from chart page 1919: if ($button eq 'Grade Student') { 1920: (undef,undef,$ENV{'form.handgrade'},undef,undef) = &showResourceInfo($url); 1921: my $processUser = $ENV{'form.unamedom'.$ENV{'form.studentNo'}}; 1922: ($ENV{'form.student'},$ENV{'form.userdom'}) = split(/:/,$processUser); 1923: $ENV{'form.fullname'} = $$fullname{$processUser}; 1924: &submission($request,0,0); 1925: return ''; 1926: } 1927: 1928: # Get the next/previous one or group of students 1929: my $firststu = $ENV{'form.unamedom0'}; 1930: my $laststu = $ENV{'form.unamedom'.($ngrade-1)}; 1931: my $ctr = 2; 1932: while ($laststu eq '') { 1933: $laststu = $ENV{'form.unamedom'.($ngrade-$ctr)}; 1934: $ctr++; 1935: $laststu = $firststu if ($ctr > $ngrade); 1936: } 1937: 1938: my (@parsedlist,@nextlist); 1939: my ($nextflg) = 0; 1940: foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) { 1941: if ($nextflg == 1 && $button =~ /Next$/) { 1942: push @parsedlist,$_; 1943: } 1944: $nextflg = 1 if ($_ eq $laststu); 1945: if ($button eq 'Previous') { 1946: last if ($_ eq $firststu); 1947: push @parsedlist,$_; 1948: } 1949: } 1950: $ctr = 0; 1951: @parsedlist = reverse @parsedlist if ($button eq 'Previous'); 1952: my ($partlist) = &response_type($url); 1953: foreach my $student (@parsedlist) { 1954: my $submitonly=$ENV{'form.submitonly'}; 1955: my ($uname,$udom) = split(/:/,$student); 1956: if ($submitonly =~ /^(yes|graded|incorrect)$/) { 1957: # my %record = &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname); 1958: my %status=&student_gradeStatus($url,$symb,$udom,$uname,$partlist); 1959: my $submitted = 0; 1960: my $graded = 1; 1961: foreach (keys(%status)) { 1962: $submitted = 1 if ($status{$_} ne 'nothing'); 1963: $graded = 0 if ($status{$_} =~ /^correct/); 1964: my ($foo,$partid,$foo1) = split(/\./,$_); 1965: if ($status{'resource.'.$partid.'.submitted_by'} ne '') { 1966: $submitted = 0; 1967: } 1968: } 1969: next if (!$submitted && ($submitonly eq 'yes' || 1970: $submitonly eq 'incorrect' || 1971: $submitonly eq 'graded')); 1972: next if (!$graded && ($submitonly eq 'graded' || 1973: $submitonly eq 'incorrect')); 1974: } 1975: push @nextlist,$student if ($ctr < $ntstu); 1976: last if ($ctr == $ntstu); 1977: $ctr++; 1978: } 1979: 1980: $ctr = 0; 1981: my $total = scalar(@nextlist)-1; 1982: 1983: foreach (sort @nextlist) { 1984: my ($uname,$udom,$submitter) = split(/:/); 1985: $ENV{'form.student'} = $uname; 1986: $ENV{'form.userdom'} = $udom; 1987: $ENV{'form.fullname'} = $$fullname{$_}; 1988: &submission($request,$ctr,$total); 1989: $ctr++; 1990: } 1991: if ($total < 0) { 1992: my $the_end = '<h3><font color="red">LON-CAPA User Message</font></h3><br />'."\n"; 1993: $the_end.='<b>Message: </b> No more students for this section or class.<br /><br />'."\n"; 1994: $the_end.='Click on the button below to return to the grading menu.<br /><br />'."\n"; 1995: $the_end.=&show_grading_menu_form ($symb,$url); 1996: $request->print($the_end); 1997: } 1998: return ''; 1999: } 2000: 2001: #---- Save the score and award for each student, if changed 2002: sub saveHandGrade { 2003: my ($request,$url,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_; 2004: my $usec = &Apache::lonnet::getsection($domain,$stuname, 2005: $ENV{'request.course.id'}); 2006: if (!&canmodify($usec)) { return('not_allowed'); } 2007: my %record = &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$domain,$stuname); 2008: my %newrecord = (); 2009: my ($pts,$wgt) = ('',''); 2010: foreach (split(/:/,$ENV{'form.partlist'.$newflg})) { 2011: #collaborator may vary for different parts 2012: if ($submitter && $_ ne $part) { next; } 2013: my $dropMenu = $ENV{'form.GD_SEL'.$newflg.'_'.$_}; 2014: if ($dropMenu eq 'excused') { 2015: if ($record{'resource.'.$_.'.solved'} ne 'excused') { 2016: $newrecord{'resource.'.$_.'.solved'} = 'excused'; 2017: if (exists($record{'resource.'.$_.'.awarded'})) { 2018: $newrecord{'resource.'.$_.'.awarded'} = ''; 2019: } 2020: $newrecord{'resource.'.$_.'.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}"; 2021: } 2022: } elsif ($dropMenu eq 'reset status' 2023: && exists($record{'resource.'.$_.'.solved'})) { #don't bother if no old records -> no attempts 2024: $newrecord{'resource.'.$_.'.tries'} = 0; 2025: $newrecord{'resource.'.$_.'.solved'} = ''; 2026: $newrecord{'resource.'.$_.'.award'} = ''; 2027: $newrecord{'resource.'.$_.'.awarded'} = 0; 2028: $newrecord{'resource.'.$_.'.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}"; 2029: } elsif ($dropMenu eq '') { 2030: $pts = ($ENV{'form.GD_BOX'.$newflg.'_'.$_} ne '' ? 2031: $ENV{'form.GD_BOX'.$newflg.'_'.$_} : 2032: $ENV{'form.RADVAL'.$newflg.'_'.$_}); 2033: if ($pts eq '' && $ENV{'form.GD_SEL'.$newflg.'_'.$_} eq '') { 2034: next; 2035: } 2036: $wgt = $ENV{'form.WGT'.$newflg.'_'.$_} eq '' ? 1 : 2037: $ENV{'form.WGT'.$newflg.'_'.$_}; 2038: my $partial= $pts/$wgt; 2039: if ($partial eq $record{'resource.'.$_.'.awarded'}) { 2040: #do not update score for part if not changed. 2041: next; 2042: } 2043: if ($record{'resource.'.$_.'.awarded'} ne $partial) { 2044: $newrecord{'resource.'.$_.'.awarded'} = $partial; 2045: } 2046: my $reckey = 'resource.'.$_.'.solved'; 2047: if ($partial == 0) { 2048: if ($record{$reckey} ne 'incorrect_by_override') { 2049: $newrecord{$reckey} = 'incorrect_by_override'; 2050: } 2051: } else { 2052: if ($record{$reckey} ne 'correct_by_override') { 2053: $newrecord{$reckey} = 'correct_by_override'; 2054: } 2055: } 2056: if ($submitter && 2057: ($record{'resource.'.$_.'.submitted_by'} ne $submitter)) { 2058: $newrecord{'resource.'.$_.'.submitted_by'} = $submitter; 2059: } 2060: $newrecord{'resource.'.$_.'.regrader'}= 2061: "$ENV{'user.name'}:$ENV{'user.domain'}"; 2062: } 2063: } 2064: if (scalar(keys(%newrecord)) > 0) { 2065: &Apache::lonnet::cstore(\%newrecord,$symb, 2066: $ENV{'request.course.id'},$domain,$stuname); 2067: } 2068: return '',$pts,$wgt; 2069: } 2070: 2071: #-------------------------------------------------------------------------------------- 2072: # 2073: #-------------------------- Next few routines handles grading by section or whole class 2074: # 2075: #--- Javascript to handle grading by section or whole class 2076: sub viewgrades_js { 2077: my ($request) = shift; 2078: 2079: $request->print(<<VIEWJAVASCRIPT); 2080: <script type="text/javascript" language="javascript"> 2081: function writePoint(partid,weight,point) { 2082: var radioButton = document.classgrade["RADVAL_"+partid]; 2083: var textbox = document.classgrade["TEXTVAL_"+partid]; 2084: if (point == "textval") { 2085: point = document.classgrade["TEXTVAL_"+partid].value; 2086: if (isNaN(point) || parseFloat(point) < 0) { 2087: alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point)); 2088: var resetbox = false; 2089: for (var i=0; i<radioButton.length; i++) { 2090: if (radioButton[i].checked) { 2091: textbox.value = i; 2092: resetbox = true; 2093: } 2094: } 2095: if (!resetbox) { 2096: textbox.value = ""; 2097: } 2098: return; 2099: } 2100: if (parseFloat(point) > parseFloat(weight)) { 2101: var resp = confirm("You entered a value ("+parseFloat(point)+ 2102: ") greater than the weight for the part. Accept?"); 2103: if (resp == false) { 2104: textbox.value = ""; 2105: return; 2106: } 2107: } 2108: for (var i=0; i<radioButton.length; i++) { 2109: radioButton[i].checked=false; 2110: if (parseFloat(point) == i) { 2111: radioButton[i].checked=true; 2112: } 2113: } 2114: 2115: } else { 2116: textbox.value = parseFloat(point); 2117: } 2118: for (i=0;i<document.classgrade.total.value;i++) { 2119: var user = document.classgrade["ctr"+i].value; 2120: var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"]; 2121: var saveval = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value; 2122: var selname = document.classgrade["GD_"+user+"_"+partid+"_solved"]; 2123: if (saveval != "correct") { 2124: scorename.value = point; 2125: if (selname[0].selected != true) { 2126: selname[0].selected = true; 2127: } 2128: } 2129: } 2130: document.classgrade["SELVAL_"+partid][0].selected = true; 2131: } 2132: 2133: function writeRadText(partid,weight) { 2134: var selval = document.classgrade["SELVAL_"+partid]; 2135: var radioButton = document.classgrade["RADVAL_"+partid]; 2136: var textbox = document.classgrade["TEXTVAL_"+partid]; 2137: if (selval[1].selected || selval[2].selected) { 2138: for (var i=0; i<radioButton.length; i++) { 2139: radioButton[i].checked=false; 2140: 2141: } 2142: textbox.value = ""; 2143: 2144: for (i=0;i<document.classgrade.total.value;i++) { 2145: var user = document.classgrade["ctr"+i].value; 2146: var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"]; 2147: var saveval = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value; 2148: var selname = document.classgrade["GD_"+user+"_"+partid+"_solved"]; 2149: if (saveval != "correct") { 2150: scorename.value = ""; 2151: if (selval[1].selected) { 2152: selname[1].selected = true; 2153: } else { 2154: selname[2].selected = true; 2155: if (Number(document.classgrade["GD_"+user+"_"+partid+"_tries"].value)) 2156: {document.classgrade["GD_"+user+"_"+partid+"_tries"].value = '0';} 2157: } 2158: } 2159: } 2160: } else { 2161: for (i=0;i<document.classgrade.total.value;i++) { 2162: var user = document.classgrade["ctr"+i].value; 2163: var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"]; 2164: var saveval = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value; 2165: var selname = document.classgrade["GD_"+user+"_"+partid+"_solved"]; 2166: if (saveval != "correct") { 2167: scorename.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value; 2168: selname[0].selected = true; 2169: } 2170: } 2171: } 2172: } 2173: 2174: function changeSelect(partid,user) { 2175: var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"]; 2176: var textbox = document.classgrade["GD_"+user+'_'+partid+"_awarded"]; 2177: var point = textbox.value; 2178: var weight = document.classgrade["weight_"+partid].value; 2179: 2180: if (isNaN(point) || parseFloat(point) < 0) { 2181: alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point)); 2182: textbox.value = ""; 2183: return; 2184: } 2185: if (parseFloat(point) > parseFloat(weight)) { 2186: var resp = confirm("You entered a value ("+parseFloat(point)+ 2187: ") greater than the weight of the part. Accept?"); 2188: if (resp == false) { 2189: textbox.value = ""; 2190: return; 2191: } 2192: } 2193: selval[0].selected = true; 2194: } 2195: 2196: function changeOneScore(partid,user) { 2197: var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"]; 2198: if (selval[1].selected || selval[2].selected) { 2199: document.classgrade["GD_"+user+'_'+partid+"_awarded"].value = ""; 2200: if (selval[2].selected) { 2201: document.classgrade["GD_"+user+'_'+partid+"_tries"].value = "0"; 2202: } 2203: } 2204: } 2205: 2206: function resetEntry(numpart) { 2207: for (ctpart=0;ctpart<numpart;ctpart++) { 2208: var partid = document.classgrade["partid_"+ctpart].value; 2209: var radioButton = document.classgrade["RADVAL_"+partid]; 2210: var textbox = document.classgrade["TEXTVAL_"+partid]; 2211: var selval = document.classgrade["SELVAL_"+partid]; 2212: for (var i=0; i<radioButton.length; i++) { 2213: radioButton[i].checked=false; 2214: 2215: } 2216: textbox.value = ""; 2217: selval[0].selected = true; 2218: 2219: for (i=0;i<document.classgrade.total.value;i++) { 2220: var user = document.classgrade["ctr"+i].value; 2221: var resetscore = document.classgrade["GD_"+user+"_"+partid+"_awarded"]; 2222: resetscore.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value; 2223: var resettries = document.classgrade["GD_"+user+"_"+partid+"_tries"]; 2224: resettries.value = document.classgrade["GD_"+user+"_"+partid+"_tries_s"].value; 2225: var saveselval = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value; 2226: var selname = document.classgrade["GD_"+user+"_"+partid+"_solved"]; 2227: if (saveselval == "excused") { 2228: if (selname[1].selected == false) { selname[1].selected = true;} 2229: } else { 2230: if (selname[0].selected == false) {selname[0].selected = true}; 2231: } 2232: } 2233: } 2234: } 2235: 2236: </script> 2237: VIEWJAVASCRIPT 2238: } 2239: 2240: #--- show scores for a section or whole class w/ option to change/update a score 2241: sub viewgrades { 2242: my ($request) = shift; 2243: &viewgrades_js($request); 2244: 2245: my ($symb,$url) = ($ENV{'form.symb'},$ENV{'form.url'}); 2246: my $result='<h3><font color="#339933">Manual Grading</font></h3>'; 2247: 2248: $result.='<font size=+1><b>Current Resource: </b>'.$ENV{'form.probTitle'}.'</font>'."\n"; 2249: 2250: #view individual student submission form - called using Javascript viewOneStudent 2251: $result.=&jscriptNform($url,$symb); 2252: 2253: #beginning of class grading form 2254: $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n". 2255: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 2256: '<input type="hidden" name="url" value="'.$url.'" />'."\n". 2257: '<input type="hidden" name="command" value="editgrades" />'."\n". 2258: '<input type="hidden" name="section" value="'.$ENV{'form.section'}.'" />'."\n". 2259: '<input type="hidden" name="saveState" value="'.$ENV{'form.saveState'}.'" />'."\n". 2260: '<input type="hidden" name="Status" value="'.$ENV{'form.Status'}.'" />'."\n". 2261: '<input type="hidden" name="probTitle" value="'.$ENV{'form.probTitle'}.'" />'."\n"; 2262: 2263: my $sectionClass; 2264: if ($ENV{'form.section'} eq 'all') { 2265: $sectionClass='Class </h3>'; 2266: } elsif ($ENV{'form.section'} eq 'no') { 2267: $sectionClass='Students in no Section </h3>'; 2268: } else { 2269: $sectionClass='Students in Section '.$ENV{'form.section'}.'</h3>'; 2270: } 2271: $result.='<h3>Assign Common Grade To '.$sectionClass; 2272: $result.= '<table border=0><tr><td bgcolor="#777777">'."\n". 2273: '<table border=0><tr bgcolor="#ffffdd"><td>'; 2274: #radio buttons/text box for assigning points for a section or class. 2275: #handles different parts of a problem 2276: my ($partlist,$handgrade) = &response_type($url,$symb); 2277: my %weight = (); 2278: my $ctsparts = 0; 2279: $result.='<table border="0">'; 2280: my %seen = (); 2281: for (sort keys(%$handgrade)) { 2282: my ($partid,$respid) = split (/_/,$_,2); 2283: next if $seen{$partid}; 2284: $seen{$partid}++; 2285: my $handgrade=$$handgrade{$_}; 2286: my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb); 2287: $weight{$partid} = $wgt eq '' ? '1' : $wgt; 2288: 2289: $result.='<input type="hidden" name="partid_'. 2290: $ctsparts.'" value="'.$partid.'" />'."\n"; 2291: $result.='<input type="hidden" name="weight_'. 2292: $partid.'" value="'.$weight{$partid}.'" />'."\n"; 2293: $result.='<tr><td><b>Part '.$partid.' Point:</b> </td><td>'; 2294: $result.='<table border="0"><tr>'; 2295: my $ctr = 0; 2296: while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across 2297: $result.= '<td><input type="radio" name="RADVAL_'.$partid.'" '. 2298: 'onclick="javascript:writePoint(\''.$partid.'\','.$weight{$partid}. 2299: ','.$ctr.')" />'.$ctr."</td>\n"; 2300: $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : ''); 2301: $ctr++; 2302: } 2303: $result.='</tr></table>'; 2304: $result.= '</td><td><b> or </b><input type="text" name="TEXTVAL_'. 2305: $partid.'" size="4" '.'onChange="javascript:writePoint(\''. 2306: $partid.'\','.$weight{$partid}.',\'textval\')" /> /'. 2307: $weight{$partid}.' (problem weight)</td>'."\n"; 2308: $result.= '</td><td><select name="SELVAL_'.$partid.'"'. 2309: 'onChange="javascript:writeRadText(\''.$partid.'\','. 2310: $weight{$partid}.')"> '. 2311: '<option selected="on"> </option>'. 2312: '<option>excused</option>'. 2313: '<option>reset status</option></select></td></tr>'."\n"; 2314: $ctsparts++; 2315: } 2316: $result.='</table>'.'</td></tr></table>'.'</td></tr></table>'."\n". 2317: '<input type="hidden" name="totalparts" value="'.$ctsparts.'" />'; 2318: $result.='<input type="button" value="Reset" '. 2319: 'onClick="javascript:resetEntry('.$ctsparts.');" TARGET=_self>'; 2320: 2321: #table listing all the students in a section/class 2322: #header of table 2323: $result.= '<h3>Assign Grade to Specific Students in '.$sectionClass; 2324: $result.= '<table border=0><tr><td bgcolor="#777777">'."\n". 2325: '<table border=0><tr bgcolor="#deffff"><td> <b>No.</b> </td>'. 2326: '<td>'.&nameUserString('header')."</td>\n"; 2327: my (@parts) = sort(&getpartlist($url,$symb)); 2328: foreach my $part (@parts) { 2329: my $display=&Apache::lonnet::metadata($url,$part.'.display'); 2330: $display =~ s|^Number of Attempts|Tries<br />|; # makes the column narrower 2331: if (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); } 2332: if ($display =~ /^Partial Credit Factor/) { 2333: my ($partid) = &split_part_type($part); 2334: $result.='<td><b>Score Part '.$partid.'<br />(weight = '. 2335: $weight{$partid}.')</b></td>'."\n"; 2336: next; 2337: } 2338: $display =~ s|Problem Status|Grade Status<br />|; 2339: $result.='<td><b>'.$display.'</b></td>'."\n"; 2340: } 2341: $result.='</tr>'; 2342: 2343: #get info for each student 2344: #list all the students - with points and grade status 2345: my (undef,undef,$fullname) = &getclasslist($ENV{'form.section'},'1'); 2346: my $ctr = 0; 2347: foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) { 2348: my $uname = $_; 2349: $uname=~s/:/_/; 2350: $result.='<input type="hidden" name="ctr'.$ctr.'" value="'.$uname.'" />'."\n"; 2351: $ctr++; 2352: $result.=&viewstudentgrade($url,$symb,$ENV{'request.course.id'}, 2353: $_,$$fullname{$_},\@parts,\%weight,$ctr); 2354: } 2355: $result.='</table></td></tr></table>'; 2356: $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n"; 2357: $result.='<input type="button" value="Save" '. 2358: 'onClick="javascript:submit();" TARGET=_self /></form>'."\n"; 2359: if (scalar(%$fullname) eq 0) { 2360: my $colspan=3+scalar(@parts); 2361: $result='<font color="red">There are no students in section "'.$ENV{'form.section'}. 2362: '" with enrollment status "'.$ENV{'form.Status'}.'" to modify or grade.</font>'; 2363: } 2364: $result.=&show_grading_menu_form($symb,$url); 2365: return $result; 2366: } 2367: 2368: #--- call by previous routine to display each student 2369: sub viewstudentgrade { 2370: my ($url,$symb,$courseid,$student,$fullname,$parts,$weight,$ctr) = @_; 2371: my ($uname,$udom) = split(/:/,$student); 2372: $student=~s/:/_/; 2373: my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname); 2374: my $result='<tr bgcolor="#ffffdd"><td align="right">'.$ctr.' </td><td> '. 2375: '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom. 2376: '\')"; TARGET=_self>'.$fullname.'</a> '. 2377: '<font color="#999999">('.$uname.($ENV{'user.domain'} eq $udom ? '' : ':'.$udom).')</font></td>'."\n"; 2378: foreach my $apart (@$parts) { 2379: my ($part,$type) = &split_part_type($apart); 2380: my $score=$record{"resource.$part.$type"}; 2381: if ($type eq 'awarded') { 2382: my $pts = $score eq '' ? '' : $score*$$weight{$part}; 2383: $result.='<input type="hidden" name="'. 2384: 'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n"; 2385: $result.='<td align="middle"><input type="text" name="'. 2386: 'GD_'.$student.'_'.$part.'_awarded" '. 2387: 'onChange="javascript:changeSelect(\''.$part.'\',\''.$student. 2388: '\')" value="'.$pts.'" size="4" /></td>'."\n"; 2389: } elsif ($type eq 'solved') { 2390: my ($status,$foo)=split(/_/,$score,2); 2391: $status = 'nothing' if ($status eq ''); 2392: $result.='<input type="hidden" name="'.'GD_'.$student.'_'. 2393: $part.'_solved_s" value="'.$status.'" />'."\n"; 2394: $result.='<td align="middle"> <select name="'. 2395: 'GD_'.$student.'_'.$part.'_solved" '. 2396: 'onChange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n"; 2397: $result.= (($status eq 'excused') ? '<option> </option><option selected="on">excused</option>' 2398: : '<option selected="on"> </option><option>excused</option>')."\n"; 2399: $result.='<option>reset status</option>'; 2400: $result.="</select> </td>\n"; 2401: } else { 2402: $result.='<input type="hidden" name="'. 2403: 'GD_'.$student.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'. 2404: "\n"; 2405: $result.='<td align="middle"><input type="text" name="'. 2406: 'GD_'.$student.'_'.$part.'_'.$type.'" '. 2407: 'value="'.$score.'" size="4" /></td>'."\n"; 2408: } 2409: } 2410: $result.='</tr>'; 2411: return $result; 2412: } 2413: 2414: #--- change scores for all the students in a section/class 2415: # record does not get update if unchanged 2416: sub editgrades { 2417: my ($request) = @_; 2418: 2419: my $symb=$ENV{'form.symb'}; 2420: my $url =$ENV{'form.url'}; 2421: my $title='<h3><font color="#339933">Current Grade Status</font></h3>'; 2422: $title.='<font size=+1><b>Current Resource: </b>'.$ENV{'form.probTitle'}.'</font><br />'."\n"; 2423: $title.='<font size=+1><b>Section: </b>'.$ENV{'form.section'}.'</font>'."\n"; 2424: 2425: my $result= '<table border="0"><tr><td bgcolor="#777777">'."\n"; 2426: $result.= '<table border="0"><tr bgcolor="#deffff">'. 2427: '<td rowspan=2 valign="center"> <b>No.</b> </td>'. 2428: '<td rowspan=2 valign="center">'.&nameUserString('header')."</td>\n"; 2429: 2430: my %scoreptr = ( 2431: 'correct' =>'correct_by_override', 2432: 'incorrect'=>'incorrect_by_override', 2433: 'excused' =>'excused', 2434: 'ungraded' =>'ungraded_attempted', 2435: 'nothing' => '', 2436: ); 2437: my ($classlist,undef,$fullname) = &getclasslist($ENV{'form.section'},'0'); 2438: 2439: my (@partid); 2440: my %weight = (); 2441: my %columns = (); 2442: my ($i,$ctr,$count,$rec_update) = (0,0,0,0); 2443: 2444: my (@parts) = sort(&getpartlist($url,$symb)); 2445: my $header; 2446: while ($ctr < $ENV{'form.totalparts'}) { 2447: my $partid = $ENV{'form.partid_'.$ctr}; 2448: push @partid,$partid; 2449: $weight{$partid} = $ENV{'form.weight_'.$partid}; 2450: $ctr++; 2451: } 2452: foreach my $partid (@partid) { 2453: $header .= '<td align="center"> <b>Old Score</b> </td>'. 2454: '<td align="center"> <b>New Score</b> </td>'; 2455: $columns{$partid}=2; 2456: foreach my $stores (@parts) { 2457: my ($part,$type) = &split_part_type($stores); 2458: if ($part !~ m/^\Q$partid\E/) { next;} 2459: if ($type eq 'awarded' || $type eq 'solved') { next; } 2460: my $display=&Apache::lonnet::metadata($url,$stores.'.display'); 2461: $display =~ s/\[Part: (\w)+\]//; 2462: $display =~ s/Number of Attempts/Tries/; 2463: $header .= '<td align="center"> <b>Old '.$display.'</b> </td>'. 2464: '<td align="center"> <b>New '.$display.'</b> </td>'; 2465: $columns{$partid}+=2; 2466: } 2467: } 2468: foreach my $partid (@partid) { 2469: $result .= '<td colspan="'.$columns{$partid}. 2470: '" align="center"><b>Part '.$partid. 2471: '</b> (Weight = '.$weight{$partid}.')</td>'; 2472: 2473: } 2474: $result .= '</tr><tr bgcolor="#deffff">'; 2475: $result .= $header; 2476: $result .= '</tr>'."\n"; 2477: my $noupdate; 2478: my ($updateCtr,$noupdateCtr) = (1,1); 2479: for ($i=0; $i<$ENV{'form.total'}; $i++) { 2480: my $line; 2481: my $user = $ENV{'form.ctr'.$i}; 2482: my $usercolon = $user; 2483: $usercolon =~s/_/:/; 2484: my ($uname,$udom)=split(/_/,$user); 2485: my %newrecord; 2486: my $updateflag = 0; 2487: $line .= '<td>'.&nameUserString(undef,$$fullname{$usercolon},$uname,$udom).'</td>'; 2488: my $usec=$classlist->{"$uname:$udom"}[5]; 2489: if (!&canmodify($usec)) { 2490: my $numcols=scalar(@partid)*4+2; 2491: $noupdate.=$line."<td colspan=\"$numcols\"><font color=\"red\">Not allowed to modify student</font></td></tr>"; 2492: next; 2493: } 2494: foreach (@partid) { 2495: my $old_aw = $ENV{'form.GD_'.$user.'_'.$_.'_awarded_s'}; 2496: my $old_part_pcr = $old_aw/($weight{$_} ne '0' ? $weight{$_}:1); 2497: my $old_part = $old_aw eq '' ? '' : $old_part_pcr; 2498: my $old_score = $scoreptr{$ENV{'form.GD_'.$user.'_'.$_.'_solved_s'}}; 2499: 2500: my $awarded = $ENV{'form.GD_'.$user.'_'.$_.'_awarded'}; 2501: my $pcr = $awarded/($weight{$_} ne '0' ? $weight{$_} : 1); 2502: my $partial = $awarded eq '' ? '' : $pcr; 2503: my $score; 2504: if ($partial eq '') { 2505: $score = $scoreptr{$ENV{'form.GD_'.$user.'_'.$_.'_solved_s'}}; 2506: } elsif ($partial > 0) { 2507: $score = 'correct_by_override'; 2508: } elsif ($partial == 0) { 2509: $score = 'incorrect_by_override'; 2510: } 2511: my $dropMenu = $ENV{'form.GD_'.$user.'_'.$_.'_solved'}; 2512: $score = 'excused' if (($dropMenu eq 'excused') && ($score ne 'excused')); 2513: 2514: if ($dropMenu eq 'reset status' && 2515: $old_score ne '') { # ignore if no previous attempts => nothing to reset 2516: $newrecord{'resource.'.$_.'.tries'} = 0; 2517: $newrecord{'resource.'.$_.'.solved'} = ''; 2518: $newrecord{'resource.'.$_.'.award'} = ''; 2519: $newrecord{'resource.'.$_.'.awarded'} = 0; 2520: $newrecord{'resource.'.$_.'.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}"; 2521: $updateflag = 1; 2522: } elsif (!($old_part eq $partial && $old_score eq $score)) { 2523: $updateflag = 1; 2524: $newrecord{'resource.'.$_.'.awarded'} = $partial if $partial ne ''; 2525: $newrecord{'resource.'.$_.'.solved'} = $score; 2526: $rec_update++; 2527: } 2528: 2529: $line .= '<td align="center">'.$old_aw.' </td>'. 2530: '<td align="center">'.$awarded. 2531: ($score eq 'excused' ? $score : '').' </td>'; 2532: 2533: 2534: my $partid=$_; 2535: foreach my $stores (@parts) { 2536: my ($part,$type) = &split_part_type($stores); 2537: if ($part !~ m/^\Q$partid\E/) { next;} 2538: if ($type eq 'awarded' || $type eq 'solved') { next; } 2539: my $old_aw = $ENV{'form.GD_'.$user.'_'.$part.'_'.$type.'_s'}; 2540: my $awarded = $ENV{'form.GD_'.$user.'_'.$part.'_'.$type}; 2541: if ($awarded ne '' && $awarded ne $old_aw) { 2542: $newrecord{'resource.'.$part.'.'.$type}= $awarded; 2543: $newrecord{'resource.'.$part.'.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}"; 2544: $updateflag=1; 2545: } 2546: $line .= '<td align="center">'.$old_aw.' </td>'. 2547: '<td align="center">'.$awarded.' </td>'; 2548: } 2549: } 2550: $line.='</tr>'."\n"; 2551: if ($updateflag) { 2552: $count++; 2553: &Apache::lonnet::cstore(\%newrecord,$symb,$ENV{'request.course.id'}, 2554: $udom,$uname); 2555: $result.='<tr bgcolor="#ffffde"><td align="right"> '.$updateCtr.' </td>'.$line; 2556: $updateCtr++; 2557: } else { 2558: $noupdate.='<tr bgcolor="#ffffde"><td align="right"> '.$noupdateCtr.' </td>'.$line; 2559: $noupdateCtr++; 2560: } 2561: } 2562: if ($noupdate) { 2563: # my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3; 2564: my $numcols=scalar(@partid)*4+2; 2565: $result .= '<tr bgcolor="#ffffff"><td align="center" colspan="'.$numcols.'">No Changes Occurred For the Students Below</td></tr>'.$noupdate; 2566: } 2567: $result .= '</table></td></tr></table>'."\n". 2568: &show_grading_menu_form ($symb,$url); 2569: my $msg = '<br /><b>Number of records updated = '.$rec_update. 2570: ' for '.$count.' student'.($count <= 1 ? '' : 's').'.</b><br />'. 2571: '<b>Total number of students = '.$ENV{'form.total'}.'</b><br />'; 2572: return $title.$msg.$result; 2573: } 2574: 2575: sub split_part_type { 2576: my ($partstr) = @_; 2577: my ($temp,@allparts)=split(/_/,$partstr); 2578: my $type=pop(@allparts); 2579: my $part=join('.',@allparts); 2580: return ($part,$type); 2581: } 2582: 2583: #------------- end of section for handling grading by section/class --------- 2584: # 2585: #---------------------------------------------------------------------------- 2586: 2587: 2588: #---------------------------------------------------------------------------- 2589: # 2590: #-------------------------- Next few routines handles grading by csv upload 2591: # 2592: #--- Javascript to handle csv upload 2593: sub csvupload_javascript_reverse_associate { 2594: return(<<ENDPICK); 2595: function verify(vf) { 2596: var foundsomething=0; 2597: var founduname=0; 2598: var founddomain=0; 2599: for (i=0;i<=vf.nfields.value;i++) { 2600: tw=eval('vf.f'+i+'.selectedIndex'); 2601: if (i==0 && tw!=0) { founduname=1; } 2602: if (i==1 && tw!=0) { founddomain=1; } 2603: if (i!=0 && i!=1 && tw!=0) { foundsomething=1; } 2604: } 2605: if (founduname==0 || founddomain==0) { 2606: alert('You need to specify at both the username and domain'); 2607: return; 2608: } 2609: if (foundsomething==0) { 2610: alert('You need to specify at least one grading field'); 2611: return; 2612: } 2613: vf.submit(); 2614: } 2615: function flip(vf,tf) { 2616: var nw=eval('vf.f'+tf+'.selectedIndex'); 2617: var i; 2618: for (i=0;i<=vf.nfields.value;i++) { 2619: //can not pick the same destination field for both name and domain 2620: if (((i ==0)||(i ==1)) && 2621: ((tf==0)||(tf==1)) && 2622: (i!=tf) && 2623: (eval('vf.f'+i+'.selectedIndex')==nw)) { 2624: eval('vf.f'+i+'.selectedIndex=0;') 2625: } 2626: } 2627: } 2628: ENDPICK 2629: } 2630: 2631: sub csvupload_javascript_forward_associate { 2632: return(<<ENDPICK); 2633: function verify(vf) { 2634: var foundsomething=0; 2635: var founduname=0; 2636: var founddomain=0; 2637: for (i=0;i<=vf.nfields.value;i++) { 2638: tw=eval('vf.f'+i+'.selectedIndex'); 2639: if (tw==1) { founduname=1; } 2640: if (tw==2) { founddomain=1; } 2641: if (tw>2) { foundsomething=1; } 2642: } 2643: if (founduname==0 || founddomain==0) { 2644: alert('You need to specify at both the username and domain'); 2645: return; 2646: } 2647: if (foundsomething==0) { 2648: alert('You need to specify at least one grading field'); 2649: return; 2650: } 2651: vf.submit(); 2652: } 2653: function flip(vf,tf) { 2654: var nw=eval('vf.f'+tf+'.selectedIndex'); 2655: var i; 2656: //can not pick the same destination field twice 2657: for (i=0;i<=vf.nfields.value;i++) { 2658: if ((i!=tf) && (eval('vf.f'+i+'.selectedIndex')==nw)) { 2659: eval('vf.f'+i+'.selectedIndex=0;') 2660: } 2661: } 2662: } 2663: ENDPICK 2664: } 2665: 2666: sub csvuploadmap_header { 2667: my ($request,$symb,$url,$datatoken,$distotal)= @_; 2668: my $javascript; 2669: if ($ENV{'form.upfile_associate'} eq 'reverse') { 2670: $javascript=&csvupload_javascript_reverse_associate(); 2671: } else { 2672: $javascript=&csvupload_javascript_forward_associate(); 2673: } 2674: 2675: my ($result) = &showResourceInfo($url,$ENV{'form.probTitle'}); 2676: 2677: $request->print(<<ENDPICK); 2678: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload"> 2679: <h3><font color="#339933">Uploading Class Grades</font></h3> 2680: $result 2681: <hr> 2682: <h3>Identify fields</h3> 2683: Total number of records found in file: $distotal <hr /> 2684: Enter as many fields as you can. The system will inform you and bring you back 2685: to this page if the data selected is insufficient to run your class.<hr /> 2686: <input type="button" value="Reverse Association" onClick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" /> 2687: <input type="hidden" name="associate" value="" /> 2688: <input type="hidden" name="phase" value="three" /> 2689: <input type="hidden" name="datatoken" value="$datatoken" /> 2690: <input type="hidden" name="fileupload" value="$ENV{'form.fileupload'}" /> 2691: <input type="hidden" name="upfiletype" value="$ENV{'form.upfiletype'}" /> 2692: <input type="hidden" name="upfile_associate" 2693: value="$ENV{'form.upfile_associate'}" /> 2694: <input type="hidden" name="symb" value="$symb" /> 2695: <input type="hidden" name="url" value="$url" /> 2696: <input type="hidden" name="saveState" value="$ENV{'form.saveState'}" /> 2697: <input type="hidden" name="probTitle" value="$ENV{'form.probTitle'}" /> 2698: <input type="hidden" name="command" value="csvuploadassign" /> 2699: <hr /> 2700: <script type="text/javascript" language="Javascript"> 2701: $javascript 2702: </script> 2703: ENDPICK 2704: return ''; 2705: 2706: } 2707: 2708: sub csvupload_fields { 2709: my ($url,$symb) = @_; 2710: my (@parts) = &getpartlist($url,$symb); 2711: my @fields=(['username','Student Username'],['domain','Student Domain']); 2712: foreach my $part (sort(@parts)) { 2713: my @datum; 2714: my $display=&Apache::lonnet::metadata($url,$part.'.display'); 2715: my $name=$part; 2716: if (!$display) { $display = $name; } 2717: @datum=($name,$display); 2718: push(@fields,\@datum); 2719: } 2720: return (@fields); 2721: } 2722: 2723: sub csvuploadmap_footer { 2724: my ($request,$i,$keyfields) =@_; 2725: $request->print(<<ENDPICK); 2726: </table> 2727: <input type="hidden" name="nfields" value="$i" /> 2728: <input type="hidden" name="keyfields" value="$keyfields" /> 2729: <input type="button" onClick="javascript:verify(this.form)" value="Assign Grades" /><br /> 2730: </form> 2731: ENDPICK 2732: } 2733: 2734: sub upcsvScores_form { 2735: my ($request) = shift; 2736: my ($symb,$url)=&get_symb_and_url($request); 2737: if (!$symb) {return '';} 2738: my $result =<<CSVFORMJS; 2739: <script type="text/javascript" language="javascript"> 2740: function checkUpload(formname) { 2741: if (formname.upfile.value == "") { 2742: alert("Please use the browse button to select a file from your local directory."); 2743: return false; 2744: } 2745: formname.submit(); 2746: } 2747: </script> 2748: CSVFORMJS 2749: $ENV{'form.probTitle'} = &Apache::lonnet::gettitle($symb); 2750: my ($table) = &showResourceInfo($url,$ENV{'form.probTitle'}); 2751: $result.=$table; 2752: $result.='<br /><table width=100% border=0><tr><td bgcolor="#777777">'."\n"; 2753: $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n"; 2754: $result.=' <b>Specify a file containing the class scores for current resource'. 2755: '.</b></td></tr>'."\n"; 2756: $result.='<tr bgcolor=#ffffe6><td>'."\n"; 2757: my $upfile_select=&Apache::loncommon::upfile_select_html(); 2758: $result.=<<ENDUPFORM; 2759: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload"> 2760: <input type="hidden" name="symb" value="$symb" /> 2761: <input type="hidden" name="url" value="$url" /> 2762: <input type="hidden" name="command" value="csvuploadmap" /> 2763: <input type="hidden" name="probTitle" value="$ENV{'form.probTitle'}" /> 2764: <input type="hidden" name="saveState" value="$ENV{'form.saveState'}" /> 2765: $upfile_select 2766: <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scores" /> 2767: 2768: </form> 2769: ENDUPFORM 2770: $result.='</td></tr></table>'."\n"; 2771: $result.='</td></tr></table><br /><br />'."\n"; 2772: $result.=&show_grading_menu_form($symb,$url); 2773: return $result; 2774: } 2775: 2776: 2777: sub csvuploadmap { 2778: my ($request)= @_; 2779: my ($symb,$url)=&get_symb_and_url($request); 2780: if (!$symb) {return '';} 2781: 2782: my $datatoken; 2783: if (!$ENV{'form.datatoken'}) { 2784: $datatoken=&Apache::loncommon::upfile_store($request); 2785: } else { 2786: $datatoken=$ENV{'form.datatoken'}; 2787: &Apache::loncommon::load_tmp_file($request); 2788: } 2789: my @records=&Apache::loncommon::upfile_record_sep(); 2790: &csvuploadmap_header($request,$symb,$url,$datatoken,$#records+1); 2791: my ($i,$keyfields); 2792: if (@records) { 2793: my @fields=&csvupload_fields($url,$symb); 2794: 2795: if ($ENV{'form.upfile_associate'} eq 'reverse') { 2796: &Apache::loncommon::csv_print_samples($request,\@records); 2797: $i=&Apache::loncommon::csv_print_select_table($request,\@records, 2798: \@fields); 2799: foreach (@fields) { $keyfields.=$_->[0].','; } 2800: chop($keyfields); 2801: } else { 2802: unshift(@fields,['none','']); 2803: $i=&Apache::loncommon::csv_samples_select_table($request,\@records, 2804: \@fields); 2805: my %sone=&Apache::loncommon::record_sep($records[0]); 2806: $keyfields=join(',',sort(keys(%sone))); 2807: } 2808: } 2809: &csvuploadmap_footer($request,$i,$keyfields); 2810: $request->print(&show_grading_menu_form($symb,$url)); 2811: 2812: return ''; 2813: } 2814: 2815: sub csvuploadassign { 2816: my ($request)= @_; 2817: my ($symb,$url)=&get_symb_and_url($request); 2818: if (!$symb) {return '';} 2819: &Apache::loncommon::load_tmp_file($request); 2820: my @gradedata = &Apache::loncommon::upfile_record_sep(); 2821: my @keyfields = split(/\,/,$ENV{'form.keyfields'}); 2822: my %fields=(); 2823: for (my $i=0; $i<=$ENV{'form.nfields'}; $i++) { 2824: if ($ENV{'form.upfile_associate'} eq 'reverse') { 2825: if ($ENV{'form.f'.$i} ne 'none') { 2826: $fields{$keyfields[$i]}=$ENV{'form.f'.$i}; 2827: } 2828: } else { 2829: if ($ENV{'form.f'.$i} ne 'none') { 2830: $fields{$ENV{'form.f'.$i}}=$keyfields[$i]; 2831: } 2832: } 2833: } 2834: $request->print('<h3>Assigning Grades</h3>'); 2835: my $courseid=$ENV{'request.course.id'}; 2836: my ($classlist) = &getclasslist('all',0); 2837: my @notallowed; 2838: my @skipped; 2839: my $countdone=0; 2840: foreach my $grade (@gradedata) { 2841: my %entries=&Apache::loncommon::record_sep($grade); 2842: my $username=$entries{$fields{'username'}}; 2843: my $domain=$entries{$fields{'domain'}}; 2844: if (!exists($$classlist{"$username:$domain"})) { 2845: push(@skipped,"$username:$domain"); 2846: next; 2847: } 2848: my $usec=$classlist->{"$username:$domain"}[5]; 2849: if (!&canmodify($usec)) { 2850: push(@notallowed,"$username:$domain"); 2851: next; 2852: } 2853: my %grades; 2854: foreach my $dest (keys(%fields)) { 2855: if ($dest eq 'username' || $dest eq 'domain') { next; } 2856: if ($entries{$fields{$dest}} eq '') { next; } 2857: my $store_key=$dest; 2858: $store_key=~s/^stores/resource/; 2859: $store_key=~s/_/\./g; 2860: $grades{$store_key}=$entries{$fields{$dest}}; 2861: } 2862: $grades{"resource.regrader"}="$ENV{'user.name'}:$ENV{'user.domain'}"; 2863: &Apache::lonnet::cstore(\%grades,$symb,$ENV{'request.course.id'}, 2864: $domain,$username); 2865: $request->print('.'); 2866: $request->rflush(); 2867: $countdone++; 2868: } 2869: $request->print("<br />Stored $countdone students\n"); 2870: if (@skipped) { 2871: $request->print('<p<font size="+1"><b>Skipped Students</b></font></p>'); 2872: foreach my $student (@skipped) { $request->print("$student<br />\n"); } 2873: } 2874: if (@notallowed) { 2875: $request->print('<p><font size="+1" color="red"><b>Students Not Allowed to Modify</b></font></p>'); 2876: foreach my $student (@notallowed) { $request->print("$student<br />\n"); } 2877: } 2878: $request->print("<br />\n"); 2879: $request->print(&show_grading_menu_form($symb,$url)); 2880: return ''; 2881: } 2882: #------------- end of section for handling csv file upload --------- 2883: # 2884: #------------------------------------------------------------------- 2885: # 2886: #-------------- Next few routines handle grading by page/sequence 2887: # 2888: #--- Select a page/sequence and a student to grade 2889: sub pickStudentPage { 2890: my ($request) = shift; 2891: 2892: $request->print(<<LISTJAVASCRIPT); 2893: <script type="text/javascript" language="javascript"> 2894: 2895: function checkPickOne(formname) { 2896: if (radioSelection(formname.student) == null) { 2897: alert("Please select the student you wish to grade."); 2898: return; 2899: } 2900: ptr = pullDownSelection(formname.selectpage); 2901: formname.page.value = formname["page"+ptr].value; 2902: formname.title.value = formname["title"+ptr].value; 2903: formname.submit(); 2904: } 2905: 2906: </script> 2907: LISTJAVASCRIPT 2908: &commonJSfunctions($request); 2909: my ($symb,$url) = &get_symb_and_url($request); 2910: my $cdom = $ENV{"course.$ENV{'request.course.id'}.domain"}; 2911: my $cnum = $ENV{"course.$ENV{'request.course.id'}.num"}; 2912: my $getsec = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'}; 2913: 2914: my $result='<h3><font color="#339933"> '. 2915: 'Manual Grading by Page or Sequence</font></h3>'; 2916: 2917: $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n"; 2918: $result.=' <b>Problems from:</b> <select name="selectpage">'."\n"; 2919: my ($titles,$symbx) = &getSymbMap($request); 2920: my ($curpage) =&Apache::lonnet::decode_symb($symb); 2921: # my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb); 2922: # my $type=($curpage =~ /\.(page|sequence)/); 2923: my $ctr=0; 2924: foreach (@$titles) { 2925: my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/); 2926: $result.='<option value="'.$ctr.'" '. 2927: ($$symbx{$_} =~ /$curpage$/ ? 'selected="on"' : ''). 2928: '>'.$showtitle.'</option>'."\n"; 2929: $ctr++; 2930: } 2931: $result.= '</select>'."<br>\n"; 2932: $ctr=0; 2933: foreach (@$titles) { 2934: my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/); 2935: $result.='<input type="hidden" name="page'.$ctr.'" value="'.$$symbx{$_}.'" />'."\n"; 2936: $result.='<input type="hidden" name="title'.$ctr.'" value="'.$showtitle.'" />'."\n"; 2937: $ctr++; 2938: } 2939: $result.='<input type="hidden" name="page" />'."\n". 2940: '<input type="hidden" name="title" />'."\n"; 2941: 2942: $result.=' <b>View Problems Text: </b><input type="radio" name="vProb" value="no" checked="on" /> no '."\n". 2943: '<input type="radio" name="vProb" value="yes" /> yes '."<br>\n"; 2944: 2945: $result.=' <b>Submission Details: </b>'. 2946: '<input type="radio" name="lastSub" value="none" /> none'."\n". 2947: '<input type="radio" name="lastSub" value="datesub" checked /> by dates and submissions'."\n". 2948: '<input type="radio" name="lastSub" value="all" /> all details'."\n"; 2949: 2950: $result.='<input type="hidden" name="section" value="'.$getsec.'" />'."\n". 2951: '<input type="hidden" name="Status" value="'.$ENV{'form.Status'}.'" />'."\n". 2952: '<input type="hidden" name="command" value="displayPage" />'."\n". 2953: '<input type="hidden" name="url" value="'.$url.'" />'."\n". 2954: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 2955: '<input type="hidden" name="saveState" value="'.$ENV{'form.saveState'}.'" />'."<br />\n"; 2956: 2957: $result.=' <input type="button" '. 2958: 'onClick="javascript:checkPickOne(this.form);"value="Next->" /><br />'."\n"; 2959: 2960: $request->print($result); 2961: 2962: my $studentTable.=' <b>Select a student you wish to grade and then click on the Next button.</b><br>'. 2963: '<table border="0"><tr><td bgcolor="#777777">'. 2964: '<table border="0"><tr bgcolor="#e6ffff">'. 2965: '<td align="right"> <b>No.</b></td>'. 2966: '<td>'.&nameUserString('header').'</td>'. 2967: '<td align="right"> <b>No.</b></td>'. 2968: '<td>'.&nameUserString('header').'</td></tr>'; 2969: 2970: my (undef,undef,$fullname) = &getclasslist($getsec,'1'); 2971: my $ptr = 1; 2972: foreach my $student (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) { 2973: my ($uname,$udom) = split(/:/,$student); 2974: $studentTable.=($ptr%2 == 1 ? '<tr bgcolor="#ffffe6">' : '</td>'); 2975: $studentTable.='<td align="right">'.$ptr.' </td>'; 2976: $studentTable.='<td> <input type="radio" name="student" value="'.$student.'" /> ' 2977: .&nameUserString(undef,$$fullname{$student},$uname,$udom)."\n"; 2978: $studentTable.=($ptr%2 == 0 ? '</td></tr>' : ''); 2979: $ptr++; 2980: } 2981: $studentTable.='</td><td> </td><td> ' if ($ptr%2 == 0); 2982: $studentTable.='</td></tr></table></td></tr></table>'."\n"; 2983: $studentTable.='<input type="button" '. 2984: 'onClick="javascript:checkPickOne(this.form);"value="Next->" /></form>'."\n"; 2985: 2986: $studentTable.=&show_grading_menu_form($symb,$url); 2987: $request->print($studentTable); 2988: 2989: return ''; 2990: } 2991: 2992: sub getSymbMap { 2993: my ($request) = @_; 2994: my $navmap = Apache::lonnavmaps::navmap->new(); 2995: 2996: my %symbx = (); 2997: my @titles = (); 2998: my $minder = 0; 2999: 3000: # Gather every sequence that has problems. 3001: my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); }, 1); 3002: for my $sequence ($navmap->getById('0.0'), @sequences) { 3003: if ($navmap->hasResource($sequence, sub { shift->is_problem(); }, 0) ) { 3004: my $title = $minder.'.'.$sequence->compTitle(); 3005: push @titles, $title; # minder in case two titles are identical 3006: $symbx{$title} = $sequence->symb(); 3007: $minder++; 3008: } 3009: } 3010: 3011: $navmap->untieHashes(); 3012: return \@titles,\%symbx; 3013: } 3014: 3015: # 3016: #--- Displays a page/sequence w/wo problems, w/wo submissions 3017: sub displayPage { 3018: my ($request) = shift; 3019: 3020: my ($symb,$url) = &get_symb_and_url($request); 3021: my $cdom = $ENV{"course.$ENV{'request.course.id'}.domain"}; 3022: my $cnum = $ENV{"course.$ENV{'request.course.id'}.num"}; 3023: my $getsec = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'}; 3024: my $pageTitle = $ENV{'form.page'}; 3025: my ($classlist,undef,$fullname) = &getclasslist($getsec,'1'); 3026: my ($uname,$udom) = split(/:/,$ENV{'form.student'}); 3027: my $usec=$classlist->{$ENV{'form.student'}}[5]; 3028: if (!&canview($usec)) { 3029: $request->print('<font color="red">Unable to view requested student.('.$ENV{'form.student'}.')</font>'); 3030: $request->print(&show_grading_menu_form($symb,$url)); 3031: return; 3032: } 3033: my $result='<h3><font color="#339933"> '.$ENV{'form.title'}.'</font></h3>'; 3034: $result.='<h3> Student: '.&nameUserString(undef,$$fullname{$ENV{'form.student'}},$uname,$udom). 3035: '</h3>'."\n"; 3036: &sub_page_js($request); 3037: $request->print($result); 3038: 3039: my $navmap = Apache::lonnavmaps::navmap->new(); 3040: my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($ENV{'form.page'}); 3041: my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps 3042: 3043: my $iterator = $navmap->getIterator($map->map_start(), 3044: $map->map_finish()); 3045: 3046: my $studentTable='<form action="/adm/grades" method="post" name="gradePage">'."\n". 3047: '<input type="hidden" name="command" value="gradeByPage" />'."\n". 3048: '<input type="hidden" name="fullname" value="'.$$fullname{$ENV{'form.student'}}.'" />'."\n". 3049: '<input type="hidden" name="student" value="'.$ENV{'form.student'}.'" />'."\n". 3050: '<input type="hidden" name="page" value="'.$pageTitle.'" />'."\n". 3051: '<input type="hidden" name="title" value="'.$ENV{'form.title'}.'" />'."\n". 3052: '<input type="hidden" name="url" value="'.$url.'" />'."\n". 3053: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 3054: '<input type="hidden" name="overRideScore" value="no" />'."\n". 3055: '<input type="hidden" name="saveState" value="'.$ENV{'form.saveState'}.'" />'."\n"; 3056: 3057: my $checkIcon = '<img src="'.$request->dir_config('lonIconsURL'). 3058: '/check.gif" height="16" border="0" />'; 3059: 3060: $studentTable.=' <b>Note:</b> Problems graded correct by the computer are marked with a '.$checkIcon. 3061: ' symbol.'."\n". 3062: '<table border="0"><tr><td bgcolor="#777777">'. 3063: '<table border="0"><tr bgcolor="#e6ffff">'. 3064: '<td align="center"><b> Prob. </b></td>'. 3065: '<td><b> '.($ENV{'form.vProb'} eq 'no' ? 'Title' : 'Problem Text').'/Grade</b></td></tr>'; 3066: 3067: my ($depth,$question) = (1,1); 3068: $iterator->next(); # skip the first BEGIN_MAP 3069: my $curRes = $iterator->next(); # for "current resource" 3070: while ($depth > 0) { 3071: if($curRes == $iterator->BEGIN_MAP) { $depth++; } 3072: if($curRes == $iterator->END_MAP) { $depth--; } 3073: 3074: if (ref($curRes) && $curRes->is_problem()) { 3075: my $parts = $curRes->parts(); 3076: my $title = $curRes->compTitle(); 3077: my $symbx = $curRes->symb(); 3078: $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$question. 3079: (scalar(@{$parts}) == 1 ? '' : '<br>('.scalar(@{$parts}).' parts)').'</td>'; 3080: $studentTable.='<td valign="top">'; 3081: if ($ENV{'form.vProb'} eq 'yes' ) { 3082: $studentTable.=&show_problem($request,$symbx,$uname,$udom,1, 3083: undef,'both'); 3084: } else { 3085: my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$ENV{'request.course.id'}); 3086: $companswer =~ s|<form(.*?)>||g; 3087: $companswer =~ s|</form>||g; 3088: # while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a> 3089: # $companswer =~ s/$1/ /ms; 3090: # $request->print('match='.$1."<br>\n"); 3091: # } 3092: # $companswer =~ s|<table border=\"1\">|<table border=\"0\">|g; 3093: $studentTable.=' <b>'.$title.'</b> <br> <b>Correct answer:</b><br>'.$companswer; 3094: } 3095: 3096: my %record = &Apache::lonnet::restore($symbx,$ENV{'request.course.id'},$udom,$uname); 3097: 3098: if ($ENV{'form.lastSub'} eq 'datesub') { 3099: if ($record{'version'} eq '') { 3100: $studentTable.='<br /> <font color="red">No recorded submission for this problem</font><br />'; 3101: } else { 3102: my %responseType = (); 3103: foreach my $partid (@{$parts}) { 3104: my @responseIds =$curRes->responseIds($partid); 3105: my @responseType =$curRes->responseType($partid); 3106: my %responseIds; 3107: for (my $i=0;$i<=$#responseIds;$i++) { 3108: $responseIds{$responseIds[$i]}=$responseType[$i]; 3109: } 3110: $responseType{$partid} = \%responseIds; 3111: } 3112: $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom); 3113: 3114: } 3115: } elsif ($ENV{'form.lastSub'} eq 'all') { 3116: my $last = ($ENV{'form.lastSub'} eq 'last' ? 'last' : ''); 3117: $studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom, 3118: $ENV{'request.course.id'}, 3119: '','.submission'); 3120: 3121: } 3122: if (&canmodify($usec)) { 3123: foreach my $partid (@{$parts}) { 3124: $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record); 3125: $studentTable.='<input type="hidden" name="q_'.$question.'" value="'.$partid.'" />'."\n"; 3126: $question++; 3127: } 3128: } 3129: $studentTable.='</td></tr>'; 3130: 3131: } 3132: $curRes = $iterator->next(); 3133: } 3134: 3135: $navmap->untieHashes(); 3136: 3137: $studentTable.='</td></tr></table></td></tr></table>'."\n". 3138: '<input type="button" value="Save" '. 3139: 'onClick="javascript:checkSubmitPage(this.form,'.$question.');" TARGET=_self />'. 3140: '</form>'."\n"; 3141: $studentTable.=&show_grading_menu_form($symb,$url); 3142: $request->print($studentTable); 3143: 3144: return ''; 3145: } 3146: 3147: sub displaySubByDates { 3148: my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_; 3149: my $studentTable='<table border="0" width="100%"><tr><td bgcolor="#777777">'. 3150: '<table border="0" width="100%"><tr bgcolor="#e6ffff">'. 3151: '<td><b>Date/Time</b></td>'. 3152: '<td><b>Submission</b></td>'. 3153: '<td><b>Status </b></td></tr>'; 3154: my ($version); 3155: my %mark; 3156: my %orders; 3157: $mark{'correct_by_student'} = $checkIcon; 3158: if (!exists($$record{'1:timestamp'})) { 3159: return '<br /> <font color="red">Nothing submitted - no attempts</font><br />'; 3160: } 3161: for ($version=1;$version<=$$record{'version'};$version++) { 3162: my $timestamp = scalar(localtime($$record{$version.':timestamp'})); 3163: $studentTable.='<tr bgcolor="#ffffff" valign="top"><td>'.$timestamp.'</td>'; 3164: my @versionKeys = split(/\:/,$$record{$version.':keys'}); 3165: my @displaySub = (); 3166: foreach my $partid (@{$parts}) { 3167: my @matchKey = sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys); 3168: # next if ($$record{"$version:resource.$partid.solved"} eq ''); 3169: foreach my $matchKey (@matchKey) { 3170: if (exists $$record{$version.':'.$matchKey}) { 3171: my ($responseId)=($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/); 3172: $displaySub[0].='<b>Part '.$partid.' '; 3173: $displaySub[0].='<font color="#999999">(ID '. 3174: $responseId.')</font> '; 3175: if ($$record{"$version:resource.$partid.tries"} eq '') { 3176: $displaySub[0].='Trial not counted'; 3177: } else { 3178: $displaySub[0].='Trial '. 3179: $$record{"$version:resource.$partid.tries"}; 3180: } 3181: my $responseType=$responseType->{$partid}->{$responseId}; 3182: if (!exists($orders{$partid})) { $orders{$partid}={}; } 3183: if (!exists($orders{$partid}->{$responseId})) { 3184: $orders{$partid}->{$responseId}= 3185: &get_order($partid,$responseId,$symb,$uname,$udom); 3186: } 3187: $displaySub[0].='</b> '. 3188: &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:").'<br />'; 3189: } 3190: } 3191: if (exists $$record{"$version:resource.$partid.award"}) { 3192: $displaySub[1].='<b>Part '.$partid.'</b> '. 3193: lc($$record{"$version:resource.$partid.award"}).' '. 3194: $mark{$$record{"$version:resource.$partid.solved"}}. 3195: '<br />'; 3196: } 3197: if (exists $$record{"$version:resource.$partid.regrader"}) { 3198: $displaySub[2].=$$record{"$version:resource.$partid.regrader"}. 3199: ' (<b>Part:</b> '.$partid.')'; 3200: } 3201: } 3202: # needed because old essay regrader has not parts info 3203: if (exists $$record{"$version:resource.regrader"}) { 3204: $displaySub[2].=$$record{"$version:resource.regrader"}; 3205: } 3206: $studentTable.='<td>'.$displaySub[0].' </td><td>'.$displaySub[1]; 3207: if ($displaySub[2]) { 3208: $studentTable.='Manually graded by '.$displaySub[2]; 3209: } 3210: $studentTable.=' </td></tr>'; 3211: 3212: } 3213: $studentTable.='</table></td></tr></table>'; 3214: return $studentTable; 3215: } 3216: 3217: sub updateGradeByPage { 3218: my ($request) = shift; 3219: 3220: my $cdom = $ENV{"course.$ENV{'request.course.id'}.domain"}; 3221: my $cnum = $ENV{"course.$ENV{'request.course.id'}.num"}; 3222: my $getsec = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'}; 3223: my $pageTitle = $ENV{'form.page'}; 3224: my ($classlist,undef,$fullname) = &getclasslist($getsec,'1'); 3225: my ($uname,$udom) = split(/:/,$ENV{'form.student'}); 3226: my $usec=$classlist->{$ENV{'form.student'}}[5]; 3227: if (!&canmodify($usec)) { 3228: $request->print('<font color="red">Unable to modify requested student.('.$ENV{'form.student'}.'</font>'); 3229: $request->print(&show_grading_menu_form($ENV{'form.symb'},$ENV{'form.url'})); 3230: return; 3231: } 3232: my $result='<h3><font color="#339933"> '.$ENV{'form.title'}.'</font></h3>'; 3233: $result.='<h3> Student: '.&nameUserString(undef,$ENV{'form.fullname'},$uname,$udom). 3234: '</h3>'."\n"; 3235: 3236: $request->print($result); 3237: 3238: my $navmap = Apache::lonnavmaps::navmap->new(); 3239: my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $ENV{'form.page'}); 3240: my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps 3241: 3242: my $iterator = $navmap->getIterator($map->map_start(), 3243: $map->map_finish()); 3244: 3245: my $studentTable='<table border="0"><tr><td bgcolor="#777777">'. 3246: '<table border="0"><tr bgcolor="#e6ffff">'. 3247: '<td align="center"><b> Prob. </b></td>'. 3248: '<td><b> Title </b></td>'. 3249: '<td><b> Previous Score </b></td>'. 3250: '<td><b> New Score </b></td></tr>'; 3251: 3252: $iterator->next(); # skip the first BEGIN_MAP 3253: my $curRes = $iterator->next(); # for "current resource" 3254: my ($depth,$question,$changeflag)= (1,1,0); 3255: while ($depth > 0) { 3256: if($curRes == $iterator->BEGIN_MAP) { $depth++; } 3257: if($curRes == $iterator->END_MAP) { $depth--; } 3258: 3259: if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout) { 3260: my $parts = $curRes->parts(); 3261: my $title = $curRes->compTitle(); 3262: my $symbx = $curRes->symb(); 3263: $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$question. 3264: (scalar(@{$parts}) == 1 ? '' : '<br>('.scalar(@{$parts}).' parts)').'</td>'; 3265: $studentTable.='<td valign="top"> <b>'.$title.'</b> </td>'; 3266: 3267: my %newrecord=(); 3268: my @displayPts=(); 3269: foreach my $partid (@{$parts}) { 3270: my $newpts = $ENV{'form.GD_BOX'.$question.'_'.$partid}; 3271: my $oldpts = $ENV{'form.oldpts'.$question.'_'.$partid}; 3272: 3273: my $wgt = $ENV{'form.WGT'.$question.'_'.$partid} != 0 ? 3274: $ENV{'form.WGT'.$question.'_'.$partid} : 1; 3275: my $partial = $newpts/$wgt; 3276: my $score; 3277: if ($partial > 0) { 3278: $score = 'correct_by_override'; 3279: } elsif ($newpts ne '') { #empty is taken as 0 3280: $score = 'incorrect_by_override'; 3281: } 3282: my $dropMenu = $ENV{'form.GD_SEL'.$question.'_'.$partid}; 3283: if ($dropMenu eq 'excused') { 3284: $partial = ''; 3285: $score = 'excused'; 3286: } elsif ($dropMenu eq 'reset status' 3287: && $ENV{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists 3288: $newrecord{'resource.'.$partid.'.tries'} = 0; 3289: $newrecord{'resource.'.$partid.'.solved'} = ''; 3290: $newrecord{'resource.'.$partid.'.award'} = ''; 3291: $newrecord{'resource.'.$partid.'.awarded'} = 0; 3292: $newrecord{'resource.'.$partid.'.regrader'} = "$ENV{'user.name'}:$ENV{'user.domain'}"; 3293: $changeflag++; 3294: $newpts = ''; 3295: } 3296: 3297: my $oldstatus = $ENV{'form.solved'.$question.'_'.$partid}; 3298: $displayPts[0].=' <b>Part</b> '.$partid.' = '. 3299: (($oldstatus eq 'excused') ? 'excused' : $oldpts). 3300: ' <br>'; 3301: $displayPts[1].=' <b>Part</b> '.$partid.' = '. 3302: (($score eq 'excused') ? 'excused' : $newpts). 3303: ' <br>'; 3304: 3305: $question++; 3306: next if ($dropMenu eq 'reset status' || ($newpts == $oldpts && $score ne 'excused')); 3307: 3308: $newrecord{'resource.'.$partid.'.awarded'} = $partial if $partial ne ''; 3309: $newrecord{'resource.'.$partid.'.solved'} = $score if $score ne ''; 3310: $newrecord{'resource.'.$partid.'.regrader'} = "$ENV{'user.name'}:$ENV{'user.domain'}" 3311: if (scalar(keys(%newrecord)) > 0); 3312: 3313: $changeflag++; 3314: } 3315: if (scalar(keys(%newrecord)) > 0) { 3316: &Apache::lonnet::cstore(\%newrecord,$symbx,$ENV{'request.course.id'}, 3317: $udom,$uname); 3318: } 3319: 3320: $studentTable.='<td valign="top">'.$displayPts[0].'</td>'. 3321: '<td valign="top">'.$displayPts[1].'</td>'. 3322: '</tr>'; 3323: 3324: } 3325: $curRes = $iterator->next(); 3326: } 3327: 3328: $navmap->untieHashes(); 3329: 3330: $studentTable.='</td></tr></table></td></tr></table>'; 3331: $studentTable.=&show_grading_menu_form($ENV{'form.symb'},$ENV{'form.url'}); 3332: my $grademsg=($changeflag == 0 ? 'No score was changed or updated.' : 3333: 'The scores were changed for '. 3334: $changeflag.' problem'.($changeflag == 1 ? '.' : 's.')); 3335: $request->print($grademsg.$studentTable); 3336: 3337: return ''; 3338: } 3339: 3340: #-------- end of section for handling grading by page/sequence --------- 3341: # 3342: #------------------------------------------------------------------- 3343: 3344: #--------------------Scantron Grading----------------------------------- 3345: # 3346: #------ start of section for handling grading by page/sequence --------- 3347: 3348: sub defaultFormData { 3349: my ($symb,$url)=@_; 3350: return ' 3351: <input type="hidden" name="symb" value="'.$symb.'" />'."\n". 3352: '<input type="hidden" name="url" value="'.$url.'" />'."\n". 3353: '<input type="hidden" name="saveState" value="'.$ENV{'form.saveState'}.'" />'."\n". 3354: '<input type="hidden" name="probTitle" value="'.$ENV{'form.probTitle'}.'" />'."\n"; 3355: } 3356: 3357: sub getSequenceDropDown { 3358: my ($request,$symb)=@_; 3359: my $result='<select name="selectpage">'."\n"; 3360: my ($titles,$symbx) = &getSymbMap($request); 3361: my ($curpage)=&Apache::lonnet::decode_symb($symb); 3362: my $ctr=0; 3363: foreach (@$titles) { 3364: my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/); 3365: $result.='<option value="'.$$symbx{$_}.'" '. 3366: ($$symbx{$_} =~ /$curpage$/ ? 'selected="on"' : ''). 3367: '>'.$showtitle.'</option>'."\n"; 3368: $ctr++; 3369: } 3370: $result.= '</select>'; 3371: return $result; 3372: } 3373: 3374: sub scantron_uploads { 3375: if (!-e $Apache::lonnet::perlvar{'lonScansDir'}) { return ''}; 3376: my $result= '<select name="scantron_selectfile">'; 3377: my $cdom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; 3378: my $cname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'}; 3379: my @files=&Apache::lonnet::dirlist('userfiles',$cdom,$cname, 3380: &Apache::locommon::propath($cdom,$cname)); 3381: foreach my $filename (@files) { 3382: ($filename)=split(/&/,$filename); 3383: if ($filename!~/^scantron_orig_/) { next ; } 3384: $filename=~s/^scantron_orig_//; 3385: $result.="<option>$filename</option>\n"; 3386: } 3387: $result.="</select>"; 3388: return $result; 3389: } 3390: 3391: sub scantron_scantab { 3392: my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab'); 3393: my $result='<select name="scantron_format">'."\n"; 3394: foreach my $line (<$fh>) { 3395: my ($name,$descrip)=split(/:/,$line); 3396: if ($name =~ /^\#/) { next; } 3397: $result.='<option value="'.$name.'">'.$descrip.'</option>'."\n"; 3398: } 3399: $result.='</select>'."\n"; 3400: 3401: return $result; 3402: } 3403: 3404: sub scantron_selectphase { 3405: my ($r) = @_; 3406: my ($symb,$url)=&get_symb_and_url($r); 3407: if (!$symb) {return '';} 3408: my $sequence_selector=&getSequenceDropDown($r,$symb); 3409: my $default_form_data=&defaultFormData($symb,$url); 3410: my $grading_menu_button=&show_grading_menu_form($symb,$url); 3411: my $file_selector=&scantron_uploads(); 3412: my $format_selector=&scantron_scantab(); 3413: my $result; 3414: #FIXME allow instructor to be able to download the scantron file 3415: # and to upload it, 3416: $result.= <<SCANTRONFORM; 3417: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantro_process"> 3418: <input type="hidden" name="command" value="scantron_validate" /> 3419: $default_form_data 3420: <table width="100%" border="0"> 3421: <tr> 3422: <td bgcolor="#777777"> 3423: <table width="100%" border="0"> 3424: <tr bgcolor="#e6ffff"> 3425: <td> 3426: <b>Specify file location and which Folder/Sequence to grade</b> 3427: </td> 3428: </tr> 3429: <tr bgcolor="#ffffe6"> 3430: <td> 3431: Sequence to grade: $sequence_selector 3432: </td> 3433: </tr> 3434: <tr bgcolor="#ffffe6"> 3435: <td> 3436: Filename of scoring office file: $file_selector 3437: </td> 3438: </tr> 3439: <tr bgcolor="#ffffe6"> 3440: <td> 3441: Format of data file: $format_selector 3442: </td> 3443: </tr> 3444: <tr bgcolor="#ffffe6"> 3445: <td> 3446: <!-- FIXME this is lazy, a single parse of the set should let me know what this is --> 3447: Last line to expect an answer on: 3448: <input type="text" name="scantron_maxbubble" /> 3449: </td> 3450: </tr> 3451: </table> 3452: </td> 3453: </tr> 3454: </table> 3455: <input type="submit" value="Validate Scantron Records" /> 3456: </form> 3457: $grading_menu_button 3458: SCANTRONFORM 3459: 3460: return $result; 3461: } 3462: 3463: sub get_scantron_config { 3464: my ($which) = @_; 3465: my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab'); 3466: my %config; 3467: #FIXME probably should move to XML it has already gotten a bit much now 3468: foreach my $line (<$fh>) { 3469: my ($name,$descrip)=split(/:/,$line); 3470: if ($name ne $which ) { next; } 3471: chomp($line); 3472: my @config=split(/:/,$line); 3473: $config{'name'}=$config[0]; 3474: $config{'description'}=$config[1]; 3475: $config{'CODElocation'}=$config[2]; 3476: $config{'CODEstart'}=$config[3]; 3477: $config{'CODElength'}=$config[4]; 3478: $config{'IDstart'}=$config[5]; 3479: $config{'IDlength'}=$config[6]; 3480: $config{'Qstart'}=$config[7]; 3481: $config{'Qlength'}=$config[8]; 3482: $config{'Qoff'}=$config[9]; 3483: $config{'Qon'}=$config[10]; 3484: $config{'PaperID'}=$config[11]; 3485: $config{'PaperIDlength'}=$config[12]; 3486: $config{'FirstName'}=$config[13]; 3487: $config{'FirstNamelength'}=$config[14]; 3488: $config{'LastName'}=$config[15]; 3489: $config{'LastNamelength'}=$config[16]; 3490: last; 3491: } 3492: return %config; 3493: } 3494: 3495: sub username_to_idmap { 3496: my ($classlist)= @_; 3497: my %idmap; 3498: foreach my $student (keys(%$classlist)) { 3499: $idmap{$classlist->{$student}->[&Apache::loncoursedata::CL_ID]}= 3500: $student; 3501: } 3502: return %idmap; 3503: } 3504: 3505: sub scantron_fixup_scanline { 3506: my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_; 3507: if ($field eq 'ID') { 3508: if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) { 3509: return ($line,1,'New value to large'); 3510: } 3511: if (length($args->{'newid'}) < $$scantron_config{'IDlength'}) { 3512: $args->{'newid'}=sprintf('%-'.$$scantron_config{'IDlength'}.'s', 3513: $args->{'newid'}); 3514: } 3515: substr($line,$$scantron_config{'IDstart'}-1, 3516: $$scantron_config{'IDlength'})=$args->{'newid'}; 3517: if ($args->{'newid'}=~/^\s*$/) { 3518: &scan_data($scan_data,"$whichline.user", 3519: $args->{'username'}.':'.$args->{'domain'}); 3520: } 3521: } elsif ($field eq 'answer') { 3522: my $length=$scantron_config->{'Qlength'}; 3523: my $off=$scantron_config->{'Qoff'}; 3524: my $on=$scantron_config->{'Qon'}; 3525: my $answer=${off}x$length; 3526: if ($args->{'response'} eq 'none') { 3527: &scan_data($scan_data, 3528: "$whichline.no_bubble.".$args->{'question'},'1'); 3529: } else { 3530: substr($answer,$args->{'response'},1)=$on; 3531: &scan_data($scan_data, 3532: "$whichline.no_bubble.".$args->{'question'},undef,'1'); 3533: } 3534: my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'}; 3535: substr($line,$where-1,$length)=$answer; 3536: } 3537: return $line; 3538: } 3539: 3540: sub scan_data { 3541: my ($scan_data,$key,$value,$delete)=@_; 3542: my $filename=$ENV{'form.scantron_selectfile'}; 3543: if (defined($value)) { 3544: $scan_data->{$filename.'_'.$key} = $value; 3545: } 3546: if ($delete) { delete($scan_data->{$filename.'_'.$key}); } 3547: return $scan_data->{$filename.'_'.$key}; 3548: } 3549: 3550: sub scantron_parse_scanline { 3551: my ($line,$whichline,$scantron_config,$scan_data)=@_; 3552: my %record; 3553: my $questions=substr($line,$$scantron_config{'Qstart'}-1); 3554: my $data=substr($line,0,$$scantron_config{'Qstart'}-1); 3555: if ($$scantron_config{'CODElocation'} ne 0) { 3556: if ($$scantron_config{'CODElocation'} < 0) { 3557: $record{'scantron.CODE'}=substr($data,$$scantron_config{'CODEstart'}-1, 3558: $$scantron_config{'CODElength'}); 3559: } else { 3560: #FIXME interpret first N questions 3561: } 3562: } 3563: $record{'scantron.ID'}=substr($data,$$scantron_config{'IDstart'}-1, 3564: $$scantron_config{'IDlength'}); 3565: $record{'scantron.PaperID'}= 3566: substr($data,$$scantron_config{'PaperID'}-1, 3567: $$scantron_config{'PaperIDlength'}); 3568: $record{'scantron.FirstName'}= 3569: substr($data,$$scantron_config{'FirstName'}-1, 3570: $$scantron_config{'FirstNamelength'}); 3571: $record{'scantron.LastName'}= 3572: substr($data,$$scantron_config{'LastName'}-1, 3573: $$scantron_config{'LastNamelength'}); 3574: my @alphabet=('A'..'Z'); 3575: my $questnum=0; 3576: while ($questions) { 3577: $questnum++; 3578: my $currentquest=substr($questions,0,$$scantron_config{'Qlength'}); 3579: substr($questions,0,$$scantron_config{'Qlength'})=''; 3580: if (length($currentquest) < $$scantron_config{'Qlength'}) { next; } 3581: my @array=split($$scantron_config{'Qon'},$currentquest,-1); 3582: if (length($array[0]) eq $$scantron_config{'Qlength'}) { 3583: $record{"scantron.$questnum.answer"}=''; 3584: if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) { 3585: push(@{$record{"scantron.missingerror"}},$questnum); 3586: } 3587: } else { 3588: $record{"scantron.$questnum.answer"}=$alphabet[length($array[0])]; 3589: } 3590: if (scalar(@array) gt 2) { 3591: push(@{$record{'scantron.doubleerror'}},$questnum); 3592: my @ans=@array; 3593: my $i=length($ans[0]);shift(@ans); 3594: while ($#ans) { 3595: $i+=length($ans[0])+1; 3596: $record{"scantron.$questnum.answer"}.=$alphabet[$i]; 3597: shift(@ans); 3598: } 3599: } 3600: } 3601: $record{'scantron.maxquest'}=$questnum; 3602: return \%record; 3603: } 3604: 3605: sub scantron_add_delay { 3606: my ($delayqueue,$scanline,$errormessage,$errorcode)=@_; 3607: push(@$delayqueue, 3608: {'line' => $scanline, 'emsg' => $errormessage, 3609: 'ecode' => $errorcode } 3610: ); 3611: } 3612: 3613: sub scantron_find_student { 3614: my ($scantron_record,$scan_data,$idmap,$line)=@_; 3615: my $scanID=$$scantron_record{'scantron.ID'}; 3616: if ($scanID =~ /^\s*$/) { 3617: return &scan_data($scan_data,"$line.user"); 3618: } 3619: foreach my $id (keys(%$idmap)) { 3620: if (lc($id) eq lc($scanID)) { 3621: return $$idmap{$id}; 3622: } 3623: } 3624: return undef; 3625: } 3626: 3627: sub scantron_filter { 3628: my ($curres)=@_; 3629: if (ref($curres) && $curres->is_problem() && !$curres->randomout) { 3630: return 1; 3631: } 3632: return 0; 3633: } 3634: 3635: #FIXME I think I am doing this in the wrong order, I think it would be 3636: #better to make a several passes analyzing all of the lines in the 3637: #file for common errors wrong/invalid PID/username duplicated 3638: #PID/username, missing bubbles, double bubbles, missing/invalid CODE 3639: #and then get the instructor to fix all of these errors, then grade 3640: #the corrected one, I'll still need to catch error conditions, but 3641: #maybe most will taken care even before we start 3642: 3643: sub scantron_validate_file { 3644: my ($r) = @_; 3645: } 3646: 3647: sub scantron_process_corrections { 3648: my ($r) = @_; 3649: my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'}); 3650: my ($scanlines,$scan_data)=&scantron_getfile(); 3651: my $classlist=&Apache::loncoursedata::get_classlist(); 3652: my $which=$ENV{'form.scantron_line'}; 3653: my $line=&scantron_get_line($scanlines,$which); 3654: my ($skip,$err,$errmsg); 3655: if ($ENV{'form.scantron_skip_record'}) { 3656: $skip=1; 3657: } elsif ($ENV{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) { 3658: my $newstudent=$ENV{'form.scantron_username'}.':'. 3659: $ENV{'form.scantron_domain'}; 3660: my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID]; 3661: ($line,$err,$errmsg)= 3662: &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which, 3663: 'ID',{'newid'=>$newid, 3664: 'username'=>$ENV{'form.scantron_username'}, 3665: 'domain'=>$ENV{'form.scantron_domain'}}); 3666: } elsif ($ENV{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) { 3667: foreach my $question (split(',',$ENV{'form.scantron_questions'})) { 3668: ($line,$err,$errmsg)= 3669: &scantron_fixup_scanline(\%scantron_config,$scan_data,$line, 3670: $which,'answer', 3671: { 'question'=>$question, 3672: 'response'=>$ENV{"form.scantron_correct_Q_$question"}}); 3673: if ($err) { last; } 3674: } 3675: } 3676: if ($err) { 3677: $r->print("Unable to accept last correction, an error occurred :$errmsg:"); 3678: } else { 3679: &scantron_put_line($scanlines,$which,$line,$skip); 3680: &scantron_putfile($scanlines,$scan_data); 3681: } 3682: } 3683: 3684: 3685: sub scantron_validate_file { 3686: my ($r) = @_; 3687: my ($symb,$url)=&get_symb_and_url($r); 3688: if (!$symb) {return '';} 3689: my $default_form_data=&defaultFormData($symb,$url); 3690: 3691: if ($ENV{'form.scantron_corrections'}) { 3692: &scantron_process_corrections($r); 3693: } 3694: #get the student pick code ready 3695: $r->print(&Apache::loncommon::studentbrowser_javascript()); 3696: my $result= <<SCANTRONFORM; 3697: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload"> 3698: <input type="hidden" name="selectpage" value="$ENV{'form.selectpage'}" /> 3699: <input type="hidden" name="scantron_format" value="$ENV{'form.scantron_format'}" /> 3700: <input type="hidden" name="scantron_selectfile" value="$ENV{'form.scantron_selectfile'}" /> 3701: <input type="hidden" name="scantron_maxbubble" value="$ENV{'form.scantron_maxbubble'}" /> 3702: $default_form_data 3703: SCANTRONFORM 3704: $r->print($result); 3705: 3706: my @validate_phases=( 'ID', 3707: 'CODE', 3708: 'doublebubble', 3709: 'missingbubbles'); 3710: if (!$ENV{'form.validatepass'}) { 3711: $ENV{'form.valiadatepass'} = 0; 3712: } 3713: my $currentphase=$ENV{'form.valiadatepass'}; 3714: 3715: if ($ENV{'form.scantron_selectfile'}=~m-^/-) { 3716: #first pass copy file to classdir 3717: 3718: } 3719: my $stop=0; 3720: while (!$stop && $currentphase < scalar(@validate_phases)) { 3721: $r->print("<p> Validating ".$validate_phases[$currentphase]."</p>"); 3722: $r->rflush(); 3723: my $which="scantron_validate_".$validate_phases[$currentphase]; 3724: { 3725: no strict 'refs'; 3726: ($stop,$currentphase)=&$which($r,$currentphase); 3727: } 3728: } 3729: if (!$stop) { 3730: $r->print("Validation process complete.<br />"); 3731: $r->print('<input type="submit" name="submit" value="Start Grading" />'); 3732: $r->print('<input type="hidden" name="command" value="scantron_process" />'); 3733: } else { 3734: $r->print('<input type="hidden" name="command" value="scantron_validate" />'); 3735: $r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />"); 3736: } 3737: if ($stop) { 3738: $r->print('<input type="submit" name="submit" value="Continue ->" />'); 3739: $r->print(' using corrected info <br />'); 3740: $r->print("<input type='submit' value='Skip' name='scantron_skip_record' />"); 3741: $r->print(" this scanline saving it for later."); 3742: } 3743: $r->print(" </form><br />".&show_grading_menu_form($symb,$url). 3744: "</body></html>"); 3745: return ''; 3746: } 3747: 3748: sub scantron_getfile { 3749: #FIXME really would prefer a scantron directory but tokenwrapper 3750: # doesn't allow access to subdirs of userfiles 3751: my $cname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'}; 3752: my $cdom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; 3753: my $lines; 3754: $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'. 3755: 'scantron_orig_'.$ENV{'form.scantron_selectfile'}); 3756: if ($lines eq '-1') { 3757: #FIXME need to actually replicate file to course space 3758: #FIXME when replicating strip CRLF to LF or CR to LF 3759: } 3760: my %scanlines; 3761: $scanlines{'orig'}=[(split("\n",$lines,-1))]; 3762: my $temp=$scanlines{'orig'}; 3763: $scanlines{'count'}=$#$temp; 3764: 3765: $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'. 3766: 'scantron_corrected_'.$ENV{'form.scantron_selectfile'}); 3767: if ($lines eq '-1') { 3768: $scanlines{'corrected'}=[]; 3769: } else { 3770: $scanlines{'corrected'}=[(split("\n",$lines,-1))]; 3771: } 3772: $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'. 3773: 'scantron_skipped_'.$ENV{'form.scantron_selectfile'}); 3774: if ($lines eq '-1') { 3775: $scanlines{'skipped'}=[]; 3776: } else { 3777: $scanlines{'skipped'}=[(split("\n",$lines,-1))]; 3778: } 3779: my @tmp=&Apache::lonnet::dump('scantrondata',$cdom,$cname); 3780: if ($tmp[0] =~ /^(error:|no_such_host)/) { @tmp=(); } 3781: my %scan_data = @tmp; 3782: return (\%scanlines,\%scan_data); 3783: } 3784: 3785: sub lonnet_putfile { 3786: my ($contents,$filename)=@_; 3787: my $docuname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'}; 3788: my $docudom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; 3789: my $docuhome=$ENV{'course.'.$ENV{'request.course.id'}.'.home'}; 3790: $ENV{'form.sillywaytopassafilearound'}=$contents; 3791: &Apache::lonnet::finishuserfileupload($docuname,$docudom,$docuhome,'sillywaytopassafilearound',$filename); 3792: 3793: } 3794: 3795: sub scantron_putfile { 3796: my ($scanlines,$scan_data) = @_; 3797: #FIXME really would prefer a scantron directory but tokenwrapper 3798: # doesn't allow access to subdirs of userfiles 3799: my $cname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'}; 3800: my $cdom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; 3801: my $prefix='scantron_'; 3802: # no need to update orig, shouldn't change 3803: # &lonnet_putfile(join("\n",@{$scanlines->{'orig'}}),$prefix.'orig_'. 3804: # $ENV{'form.scantron_selectfile'}); 3805: &lonnet_putfile(join("\n",@{$scanlines->{'corrected'}}), 3806: $prefix.'corrected_'. 3807: $ENV{'form.scantron_selectfile'}); 3808: &lonnet_putfile(join("\n",@{$scanlines->{'skipped'}}), 3809: $prefix.'skipped_'. 3810: $ENV{'form.scantron_selectfile'}); 3811: &Apache::lonnet::put('scantrondata',$scan_data,$cdom,$cname); 3812: } 3813: 3814: sub scantron_get_line { 3815: my ($scanlines,$i)=@_; 3816: if ($scanlines->{'skipped'}[$i]) {return undef;} 3817: if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$i];} 3818: return $scanlines->{'orig'}[$i]; 3819: } 3820: 3821: sub scantron_put_line { 3822: my ($scanlines,$i,$newline,$skip)=@_; 3823: if ($skip) { 3824: $scanlines->{'skipped'}[$i]=$newline; 3825: return; 3826: } 3827: $scanlines->{'corrected'}[$i]=$newline; 3828: } 3829: 3830: sub scantron_validate_ID { 3831: my ($r,$currentphase) = @_; 3832: 3833: #get student info 3834: my $classlist=&Apache::loncoursedata::get_classlist(); 3835: my %idmap=&username_to_idmap($classlist); 3836: 3837: #get scantron line setup 3838: my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'}); 3839: my ($scanlines,$scan_data)=&scantron_getfile(); 3840: 3841: my %found=('ids'=>{},'usernames'=>{}); 3842: for (my $i=0;$i<=$scanlines->{'count'};$i++) { 3843: my $line=&scantron_get_line($scanlines,$i); 3844: if ($line=~/^[\s\cz]*$/) { next; } 3845: my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, 3846: $scan_data); 3847: my $id=$$scan_record{'scantron.ID'}; 3848: my $found; 3849: foreach my $checkid (keys(%idmap)) { 3850: if (lc($checkid) eq lc($id)) { $found=$checkid;last; } 3851: } 3852: if ($found) { 3853: my $username=$idmap{$found}; 3854: if ($found{'ids'}{$found}) { 3855: &scantron_get_correction($r,$i,$scan_record,\%scantron_config, 3856: $line,'duplicateID',$found); 3857: return(1); 3858: } elsif ($found{'usernames'}{$username}) { 3859: &scantron_get_correction($r,$i,$scan_record,\%scantron_config, 3860: $line,'duplicateID',$username); 3861: return(1); 3862: } 3863: #FIXME store away line we prviously saw the ID on to use above 3864: $found{'ids'}{$found}++; 3865: $found{'usernames'}{$username}++; 3866: } else { 3867: if ($id =~ /^\s*$/) { 3868: my $username=&scan_data($scan_data,"$i.user"); 3869: if (defined($username) && $found{'usernames'}{$username}) { 3870: &scantron_get_correction($r,$i,$scan_record, 3871: \%scantron_config, 3872: $line,'duplicateID',$username); 3873: return(1); 3874: } elsif (!defined($username)) { 3875: &scantron_get_correction($r,$i,$scan_record, 3876: \%scantron_config, 3877: $line,'incorrectID'); 3878: return(1); 3879: } 3880: $found{'usernames'}{$username}++; 3881: } else { 3882: &scantron_get_correction($r,$i,$scan_record,\%scantron_config, 3883: $line,'incorrectID'); 3884: return(1); 3885: } 3886: } 3887: } 3888: 3889: return (0,$currentphase+1); 3890: } 3891: 3892: sub scantron_get_correction { 3893: my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_; 3894: 3895: #FIXME in the case of a duplicated ID the previous line, probaly need 3896: #to show both the current line and the previous one and allow skipping 3897: #the previous one or the current one 3898: 3899: $r->print("<p>An error was detected ($error) "); 3900: if ( defined($$scan_record{'scantron.PaperID'}) ) { 3901: $r->print(" for PaperID <tt>". 3902: $$scan_record{'scantron.PaperID'}."</tt> \n"); 3903: } else { 3904: $r->print(" in scanline $i <pre>". 3905: $line."</pre> \n"); 3906: } 3907: $r->print('<input type="hidden" name="scantron_corrections" value="'.$error.'" />'."\n"); 3908: $r->print('<input type="hidden" name="scantron_line" value="'.$i.'" />'."\n"); 3909: if ($error =~ /ID$/) { 3910: if ($error eq 'unknownID') { 3911: $r->print("The encoded ID is not in the classlist</p>\n"); 3912: } elsif ($error eq 'duplicateID') { 3913: $r->print("The encoded ID has also been used by a previous paper $arg</p>\n"); 3914: } 3915: $r->print("<p>The ID on the form is <tt>". 3916: $$scan_record{'scantron.ID'}."</tt><br />\n"); 3917: $r->print("The name on the paper is ". 3918: $$scan_record{'scantron.LastName'}.",". 3919: $$scan_record{'scantron.FirstName'}."</p>"); 3920: $r->print("<p>How should I handle this? <br /> \n"); 3921: $r->print("\n<ul><li> "); 3922: #FIXME it would be nice if this sent back the user ID and 3923: #could do partial userID matches 3924: $r->print(&Apache::loncommon::selectstudent_link('scantronupload', 3925: 'scantron_username','scantron_domain')); 3926: $r->print(": <input type='text' name='scantron_username' value='' />"); 3927: $r->print("\n@". 3928: &Apache::loncommon::select_dom_form(undef,'scantron_domain')); 3929: 3930: $r->print('</li>'); 3931: } elsif ($error eq 'doublebubble') { 3932: #FIXME Need to print out who this is along with the paper info 3933: $r->print("<p>There have been multiple bubbles scanned for a some question(s)</p>\n"); 3934: $r->print('<input type="hidden" name="scantron_questions" value="'. 3935: join(',',@{$arg}).'" />'); 3936: $r->print("<p>Please indicate which bubble should be used for grading</p>"); 3937: foreach my $question (@{$arg}) { 3938: my $selected=$$scan_record{"scantron.$question.answer"}; 3939: &scantron_bubble_selector($r,$scan_config,$question,split('',$selected)); 3940: } 3941: } elsif ($error eq 'missingbubble') { 3942: $r->print("<p>There have been <b>no</b> bubbles scanned for some question(s)</p>\n"); 3943: $r->print("<p>Please indicate which bubble should be used for grading</p>"); 3944: $r->print("Some questions have no scanned bubbles\n"); 3945: $r->print('<input type="hidden" name="scantron_questions" value="'. 3946: join(',',@{$arg}).'" />'); 3947: foreach my $question (@{$arg}) { 3948: my $selected=$$scan_record{"scantron.$question.answer"}; 3949: &scantron_bubble_selector($r,$scan_config,$question); 3950: } 3951: } else { 3952: $r->print("\n<ul>"); 3953: } 3954: $r->print("\n</li></ul>"); 3955: 3956: } 3957: 3958: sub scantron_bubble_selector { 3959: my ($r,$scan_config,$quest,@selected)=@_; 3960: my $max=$$scan_config{'Qlength'}; 3961: my @alphabet=('A'..'Z'); 3962: $r->print("<table border='1'><tr><td rowspan='2'>$quest</td>"); 3963: for (my $i=0;$i<$max+1;$i++) { 3964: $r->print('<td align="center">'); 3965: if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) } 3966: else { $r->print(' '); } 3967: $r->print('</td>'); 3968: } 3969: $r->print('<td></td></tr><tr>'); 3970: for (my $i=0;$i<$max;$i++) { 3971: $r->print('<td><input type="radio" name="scantron_correct_Q_'.$quest. 3972: '" value="'.$i.'" />'.$alphabet[$i]."</td>"); 3973: } 3974: $r->print('<td><input type="radio" name="scantron_correct_Q_'.$quest. 3975: '" value="none" /> No bubble </td>'); 3976: $r->print('</tr></table>'); 3977: } 3978: 3979: sub scantron_validate_CODE { 3980: my ($r,$currentphase) = @_; 3981: #FIXME doesn't do anything yet 3982: return (0,$currentphase+1); 3983: } 3984: 3985: sub scantron_validate_doublebubble { 3986: my ($r,$currentphase) = @_; 3987: #get student info 3988: my $classlist=&Apache::loncoursedata::get_classlist(); 3989: my %idmap=&username_to_idmap($classlist); 3990: 3991: #get scantron line setup 3992: my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'}); 3993: my ($scanlines,$scan_data)=&scantron_getfile(); 3994: for (my $i=0;$i<=$scanlines->{'count'};$i++) { 3995: my $line=&scantron_get_line($scanlines,$i); 3996: if ($line=~/^[\s\cz]*$/) { next; } 3997: my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, 3998: $scan_data); 3999: if (!defined($$scan_record{'scantron.doubleerror'})) { next; } 4000: &scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line, 4001: 'doublebubble', 4002: $$scan_record{'scantron.doubleerror'}); 4003: return (1,$currentphase); 4004: } 4005: return (0,$currentphase+1); 4006: } 4007: 4008: sub scantron_validate_missingbubbles { 4009: my ($r,$currentphase) = @_; 4010: #get student info 4011: my $classlist=&Apache::loncoursedata::get_classlist(); 4012: my %idmap=&username_to_idmap($classlist); 4013: 4014: #get scantron line setup 4015: my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'}); 4016: my ($scanlines,$scan_data)=&scantron_getfile(); 4017: my $max_bubble=$ENV{'form.scantron_maxbubble'}; 4018: if (!$max_bubble) { $max_bubble=2**31; } 4019: for (my $i=0;$i<=$scanlines->{'count'};$i++) { 4020: my $line=&scantron_get_line($scanlines,$i); 4021: if ($line=~/^[\s\cz]*$/) { next; } 4022: my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, 4023: $scan_data); 4024: if (!defined($$scan_record{'scantron.missingerror'})) { next; } 4025: my @to_correct; 4026: foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) { 4027: if ($missing > $max_bubble) { next; } 4028: push(@to_correct,$missing); 4029: } 4030: if (@to_correct) { 4031: &scantron_get_correction($r,$i,$scan_record,\%scantron_config, 4032: $line,'missingbubble',\@to_correct); 4033: return (1,$currentphase); 4034: } 4035: 4036: } 4037: return (0,$currentphase+1); 4038: } 4039: 4040: sub scantron_process_students { 4041: my ($r) = @_; 4042: my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($ENV{'form.selectpage'}); 4043: my ($symb,$url)=&get_symb_and_url($r); 4044: if (!$symb) {return '';} 4045: my $default_form_data=&defaultFormData($symb,$url); 4046: 4047: my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'}); 4048: my ($scanlines,$scan_data)=&scantron_getfile(); 4049: my $classlist=&Apache::loncoursedata::get_classlist(); 4050: my %idmap=&username_to_idmap($classlist); 4051: my $navmap=Apache::lonnavmaps::navmap->new(); 4052: my $map=$navmap->getResourceByUrl($sequence); 4053: my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0); 4054: # $r->print("geto ".scalar(@resources)."<br />"); 4055: my $result= <<SCANTRONFORM; 4056: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload"> 4057: <input type="hidden" name="command" value="scantron_configphase" /> 4058: $default_form_data 4059: SCANTRONFORM 4060: $r->print($result); 4061: 4062: my @delayqueue; 4063: my %completedstudents; 4064: 4065: my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron Status', 4066: 'Scantron Progress',$scanlines->{'count'}); 4067: &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state, 4068: 'Processing first student'); 4069: my $start=&Time::HiRes::time(); 4070: my $i=-1; 4071: my ($uname,$udom); 4072: while ($i<$scanlines->{'count'}) { 4073: ($uname,$udom)=('',''); 4074: $i++; 4075: my $line=&scantron_get_line($scanlines,$i); 4076: # $r->print('<pre>line is'.$line.'</pre>'); 4077: if ($line=~/^[\s\cz]*$/) { next; } 4078: my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, 4079: $scan_data); 4080: unless ($uname=&scantron_find_student($scan_record,$scan_data, 4081: \%idmap,$i)) { 4082: &scantron_add_delay(\@delayqueue,$line, 4083: 'Unable to find a student that matches',1); 4084: next; 4085: } 4086: if (exists $completedstudents{$uname}) { 4087: &scantron_add_delay(\@delayqueue,$line, 4088: 'Student '.$uname.' has multiple sheets',2); 4089: next; 4090: } 4091: # $r->print('<pre>doing studnet'.$uname.'</pre>'); 4092: ($uname,$udom)=split(/:/,$uname); 4093: &Apache::lonnet::delenv('form.counter'); 4094: &Apache::lonnet::appenv(%$scan_record); 4095: # &Apache::lonhomework::showhash(%ENV); 4096: # $Apache::lonxml::debug=1; 4097: # &Apache::lonxml::debug("line is $line"); 4098: 4099: my $i=0; 4100: foreach my $resource (@resources) { 4101: $i++; 4102: my $result=&Apache::lonnet::ssi($resource->src(), 4103: ('submitted' =>'scantron', 4104: 'grade_target' =>'grade', 4105: 'grade_username'=>$uname, 4106: 'grade_domain' =>$udom, 4107: 'grade_courseid'=>$ENV{'request.course.id'}, 4108: 'grade_symb' =>$resource->symb())); 4109: # my %score=&Apache::lonnet::restore($resource->symb(), 4110: # $ENV{'request.course.id'}, 4111: # $udom,$uname); 4112: # foreach my $part ($resource->{PARTS}) { 4113: # if ($score{'resource.'.$part.'.solved'} =~ /^correct/) { 4114: # $studentcorrect++; 4115: # $totalcorrect++; 4116: # } else { 4117: # $studentincorrect++; 4118: # $totalincorrect++; 4119: # } 4120: # } 4121: # $r->print('<pre>'. 4122: # $resource->symb().'-'. 4123: # $resource->src().'-'.'</pre>result is'.$result); 4124: # &Apache::lonhomework::showhash(%score); 4125: # if ($i eq 3) {last;} 4126: } 4127: $completedstudents{$uname}={'line'=>$line}; 4128: } continue { 4129: &Apache::lonnet::delenv('form.counter'); 4130: &Apache::lonnet::delenv('scantron\.'); 4131: &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state, 4132: 'last student'); 4133: #last; 4134: #FIXME 4135: #get iterator for $sequence 4136: #foreach question 'submit' the students answer to the server 4137: # through grade target { 4138: # generate data to pass back that includes grade recevied 4139: #} 4140: } 4141: &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); 4142: my $lasttime = &Time::HiRes::time()-$start; 4143: $r->print("<p>took $lasttime</p>"); 4144: 4145: #$Apache::lonxml::debug=0; 4146: foreach my $delay (@delayqueue) { 4147: #FIXME 4148: #print out each delayed student with interface to select how 4149: # to repair student provided info 4150: #Expected errors include 4151: # 1 bad/no stuid/username 4152: # 2 invalid bubblings 4153: 4154: } 4155: #FIXME 4156: # if delay queue exists 2 submits one to process delayed students one 4157: # to ignore delayed students, possibly saving the delay queue for later 4158: 4159: $navmap->untieHashes(); 4160: $r->print("<p>Done</p>"); 4161: $r->print(&show_grading_menu_form($symb,$url)); 4162: return ''; 4163: } 4164: 4165: sub scantron_upload_scantron_data { 4166: my ($r)=@_; 4167: $r->print(&Apache::loncommon::coursebrowser_javascript($ENV{'request.role.domain'})); 4168: my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid', 4169: 'domainid'); 4170: my $domsel=&Apache::loncommon::select_dom_form($ENV{'request.role.domain'}, 4171: 'domainid'); 4172: $r->print(<<UPLOAD); 4173: <script type="text/javascript" language="javascript"> 4174: function checkUpload(formname) { 4175: if (formname.upfile.value == "") { 4176: alert("Please use the browse button to select a file from your local directory."); 4177: return false; 4178: } 4179: formname.submit(); 4180: } 4181: </script> 4182: 4183: <form enctype='multipart/form-data' action='/adm/grades' name='rules' method='post'> 4184: Course: <input name='courseid' type='text' /> 4185: Domain: $domsel $select_link 4186: <br /> 4187: <input name='command' value='scantronupload_save' type='hidden' /> 4188: File to upload:<input type="file" name="upfile" size="50" /> 4189: <br /> 4190: <input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scantron Data" /> 4191: </form> 4192: UPLOAD 4193: return ''; 4194: } 4195: 4196: sub scantron_upload_scantron_data_save { 4197: my($r)=@_; 4198: $r->print("Doing upload to ".$ENV{'form.courseid'}); 4199: my $home=&Apache::lonnet::homeserver($ENV{'form.courseid'}, 4200: $ENV{'form.domainid'}); 4201: my $fname=$ENV{'form.upfile.filename'}; 4202: #FIXME 4203: #copied from lonnet::userfileupload() 4204: #make that function able to target a specified course 4205: # Replace Windows backslashes by forward slashes 4206: $fname=~s/\\/\//g; 4207: # Get rid of everything but the actual filename 4208: $fname=~s/^.*\/([^\/]+)$/$1/; 4209: # Replace spaces by underscores 4210: $fname=~s/\s+/\_/g; 4211: # Replace all other weird characters by nothing 4212: $fname=~s/[^\w\.\-]//g; 4213: # See if there is anything left 4214: unless ($fname) { return 'error: no uploaded file'; } 4215: $fname='scantron_orig_'.$fname; 4216: &Apache::lonnet::logthis("fname is $fname"); 4217: $r->print(&Apache::lonnet::finishuserfileupload($ENV{'form.courseid'}, 4218: $ENV{'form.domainid'}, 4219: $home,'upfile',$fname)); 4220: return ''; 4221: } 4222: 4223: 4224: #-------- end of section for handling grading scantron forms ------- 4225: # 4226: #------------------------------------------------------------------- 4227: 4228: 4229: #-------------------------- Menu interface ------------------------- 4230: # 4231: #--- Show a Grading Menu button - Calls the next routine --- 4232: sub show_grading_menu_form { 4233: my ($symb,$url)=@_; 4234: my $result.='<br /><form action="/adm/grades" method="post">'."\n". 4235: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 4236: '<input type="hidden" name="url" value="'.$url.'" />'."\n". 4237: '<input type="hidden" name="saveState" value="'.$ENV{'form.saveState'}.'" />'."\n". 4238: '<input type="hidden" name="command" value="gradingmenu" />'."\n". 4239: '<input type="submit" name="submit" value="Grading Menu" />'."\n". 4240: '</form>'."\n"; 4241: return $result; 4242: } 4243: 4244: # -- Retrieve choices for grading form 4245: sub savedState { 4246: my %savedState = (); 4247: if ($ENV{'form.saveState'}) { 4248: foreach (split(/:/,$ENV{'form.saveState'})) { 4249: my ($key,$value) = split(/=/,$_,2); 4250: $savedState{$key} = $value; 4251: } 4252: } 4253: return \%savedState; 4254: } 4255: 4256: #--- Displays the main menu page ------- 4257: sub gradingmenu { 4258: my ($request) = @_; 4259: my ($symb,$url)=&get_symb_and_url($request); 4260: if (!$symb) {return '';} 4261: my $probTitle = &Apache::lonnet::gettitle($symb); 4262: 4263: $request->print(<<GRADINGMENUJS); 4264: <script type="text/javascript" language="javascript"> 4265: function checkChoice(formname,val,cmdx) { 4266: if (val <= 2) { 4267: var cmd = radioSelection(formname.radioChoice); 4268: var cmdsave = cmd; 4269: } else { 4270: cmd = cmdx; 4271: cmdsave = 'submission'; 4272: } 4273: formname.command.value = cmd; 4274: formname.saveState.value = "saveCmd="+cmdsave+":saveSec="+pullDownSelection(formname.section)+ 4275: ":saveSub="+pullDownSelection(formname.submitonly)+":saveStatus="+pullDownSelection(formname.Status); 4276: if (val < 5) formname.submit(); 4277: if (val == 5) { 4278: if (!checkReceiptNo(formname,'notOK')) { return false;} 4279: formname.submit(); 4280: } 4281: } 4282: 4283: function checkReceiptNo(formname,nospace) { 4284: var receiptNo = formname.receipt.value; 4285: var checkOpt = false; 4286: if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;} 4287: if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;} 4288: if (checkOpt) { 4289: alert("Please enter a receipt number given by a student in the receipt box."); 4290: formname.receipt.value = ""; 4291: formname.receipt.focus(); 4292: return false; 4293: } 4294: return true; 4295: } 4296: </script> 4297: GRADINGMENUJS 4298: &commonJSfunctions($request); 4299: my $result='<h3> <font color="#339933">Manual Grading/View Submission</font></h3>'; 4300: my ($table,undef,$hdgrade) = &showResourceInfo($url,$probTitle); 4301: $result.=$table; 4302: my (undef,$sections) = &getclasslist('all','0'); 4303: my $savedState = &savedState(); 4304: my $saveCmd = ($$savedState{'saveCmd'} eq '' ? 'submission' : $$savedState{'saveCmd'}); 4305: my $saveSec = ($$savedState{'saveSec'} eq '' ? 'all' : $$savedState{'saveSec'}); 4306: my $saveSub = ($$savedState{'saveSub'} eq '' ? 'all' : $$savedState{'saveSub'}); 4307: my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'}); 4308: 4309: $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n". 4310: '<input type="hidden" name="symb" value="'.$symb.'" />'."\n". 4311: '<input type="hidden" name="url" value="'.$url.'" />'."\n". 4312: '<input type="hidden" name="handgrade" value="'.$hdgrade.'" />'."\n". 4313: '<input type="hidden" name="probTitle" value="'.$probTitle.'" />'."\n". 4314: '<input type="hidden" name="command" value="" />'."\n". 4315: '<input type="hidden" name="saveState" value="" />'."\n". 4316: '<input type="hidden" name="gradingMenu" value="1" />'."\n". 4317: '<input type="hidden" name="showgrading" value="yes" />'."\n"; 4318: 4319: $result.='<table width="100%" border=0><tr><td bgcolor=#777777>'."\n". 4320: '<table width=100% border=0><tr bgcolor="#e6ffff"><td colspan="2">'."\n". 4321: ' <b>Select a Grading/Viewing Option</b></td></tr>'."\n". 4322: '<tr bgcolor="#ffffe6" valign="top"><td>'."\n"; 4323: 4324: $result.='<table width="100%" border=0>'; 4325: $result.='<tr bgcolor="#ffffe6" valign="top"><td>'."\n". 4326: ' Select Section: <select name="section">'."\n"; 4327: if (ref($sections)) { 4328: foreach (sort (@$sections)) { 4329: $result.='<option value="'.$_.'" '. 4330: ($saveSec eq $_ ? 'selected="on"':'').'>'.$_.'</option>'."\n"; 4331: } 4332: } 4333: $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="on"' : ''). '>all</select> '; 4334: 4335: $result.='Student Status:</b>'.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,undef); 4336: 4337: if (ref($sections) && (grep /no/,@$sections)) { 4338: $result.=' (Section "no" implies the students were not assigned a section.)<br />'; 4339: } 4340: $result.='</td></tr>'; 4341: 4342: $result.='<tr bgcolor="#ffffe6"valign="top"><td>'. 4343: '<input type="radio" name="radioChoice" value="submission" '. 4344: ($saveCmd eq 'submission' ? 'checked' : '').'> '.'<b>Current Resource:</b> For one or more students '. 4345: '<select name="submitonly">'. 4346: '<option value="yes" '. 4347: ($saveSub eq 'yes' ? 'selected="on"' : '').'>with submissions</option>'. 4348: '<option value="graded" '. 4349: ($saveSub eq 'graded' ? 'selected="on"' : '').'>with ungraded submissions</option>'. 4350: '<option value="incorrect" '. 4351: ($saveSub eq 'incorrect' ? 'selected="on"' : '').'>with incorrect submissions</option>'. 4352: '<option value="all" '. 4353: ($saveSub eq 'all' ? 'selected="on"' : '').'>with any status</option></select></td></tr>'."\n"; 4354: 4355: $result.='<tr bgcolor="#ffffe6"valign="top"><td>'. 4356: '<input type="radio" name="radioChoice" value="viewgrades" '. 4357: ($saveCmd eq 'viewgrades' ? 'checked' : '').'> '. 4358: '<b>Current Resource:</b> For all students in selected section or course</td></tr>'."\n"; 4359: 4360: $result.='<tr bgcolor="#ffffe6" valign="top"><td>'. 4361: '<input type="radio" name="radioChoice" value="pickStudentPage" '. 4362: ($saveCmd eq 'pickStudentPage' ? 'checked' : '').'> '. 4363: 'The <b>complete</b> set/page/sequence: For one student</td></tr>'."\n"; 4364: 4365: $result.='<tr bgcolor="#ffffe6"><td><br />'. 4366: '<input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="Next->" />'. 4367: '</td></tr></table>'."\n"; 4368: 4369: $result.='</td><td valign="top">'; 4370: 4371: $result.='<table width="100%" border=0>'; 4372: $result.='<tr bgcolor="#ffffe6"><td>'. 4373: '<input type="button" onClick="javascript:checkChoice(this.form,\'3\',\'csvform\');" value="Upload" />'. 4374: ' scores from file </td></tr>'."\n"; 4375: 4376: $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'. 4377: '<input type="button" onClick="javascript:checkChoice(this.form,\'4\',\'scantron_selectphase\');'. 4378: '" value="Grade" /> scantron forms</td></tr>'."\n"; 4379: 4380: if ((&Apache::lonnet::allowed('mgr',$ENV{'request.course.id'})) && ($symb)) { 4381: $result.='<tr bgcolor="#ffffe6"valign="top"><td>'. 4382: '<input type="button" onClick="javascript:checkChoice(this.form,\'5\',\'verify\');" value="Verify" />'. 4383: ' submission Receipt no: '.unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'}). 4384: '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')">'. 4385: '</td></tr>'."\n"; 4386: } 4387: 4388: $result.='</form></td></tr></table>'."\n". 4389: '</td></tr></table>'."\n". 4390: '</td></tr></table>'."\n"; 4391: return $result; 4392: } 4393: 4394: sub handler { 4395: my $request=$_[0]; 4396: 4397: undef(%perm); 4398: if ($ENV{'browser.mathml'}) { 4399: &Apache::loncommon::content_type($request,'text/xml'); 4400: } else { 4401: &Apache::loncommon::content_type($request,'text/html'); 4402: } 4403: $request->send_http_header; 4404: return '' if $request->header_only; 4405: &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}); 4406: my $url=$ENV{'form.url'}; 4407: my $symb=$ENV{'form.symb'}; 4408: my $command=$ENV{'form.command'}; 4409: if (!$url) { 4410: my ($temp1,$temp2); 4411: ($temp1,$temp2,$ENV{'form.url'})=&Apache::lonnet::decode_symb($symb); 4412: $url = $ENV{'form.url'}; 4413: } 4414: &send_header($request); 4415: if ($url eq '' && $symb eq '' && $command eq '') { 4416: if ($ENV{'user.adv'}) { 4417: if (($ENV{'form.codeone'}) && ($ENV{'form.codetwo'}) && 4418: ($ENV{'form.codethree'})) { 4419: my $token=$ENV{'form.codeone'}.'*'.$ENV{'form.codetwo'}.'*'. 4420: $ENV{'form.codethree'}; 4421: my ($tsymb,$tuname,$tudom,$tcrsid)= 4422: &Apache::lonnet::checkin($token); 4423: if ($tsymb) { 4424: my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb); 4425: if (&Apache::lonnet::allowed('mgr',$tcrsid)) { 4426: $request->print(&Apache::lonnet::ssi_body('/res/'.$url, 4427: ('grade_username' => $tuname, 4428: 'grade_domain' => $tudom, 4429: 'grade_courseid' => $tcrsid, 4430: 'grade_symb' => $tsymb))); 4431: } else { 4432: $request->print('<h3>Not authorized: '.$token.'</h3>'); 4433: } 4434: } else { 4435: $request->print('<h3>Not a valid DocID: '.$token.'</h3>'); 4436: } 4437: } else { 4438: $request->print(&Apache::lonxml::tokeninputfield()); 4439: } 4440: } 4441: } else { 4442: if (!($perm{'vgr'}=&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'}))) { 4443: if ($perm{'vgr'}=&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'}.'/'.$ENV{'request.course.sec'})) { 4444: $perm{'vgr_section'}=$ENV{'request.course.sec'}; 4445: } else { 4446: delete($perm{'vgr'}); 4447: } 4448: } 4449: if (!($perm{'mgr'}=&Apache::lonnet::allowed('mgr',$ENV{'request.course.id'}))) { 4450: if ($perm{'mgr'}=&Apache::lonnet::allowed('mgr',$ENV{'request.course.id'}.'/'.$ENV{'request.course.sec'})) { 4451: $perm{'mgr_section'}=$ENV{'request.course.sec'}; 4452: } else { 4453: delete($perm{'mgr'}); 4454: } 4455: } 4456: if ($command eq 'submission' && $perm{'vgr'}) { 4457: ($ENV{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0)); 4458: } elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) { 4459: &pickStudentPage($request); 4460: } elsif ($command eq 'displayPage' && $perm{'vgr'}) { 4461: &displayPage($request); 4462: } elsif ($command eq 'gradeByPage' && $perm{'mgr'}) { 4463: &updateGradeByPage($request); 4464: } elsif ($command eq 'processGroup' && $perm{'vgr'}) { 4465: &processGroup($request); 4466: } elsif ($command eq 'gradingmenu' && $perm{'vgr'}) { 4467: $request->print(&gradingmenu($request)); 4468: } elsif ($command eq 'viewgrades' && $perm{'vgr'}) { 4469: $request->print(&viewgrades($request)); 4470: } elsif ($command eq 'handgrade' && $perm{'mgr'}) { 4471: $request->print(&processHandGrade($request)); 4472: } elsif ($command eq 'editgrades' && $perm{'mgr'}) { 4473: $request->print(&editgrades($request)); 4474: } elsif ($command eq 'verify' && $perm{'vgr'}) { 4475: $request->print(&verifyreceipt($request)); 4476: } elsif ($command eq 'csvform' && $perm{'mgr'}) { 4477: $request->print(&upcsvScores_form($request)); 4478: } elsif ($command eq 'csvupload' && $perm{'mgr'}) { 4479: $request->print(&csvupload($request)); 4480: } elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) { 4481: $request->print(&csvuploadmap($request)); 4482: } elsif ($command eq 'csvuploadassign' && $perm{'mgr'}) { 4483: if ($ENV{'form.associate'} ne 'Reverse Association') { 4484: $request->print(&csvuploadassign($request)); 4485: } else { 4486: if ( $ENV{'form.upfile_associate'} ne 'reverse' ) { 4487: $ENV{'form.upfile_associate'} = 'reverse'; 4488: } else { 4489: $ENV{'form.upfile_associate'} = 'forward'; 4490: } 4491: $request->print(&csvuploadmap($request)); 4492: } 4493: } elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) { 4494: $request->print(&scantron_selectphase($request)); 4495: } elsif ($command eq 'scantron_validate' && $perm{'mgr'}) { 4496: $request->print(&scantron_validate_file($request)); 4497: } elsif ($command eq 'scantron_validate' && $perm{'mgr'}) { 4498: $request->print(&scantron_validate_file($request)); 4499: } elsif ($command eq 'scantron_process' && $perm{'mgr'}) { 4500: $request->print(&scantron_process_students($request)); 4501: } elsif ($command eq 'scantronupload' && 4502: &Apache::lonnet::allowed('usc',$ENV{'request.role.domain'})) { 4503: $request->print(&scantron_upload_scantron_data($request)); 4504: 4505: } elsif ($command eq 'scantronupload_save' && 4506: &Apache::lonnet::allowed('usc',$ENV{'request.role.domain'})) { 4507: $request->print(&scantron_upload_scantron_data_save($request)); 4508: } elsif ($command) { 4509: $request->print("Access Denied ($command)"); 4510: } 4511: } 4512: &send_footer($request); 4513: return ''; 4514: } 4515: 4516: sub send_header { 4517: my ($request)= @_; 4518: $request->print(&Apache::lontexconvert::header()); 4519: # $request->print(" 4520: #<script> 4521: #remotewindow=open('','homeworkremote'); 4522: #remotewindow.close(); 4523: #</script>"); 4524: $request->print(&Apache::loncommon::bodytag('Grading')); 4525: $request->rflush(); 4526: } 4527: 4528: sub send_footer { 4529: my ($request)= @_; 4530: $request->print('</body>'); 4531: $request->print(&Apache::lontexconvert::footer()); 4532: } 4533: 4534: 1; 4535: 4536: __END__;