File:
[LON-CAPA] /
loncom /
homework /
grades.pm
Revision
1.503:
download - view:
text,
annotated -
select for diffs
Fri Feb 1 22:50:43 2008 UTC (16 years, 3 months ago) by
raeburn
Branches:
MAIN
CVS tags:
HEAD
Bug 5589
In exam mode, optionresponse and matchresponse problems in a single part are divided into sub-questions, each one with its own scantron bubble line(s).
In exam mode, essayresponse, formularesponse and stringresponse can have multiple bubble lines associated with a single part, and bubbles in more than one of these lines, if the weight assigned to the part exceeds 10.
These two cases are addressed. The first, by passing scantron data lines to the validator for each of the sub-questions in turn, and the second by performing the occurrence_count check on each line in turn. Some bookkeeping code has been added to keep track of data for each subquestion.
- %subdivided_bubble_lines hash provides a place to store bubble lines per subquestion for optionresponse and matchresponse.
- %responsetype_per_response provides a place to store the responsetype for each response.
- In the case of essayresponse problems, items are not included in $analysis{'parts'} from lonnet::ssi, so possible part IDs need to be retrieved from the $resource object.
- When preserving information about missingbubbles, use questionnum (which includes subquestion) instead of question.
- Validation for the different scantron formats - letter/number or positional moved to two subroutines - &scantron_validator_lettnum(), and &scantron_validator_positional().
- &prompt_for_corrections() can return an array containing numbers of lines with missingbubble or doublebubble.
- This array is passed to &verify_bubbles_checked() which provides a javascript function to alert the user if any lines are without a choice of bubble or 'No bubble' for the current validation screen, for missingbubble or doublebubble cases only.
- Data tabling the validation tables for missing or double bubbles.
- Fix some typos and expand the documentation for bubbling multiple lines for essayresponse, formularesponse or string response (where more than 1 line may contain a bubble).
1: # The LearningOnline Network with CAPA
2: # The LON-CAPA Grading handler
3: #
4: # $Id: grades.pm,v 1.503 2008/02/01 22:50:43 raeburn Exp $
5: #
6: # Copyright Michigan State University Board of Trustees
7: #
8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
9: #
10: # LON-CAPA is free software; you can redistribute it and/or modify
11: # it under the terms of the GNU General Public License as published by
12: # the Free Software Foundation; either version 2 of the License, or
13: # (at your option) any later version.
14: #
15: # LON-CAPA is distributed in the hope that it will be useful,
16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18: # GNU General Public License for more details.
19: #
20: # You should have received a copy of the GNU General Public License
21: # along with LON-CAPA; if not, write to the Free Software
22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23: #
24: # /home/httpd/html/adm/gpl.txt
25: #
26: # http://www.lon-capa.org/
27: #
28:
29: package Apache::grades;
30: use strict;
31: use Apache::style;
32: use Apache::lonxml;
33: use Apache::lonnet;
34: use Apache::loncommon;
35: use Apache::lonhtmlcommon;
36: use Apache::lonnavmaps;
37: use Apache::lonhomework;
38: use Apache::lonpickcode;
39: use Apache::loncoursedata;
40: use Apache::lonmsg();
41: use Apache::Constants qw(:common);
42: use Apache::lonlocal;
43: use Apache::lonenc;
44: use String::Similarity;
45: use LONCAPA;
46:
47: use POSIX qw(floor);
48:
49:
50: my %perm=();
51:
52: #
53: # --- Retrieve the parts from the metadata file.---
54: sub getpartlist {
55: my ($symb) = @_;
56:
57: my $navmap = Apache::lonnavmaps::navmap->new();
58: my $res = $navmap->getBySymb($symb);
59: my $partlist = $res->parts();
60: my $url = $res->src();
61: my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys'));
62:
63: my @stores;
64: foreach my $part (@{ $partlist }) {
65: foreach my $key (@metakeys) {
66: if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }
67: }
68: }
69: return @stores;
70: }
71:
72: # --- Get the symbolic name of a problem and the url
73: sub get_symb {
74: my ($request,$silent) = @_;
75: (my $url=$env{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
76: my $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url)));
77: if ($symb eq '') {
78: if (!$silent) {
79: $request->print("Unable to handle ambiguous references:$url:.");
80: return ();
81: }
82: }
83: &Apache::lonenc::check_decrypt(\$symb);
84: return ($symb);
85: }
86:
87: #--- Format fullname, username:domain if different for display
88: #--- Use anywhere where the student names are listed
89: sub nameUserString {
90: my ($type,$fullname,$uname,$udom) = @_;
91: if ($type eq 'header') {
92: return '<b> '.&mt('Fullname').' </b><span class="LC_internal_info">('.&mt('Username').')</span>';
93: } else {
94: return ' '.$fullname.'<span class="LC_internal_info"> ('.$uname.
95: ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</span>';
96: }
97: }
98:
99: #--- Get the partlist and the response type for a given problem. ---
100: #--- Indicate if a response type is coded handgraded or not. ---
101: sub response_type {
102: my ($symb) = shift;
103:
104: my $navmap = Apache::lonnavmaps::navmap->new();
105: my $res = $navmap->getBySymb($symb);
106: my $partlist = $res->parts();
107: my %vPart =
108: map { $_ => 1 } (&Apache::loncommon::get_env_multiple('form.vPart'));
109: my (%response_types,%handgrade);
110: foreach my $part (@{ $partlist }) {
111: next if (%vPart && !exists($vPart{$part}));
112:
113: my @types = $res->responseType($part);
114: my @ids = $res->responseIds($part);
115: for (my $i=0; $i < scalar(@ids); $i++) {
116: $response_types{$part}{$ids[$i]} = $types[$i];
117: $handgrade{$part.'_'.$ids[$i]} =
118: &Apache::lonnet::EXT('resource.'.$part.'_'.$ids[$i].
119: '.handgrade',$symb);
120: }
121: }
122: return ($partlist,\%handgrade,\%response_types);
123: }
124:
125: sub flatten_responseType {
126: my ($responseType) = @_;
127: my @part_response_id =
128: map {
129: my $part = $_;
130: map {
131: [$part,$_]
132: } sort(keys(%{ $responseType->{$part} }));
133: } sort(keys(%$responseType));
134: return @part_response_id;
135: }
136:
137: sub get_display_part {
138: my ($partID,$symb)=@_;
139: my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);
140: if (defined($display) and $display ne '') {
141: $display.= " (<span class=\"LC_internal_info\">id $partID</span>)";
142: } else {
143: $display=$partID;
144: }
145: return $display;
146: }
147:
148: #--- Show resource title
149: #--- and parts and response type
150: sub showResourceInfo {
151: my ($symb,$probTitle,$checkboxes) = @_;
152: my $col=3;
153: if ($checkboxes) { $col=4; }
154: my $result = '<h3>'.&mt('Current Resource').': '.$probTitle.'</h3>'."\n";
155: $result .='<table border="0">';
156: my ($partlist,$handgrade,$responseType) = &response_type($symb);
157: my %resptype = ();
158: my $hdgrade='no';
159: my %partsseen;
160: foreach my $partID (sort keys(%$responseType)) {
161: foreach my $resID (sort keys(%{ $responseType->{$partID} })) {
162: my $handgrade=$$handgrade{$partID.'_'.$resID};
163: my $responsetype = $responseType->{$partID}->{$resID};
164: $hdgrade = $handgrade if ($handgrade eq 'yes');
165: $result.='<tr>';
166: if ($checkboxes) {
167: if (exists($partsseen{$partID})) {
168: $result.="<td> </td>";
169: } else {
170: $result.="<td><input type='checkbox' name='vPart' value='$partID' checked='checked' /></td>";
171: }
172: $partsseen{$partID}=1;
173: }
174: my $display_part=&get_display_part($partID,$symb);
175: $result.='<td>'.&mt('<b>Part: </b>[_1]',$display_part).' <span class="LC_internal_info">'.
176: $resID.'</span></td>'.
177: '<td>'.&mt('<b>Type: </b>[_1]',$responsetype).'</td></tr>';
178: # '<td>'.&mt('<b>Handgrade: </b>[_1]',$handgrade).'</td></tr>';
179: }
180: }
181: $result.='</table>'."\n";
182: return $result,$responseType,$hdgrade,$partlist,$handgrade;
183: }
184:
185: sub reset_caches {
186: &reset_analyze_cache();
187: &reset_perm();
188: }
189:
190: {
191: my %analyze_cache;
192:
193: sub reset_analyze_cache {
194: undef(%analyze_cache);
195: }
196:
197: sub get_analyze {
198: my ($symb,$uname,$udom)=@_;
199: my $key = "$symb\0$uname\0$udom";
200: return $analyze_cache{$key} if (exists($analyze_cache{$key}));
201:
202: my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
203: $url=&Apache::lonnet::clutter($url);
204: my $subresult=&Apache::lonnet::ssi($url,
205: ('grade_target' => 'analyze'),
206: ('grade_domain' => $udom),
207: ('grade_symb' => $symb),
208: ('grade_courseid' =>
209: $env{'request.course.id'}),
210: ('grade_username' => $uname));
211: (undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
212: my %analyze=&Apache::lonnet::str2hash($subresult);
213: return $analyze_cache{$key} = \%analyze;
214: }
215:
216: sub get_order {
217: my ($partid,$respid,$symb,$uname,$udom)=@_;
218: my $analyze = &get_analyze($symb,$uname,$udom);
219: return $analyze->{"$partid.$respid.shown"};
220: }
221:
222: sub get_radiobutton_correct_foil {
223: my ($partid,$respid,$symb,$uname,$udom)=@_;
224: my $analyze = &get_analyze($symb,$uname,$udom);
225: foreach my $foil (@{&get_order($partid,$respid,$symb,$uname,$udom)}) {
226: if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {
227: return $foil;
228: }
229: }
230: }
231: }
232:
233: #--- Clean response type for display
234: #--- Currently filters option/rank/radiobutton/match/essay/Task
235: # response types only.
236: sub cleanRecord {
237: my ($answer,$response,$symb,$partid,$respid,$record,$order,$version,
238: $uname,$udom) = @_;
239: my $grayFont = '<span class="LC_internal_info">';
240: if ($response =~ /^(option|rank)$/) {
241: my %answer=&Apache::lonnet::str2hash($answer);
242: my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
243: my ($toprow,$bottomrow);
244: foreach my $foil (@$order) {
245: if ($grading{$foil} == 1) {
246: $toprow.='<td><b>'.$answer{$foil}.' </b></td>';
247: } else {
248: $toprow.='<td><i>'.$answer{$foil}.' </i></td>';
249: }
250: $bottomrow.='<td>'.$grayFont.$foil.'</span> </td>';
251: }
252: return '<blockquote><table border="1">'.
253: '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
254: '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
255: $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';
256: } elsif ($response eq 'match') {
257: my %answer=&Apache::lonnet::str2hash($answer);
258: my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
259: my @items=&Apache::lonnet::str2array($record->{$version."resource.$partid.$respid.submissionitems"});
260: my ($toprow,$middlerow,$bottomrow);
261: foreach my $foil (@$order) {
262: my $item=shift(@items);
263: if ($grading{$foil} == 1) {
264: $toprow.='<td><b>'.$item.' </b></td>';
265: $middlerow.='<td><b>'.$grayFont.$answer{$foil}.' </span></b></td>';
266: } else {
267: $toprow.='<td><i>'.$item.' </i></td>';
268: $middlerow.='<td><i>'.$grayFont.$answer{$foil}.' </span></i></td>';
269: }
270: $bottomrow.='<td>'.$grayFont.$foil.'</span> </td>';
271: }
272: return '<blockquote><table border="1">'.
273: '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
274: '<tr valign="top"><td>'.$grayFont.&mt('Item ID').'</span></td>'.
275: $middlerow.'</tr>'.
276: '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
277: $bottomrow.'</tr>'.'</table></blockquote>';
278: } elsif ($response eq 'radiobutton') {
279: my %answer=&Apache::lonnet::str2hash($answer);
280: my ($toprow,$bottomrow);
281: my $correct =
282: &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom);
283: foreach my $foil (@$order) {
284: if (exists($answer{$foil})) {
285: if ($foil eq $correct) {
286: $toprow.='<td><b>'.&mt('true').'</b></td>';
287: } else {
288: $toprow.='<td><i>'.&mt('true').'</i></td>';
289: }
290: } else {
291: $toprow.='<td>'.&mt('false').'</td>';
292: }
293: $bottomrow.='<td>'.$grayFont.$foil.'</span> </td>';
294: }
295: return '<blockquote><table border="1">'.
296: '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
297: '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
298: $grayFont.$bottomrow.'</tr>'.'</table></blockquote>';
299: } elsif ($response eq 'essay') {
300: if (! exists ($env{'form.'.$symb})) {
301: my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade',
302: $env{'course.'.$env{'request.course.id'}.'.domain'},
303: $env{'course.'.$env{'request.course.id'}.'.num'});
304:
305: my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
306: $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
307: $env{'form.kwclr'} = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
308: $env{'form.kwsize'} = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
309: $env{'form.kwstyle'} = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
310: $env{'form.'.$symb} = 1; # so that we don't have to read it from disk for multiple sub of the same prob.
311: }
312: $answer =~ s-\n-<br />-g;
313: return '<br /><br /><blockquote><tt>'.&keywords_highlight($answer).'</tt></blockquote>';
314: } elsif ( $response eq 'organic') {
315: my $result='Smile representation: "<tt>'.$answer.'</tt>"';
316: my $jme=$record->{$version."resource.$partid.$respid.molecule"};
317: $result.=&Apache::chemresponse::jme_img($jme,$answer,400);
318: return $result;
319: } elsif ( $response eq 'Task') {
320: if ( $answer eq 'SUBMITTED') {
321: my $files = $record->{$version."resource.$respid.$partid.bridgetask.portfiles"};
322: my $result = &Apache::bridgetask::file_list($files,$uname,$udom);
323: return $result;
324: } elsif ( grep(/^\Q$version\E.*?\.instance$/, keys(%{$record})) ) {
325: my @matches = grep(/^\Q$version\E.*?\.instance$/,
326: keys(%{$record}));
327: return join('<br />',($version,@matches));
328:
329:
330: } else {
331: my $result =
332: '<p>'
333: .&mt('Overall result: [_1]',
334: $record->{$version."resource.$respid.$partid.status"})
335: .'</p>';
336:
337: $result .= '<ul>';
338: my @grade = grep(/^\Q${version}resource.$respid.$partid.\E[^.]*[.]status$/,
339: keys(%{$record}));
340: foreach my $grade (sort(@grade)) {
341: my ($dim) = ($grade =~/[.]([^.]+)[.]status$/);
342: $result.= '<li>'.&mt("Dimension: [_1], status [_2] ",
343: $dim, $record->{$grade}).
344: '</li>';
345: }
346: $result.='</ul>';
347: return $result;
348: }
349: } elsif ( $response =~ m/(?:numerical|formula)/) {
350: $answer =
351: &Apache::loncommon::format_previous_attempt_value('submission',
352: $answer);
353: }
354: return $answer;
355: }
356:
357: #-- A couple of common js functions
358: sub commonJSfunctions {
359: my $request = shift;
360: $request->print(<<COMMONJSFUNCTIONS);
361: <script type="text/javascript" language="javascript">
362: function radioSelection(radioButton) {
363: var selection=null;
364: if (radioButton.length > 1) {
365: for (var i=0; i<radioButton.length; i++) {
366: if (radioButton[i].checked) {
367: return radioButton[i].value;
368: }
369: }
370: } else {
371: if (radioButton.checked) return radioButton.value;
372: }
373: return selection;
374: }
375:
376: function pullDownSelection(selectOne) {
377: var selection="";
378: if (selectOne.length > 1) {
379: for (var i=0; i<selectOne.length; i++) {
380: if (selectOne[i].selected) {
381: return selectOne[i].value;
382: }
383: }
384: } else {
385: // only one value it must be the selected one
386: return selectOne.value;
387: }
388: }
389: </script>
390: COMMONJSFUNCTIONS
391: }
392:
393: #--- Dumps the class list with usernames,list of sections,
394: #--- section, ids and fullnames for each user.
395: sub getclasslist {
396: my ($getsec,$filterlist,$getgroup) = @_;
397: my @getsec;
398: my @getgroup;
399: my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
400: if (!ref($getsec)) {
401: if ($getsec ne '' && $getsec ne 'all') {
402: @getsec=($getsec);
403: }
404: } else {
405: @getsec=@{$getsec};
406: }
407: if (grep(/^all$/,@getsec)) { undef(@getsec); }
408: if (!ref($getgroup)) {
409: if ($getgroup ne '' && $getgroup ne 'all') {
410: @getgroup=($getgroup);
411: }
412: } else {
413: @getgroup=@{$getgroup};
414: }
415: if (grep(/^all$/,@getgroup)) { undef(@getgroup); }
416:
417: my ($classlist,$keylist)=&Apache::loncoursedata::get_classlist();
418: # Bail out if we were unable to get the classlist
419: return if (! defined($classlist));
420: &Apache::loncoursedata::get_group_memberships($classlist,$keylist);
421: #
422: my %sections;
423: my %fullnames;
424: foreach my $student (keys(%$classlist)) {
425: my $end =
426: $classlist->{$student}->[&Apache::loncoursedata::CL_END()];
427: my $start =
428: $classlist->{$student}->[&Apache::loncoursedata::CL_START()];
429: my $id =
430: $classlist->{$student}->[&Apache::loncoursedata::CL_ID()];
431: my $section =
432: $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
433: my $fullname =
434: $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()];
435: my $status =
436: $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()];
437: my $group =
438: $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
439: # filter students according to status selected
440: if ($filterlist && (!($stu_status =~ /Any/))) {
441: if (!($stu_status =~ $status)) {
442: delete($classlist->{$student});
443: next;
444: }
445: }
446: # filter students according to groups selected
447: my @stu_groups = split(/,/,$group);
448: if (@getgroup) {
449: my $exclude = 1;
450: foreach my $grp (@getgroup) {
451: foreach my $stu_group (@stu_groups) {
452: if ($stu_group eq $grp) {
453: $exclude = 0;
454: }
455: }
456: if (($grp eq 'none') && !$group) {
457: $exclude = 0;
458: }
459: }
460: if ($exclude) {
461: delete($classlist->{$student});
462: }
463: }
464: $section = ($section ne '' ? $section : 'none');
465: if (&canview($section)) {
466: if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {
467: $sections{$section}++;
468: if ($classlist->{$student}) {
469: $fullnames{$student}=$fullname;
470: }
471: } else {
472: delete($classlist->{$student});
473: }
474: } else {
475: delete($classlist->{$student});
476: }
477: }
478: my %seen = ();
479: my @sections = sort(keys(%sections));
480: return ($classlist,\@sections,\%fullnames);
481: }
482:
483: sub canmodify {
484: my ($sec)=@_;
485: if ($perm{'mgr'}) {
486: if (!defined($perm{'mgr_section'})) {
487: # can modify whole class
488: return 1;
489: } else {
490: if ($sec eq $perm{'mgr_section'}) {
491: #can modify the requested section
492: return 1;
493: } else {
494: # can't modify the request section
495: return 0;
496: }
497: }
498: }
499: #can't modify
500: return 0;
501: }
502:
503: sub canview {
504: my ($sec)=@_;
505: if ($perm{'vgr'}) {
506: if (!defined($perm{'vgr_section'})) {
507: # can modify whole class
508: return 1;
509: } else {
510: if ($sec eq $perm{'vgr_section'}) {
511: #can modify the requested section
512: return 1;
513: } else {
514: # can't modify the request section
515: return 0;
516: }
517: }
518: }
519: #can't modify
520: return 0;
521: }
522:
523: #--- Retrieve the grade status of a student for all the parts
524: sub student_gradeStatus {
525: my ($symb,$udom,$uname,$partlist) = @_;
526: my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
527: my %partstatus = ();
528: foreach (@$partlist) {
529: my ($status,undef) = split(/_/,$record{"resource.$_.solved"},2);
530: $status = 'nothing' if ($status eq '');
531: $partstatus{$_} = $status;
532: my $subkey = "resource.$_.submitted_by";
533: $partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
534: }
535: return %partstatus;
536: }
537:
538: # hidden form and javascript that calls the form
539: # Use by verifyscript and viewgrades
540: # Shows a student's view of problem and submission
541: sub jscriptNform {
542: my ($symb) = @_;
543: my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
544: my $jscript='<script type="text/javascript" language="javascript">'."\n".
545: ' function viewOneStudent(user,domain) {'."\n".
546: ' document.onestudent.student.value = user;'."\n".
547: ' document.onestudent.userdom.value = domain;'."\n".
548: ' document.onestudent.submit();'."\n".
549: ' }'."\n".
550: '</script>'."\n";
551: $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\n".
552: '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
553: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
554: '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n".
555: '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n".
556: '<input type="hidden" name="command" value="submission" />'."\n".
557: '<input type="hidden" name="student" value="" />'."\n".
558: '<input type="hidden" name="userdom" value="" />'."\n".
559: '</form>'."\n";
560: return $jscript;
561: }
562:
563:
564:
565: # Given the score (as a number [0-1] and the weight) what is the final
566: # point value? This function will round to the nearest tenth, third,
567: # or quarter if one of those is within the tolerance of .00001.
568: sub compute_points {
569: my ($score, $weight) = @_;
570:
571: my $tolerance = .00001;
572: my $points = $score * $weight;
573:
574: # Check for nearness to 1/x.
575: my $check_for_nearness = sub {
576: my ($factor) = @_;
577: my $num = ($points * $factor) + $tolerance;
578: my $floored_num = floor($num);
579: if ($num - $floored_num < 2 * $tolerance * $factor) {
580: return $floored_num / $factor;
581: }
582: return $points;
583: };
584:
585: $points = $check_for_nearness->(10);
586: $points = $check_for_nearness->(3);
587: $points = $check_for_nearness->(4);
588:
589: return $points;
590: }
591:
592: #------------------ End of general use routines --------------------
593:
594: #
595: # Find most similar essay
596: #
597:
598: sub most_similar {
599: my ($uname,$udom,$uessay,$old_essays)=@_;
600:
601: # ignore spaces and punctuation
602:
603: $uessay=~s/\W+/ /gs;
604:
605: # ignore empty submissions (occuring when only files are sent)
606:
607: unless ($uessay=~/\w+/) { return ''; }
608:
609: # these will be returned. Do not care if not at least 50 percent similar
610: my $limit=0.6;
611: my $sname='';
612: my $sdom='';
613: my $scrsid='';
614: my $sessay='';
615: # go through all essays ...
616: foreach my $tkey (keys(%$old_essays)) {
617: my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey));
618: # ... except the same student
619: next if (($tname eq $uname) && ($tdom eq $udom));
620: my $tessay=$old_essays->{$tkey};
621: $tessay=~s/\W+/ /gs;
622: # String similarity gives up if not even limit
623: my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit);
624: # Found one
625: if ($tsimilar>$limit) {
626: $limit=$tsimilar;
627: $sname=$tname;
628: $sdom=$tdom;
629: $scrsid=$tcrsid;
630: $sessay=$old_essays->{$tkey};
631: }
632: }
633: if ($limit>0.6) {
634: return ($sname,$sdom,$scrsid,$sessay,$limit);
635: } else {
636: return ('','','','',0);
637: }
638: }
639:
640: #-------------------------------------------------------------------
641:
642: #------------------------------------ Receipt Verification Routines
643: #
644: #--- Check whether a receipt number is valid.---
645: sub verifyreceipt {
646: my $request = shift;
647:
648: my $courseid = $env{'request.course.id'};
649: my $receipt = &Apache::lonnet::recprefix($courseid).'-'.
650: $env{'form.receipt'};
651: $receipt =~ s/[^\-\d]//g;
652: my ($symb) = &get_symb($request);
653:
654: my $title.=
655: '<h3><span class="LC_info">'.
656: &mt('Verifying Submission Receipt [_1]',$receipt).
657: '</span></h3>'."\n".
658: '<h4>'.&mt('<b>Resource: </b>[_1]',$env{'form.probTitle'}).
659: '</h4>'."\n";
660:
661: my ($string,$contents,$matches) = ('','',0);
662: my (undef,undef,$fullname) = &getclasslist('all','0');
663:
664: my $receiptparts=0;
665: if ($env{"course.$courseid.receiptalg"} eq 'receipt2' ||
666: $env{"course.$courseid.receiptalg"} eq 'receipt3') { $receiptparts=1; }
667: my $parts=['0'];
668: if ($receiptparts) { ($parts)=&response_type($symb); }
669:
670: my $header =
671: &Apache::loncommon::start_data_table().
672: &Apache::loncommon::start_data_table_header_row().
673: '<th> '.&mt('Fullname').' </th>'."\n".
674: '<th> '.&mt('Username').' </th>'."\n".
675: '<th> '.&mt('Domain').' </th>';
676: if ($receiptparts) {
677: $header.='<th> '.&mt('Problem Part').' </th>';
678: }
679: $header.=
680: &Apache::loncommon::end_data_table_header_row();
681:
682: foreach (sort
683: {
684: if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
685: return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
686: }
687: return $a cmp $b;
688: } (keys(%$fullname))) {
689: my ($uname,$udom)=split(/\:/);
690: foreach my $part (@$parts) {
691: if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) {
692: $contents.=
693: &Apache::loncommon::start_data_table_row().
694: '<td> '."\n".
695: '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
696: '\');" target="_self">'.$$fullname{$_}.'</a> </td>'."\n".
697: '<td> '.$uname.' </td>'.
698: '<td> '.$udom.' </td>';
699: if ($receiptparts) {
700: $contents.='<td> '.$part.' </td>';
701: }
702: $contents.=
703: &Apache::loncommon::end_data_table_row()."\n";
704:
705: $matches++;
706: }
707: }
708: }
709: if ($matches == 0) {
710: $string = $title.&mt('No match found for the above receipt.');
711: } else {
712: $string = &jscriptNform($symb).$title.
713: '<p>'.
714: &mt('The above receipt matches the following [numerate,_1,student].',$matches).
715: '</p>'.
716: $header.
717: $contents.
718: &Apache::loncommon::end_data_table()."\n";
719: }
720: return $string.&show_grading_menu_form($symb);
721: }
722:
723: #--- This is called by a number of programs.
724: #--- Called from the Grading Menu - View/Grade an individual student
725: #--- Also called directly when one clicks on the subm button
726: # on the problem page.
727: sub listStudents {
728: my ($request) = shift;
729:
730: my ($symb) = &get_symb($request);
731: my $cdom = $env{"course.$env{'request.course.id'}.domain"};
732: my $cnum = $env{"course.$env{'request.course.id'}.num"};
733: my $getsec = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
734: my $getgroup = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
735: my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
736: my $viewgrade = $env{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View';
737: $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ?
738: &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
739:
740: my $result='<h3><span class="LC_info"> '.
741: &mt($viewgrade.' Submissions for a Student or a Group of Students')
742: .'</span></h3>';
743:
744: my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($symb,$env{'form.probTitle'},($env{'form.showgrading'} eq 'yes'));
745:
746: my %lt = ( 'multiple' =>
747: "Please select a student or group of students before clicking on the Next button.",
748: 'single' =>
749: "Please select the student before clicking on the Next button.",
750: );
751: %lt = &Apache::lonlocal::texthash(%lt);
752: $request->print(<<LISTJAVASCRIPT);
753: <script type="text/javascript" language="javascript">
754: function checkSelect(checkBox) {
755: var ctr=0;
756: var sense="";
757: if (checkBox.length > 1) {
758: for (var i=0; i<checkBox.length; i++) {
759: if (checkBox[i].checked) {
760: ctr++;
761: }
762: }
763: sense = '$lt{'multiple'}';
764: } else {
765: if (checkBox.checked) {
766: ctr = 1;
767: }
768: sense = '$lt{'single'}';
769: }
770: if (ctr == 0) {
771: alert(sense);
772: return false;
773: }
774: document.gradesub.submit();
775: }
776:
777: function reLoadList(formname) {
778: if (formname.saveStatusOld.value == pullDownSelection(formname.Status)) {return;}
779: formname.command.value = 'submission';
780: formname.submit();
781: }
782: </script>
783: LISTJAVASCRIPT
784:
785: &commonJSfunctions($request);
786: $request->print($result);
787:
788: my $checkhdgrade = ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1 ) ? 'checked="checked"' : '';
789: my $checklastsub = $checkhdgrade eq '' ? 'checked="checked"' : '';
790: my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.
791: "\n".$table;
792:
793: $gradeTable .=
794: ' '.
795: &mt('<b>View Problem Text: </b>[_1]',
796: '<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n".
797: '<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n".
798: '<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label>').'<br />'."\n";
799: $gradeTable .=
800: ' '.
801: &mt('<b>View Answer: </b>[_1]',
802: '<label><input type="radio" name="vAns" value="no" /> '.&mt('no').' </label>'."\n".
803: '<label><input type="radio" name="vAns" value="yes" /> '.&mt('one student').' </label>'."\n".
804: '<label><input type="radio" name="vAns" value="all" checked="checked" /> '.&mt('all students').' </label>').'<br />'."\n";
805:
806: my $submission_options;
807: if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {
808: $submission_options.=
809: '<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> '.&mt('essay part only').' </label>'."\n";
810: }
811: my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
812: my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status;
813: $env{'form.Status'} = $saveStatus;
814: $submission_options.=
815: '<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> '.&mt('last submission only').' </label>'."\n".
816: '<label><input type="radio" name="lastSub" value="last" /> '.&mt('last submission & parts info').' </label>'."\n".
817: '<label><input type="radio" name="lastSub" value="datesub" /> '.&mt('by dates and submissions').' </label>'."\n".
818: '<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').'</label>';
819: $gradeTable .=
820: ' '.
821: &mt('<b>Submissions: </b>[_1]',$submission_options).'<br />'."\n";
822:
823: $gradeTable .=
824: ' '.
825: &mt('<b>Grading Increments:</b> [_1]',
826: '<select name="increment">'.
827: '<option value="1">'.&mt('Whole Points').'</option>'.
828: '<option value=".5">'.&mt('Half Points').'</option>'.
829: '<option value=".25">'.&mt('Quarter Points').'</option>'.
830: '<option value=".1">'.&mt('Tenths of a Point').'</option>'.
831: '</select>');
832:
833: $gradeTable .=
834: &build_section_inputs().
835: '<input type="hidden" name="submitonly" value="'.$submitonly.'" />'."\n".
836: '<input type="hidden" name="handgrade" value="'.$env{'form.handgrade'}.'" /><br />'."\n".
837: '<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" /><br />'."\n".
838: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
839: '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n".
840: '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
841: '<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";
842:
843: if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) {
844: $gradeTable.='<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n";
845: } else {
846: $gradeTable.=&mt('<b>Student Status:</b> [_1]',
847: &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);')).'<br />';
848: }
849:
850: $gradeTable.=&mt('To '.lc($viewgrade).' a submission or a group of submissions, click on the check box(es) '.
851: 'next to the student\'s name(s). Then click on the Next button.').'<br />'."\n".
852: '<input type="hidden" name="command" value="processGroup" />'."\n";
853:
854: # checkall buttons
855: $gradeTable.=&check_script('gradesub', 'stuinfo');
856: $gradeTable.='<input type="button" '."\n".
857: 'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n".
858: 'value="'.&mt('Next->').'" /> <br />'."\n";
859: $gradeTable.=&check_buttons();
860: $gradeTable.='<label><input type="checkbox" name="checkPlag" checked="checked" />'.&mt('Check For Plagiarism').'</label>';
861: my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup);
862: $gradeTable.= &Apache::loncommon::start_data_table().
863: &Apache::loncommon::start_data_table_header_row();
864: my $loop = 0;
865: while ($loop < 2) {
866: $gradeTable.='<th>'.&mt('No.').'</th><th>'.&mt('Select').'</th>'.
867: '<th>'.&nameUserString('header').' '.&mt('Section/Group').'</th>';
868: if ($env{'form.showgrading'} eq 'yes'
869: && $submitonly ne 'queued'
870: && $submitonly ne 'all') {
871: foreach my $part (sort(@$partlist)) {
872: my $display_part=
873: &get_display_part((split(/_/,$part))[0],$symb);
874: $gradeTable.=
875: '<th>'.&mt('Part: [_1] Status',$display_part).'</th>';
876: }
877: } elsif ($submitonly eq 'queued') {
878: $gradeTable.='<th>'.&mt('Queue Status').' </th>';
879: }
880: $loop++;
881: # $gradeTable.='<td></td>' if ($loop%2 ==1);
882: }
883: $gradeTable.=&Apache::loncommon::end_data_table_header_row()."\n";
884:
885: my $ctr = 0;
886: foreach my $student (sort
887: {
888: if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
889: return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
890: }
891: return $a cmp $b;
892: }
893: (keys(%$fullname))) {
894: my ($uname,$udom) = split(/:/,$student);
895:
896: my %status = ();
897:
898: if ($submitonly eq 'queued') {
899: my %queue_status =
900: &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
901: $udom,$uname);
902: next if (!defined($queue_status{'gradingqueue'}));
903: $status{'gradingqueue'} = $queue_status{'gradingqueue'};
904: }
905:
906: if ($env{'form.showgrading'} eq 'yes'
907: && $submitonly ne 'queued'
908: && $submitonly ne 'all') {
909: (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist);
910: my $submitted = 0;
911: my $graded = 0;
912: my $incorrect = 0;
913: foreach (keys(%status)) {
914: $submitted = 1 if ($status{$_} ne 'nothing');
915: $graded = 1 if ($status{$_} =~ /^ungraded/);
916: $incorrect = 1 if ($status{$_} =~ /^incorrect/);
917:
918: my ($foo,$partid,$foo1) = split(/\./,$_);
919: if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
920: $submitted = 0;
921: my ($part)=split(/\./,$partid);
922: $gradeTable.='<input type="hidden" name="'.
923: $student.':'.$part.':submitted_by" value="'.
924: $status{'resource.'.$partid.'.submitted_by'}.'" />';
925: }
926: }
927:
928: next if (!$submitted && ($submitonly eq 'yes' ||
929: $submitonly eq 'incorrect' ||
930: $submitonly eq 'graded'));
931: next if (!$graded && ($submitonly eq 'graded'));
932: next if (!$incorrect && $submitonly eq 'incorrect');
933: }
934:
935: $ctr++;
936: my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
937: my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
938: if ( $perm{'vgr'} eq 'F' ) {
939: if ($ctr%2 ==1) {
940: $gradeTable.= &Apache::loncommon::start_data_table_row();
941: }
942: $gradeTable.='<td align="right">'.$ctr.' </td>'.
943: '<td align="center"><label><input type=checkbox name="stuinfo" value="'.
944: $student.':'.$$fullname{$student}.':::SECTION'.$section.
945: ') " /> </label></td>'."\n".'<td>'.
946: &nameUserString(undef,$$fullname{$student},$uname,$udom).
947: ' '.$section.($group ne '' ?'/'.$group:'').'</td>'."\n";
948:
949: if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {
950: foreach (sort keys(%status)) {
951: next if ($_ =~ /^resource.*?submitted_by$/);
952: $gradeTable.='<td align="center"> '.&mt($status{$_}).' </td>'."\n";
953: }
954: }
955: # $gradeTable.='<td></td>' if ($ctr%2 ==1);
956: if ($ctr%2 ==0) {
957: $gradeTable.=&Apache::loncommon::end_data_table_row()."\n";
958: }
959: }
960: }
961: if ($ctr%2 ==1) {
962: $gradeTable.='<td> </td><td> </td><td> </td>';
963: if ($env{'form.showgrading'} eq 'yes'
964: && $submitonly ne 'queued'
965: && $submitonly ne 'all') {
966: foreach (@$partlist) {
967: $gradeTable.='<td> </td>';
968: }
969: } elsif ($submitonly eq 'queued') {
970: $gradeTable.='<td> </td>';
971: }
972: $gradeTable.=&Apache::loncommon::end_data_table_row();
973: }
974:
975: $gradeTable.=&Apache::loncommon::end_data_table()."\n".
976: '<input type="button" '.
977: 'onClick="javascript:checkSelect(this.form.stuinfo);" '.
978: 'value="'.&mt('Next->').'" /></form>'."\n";
979: if ($ctr == 0) {
980: my $num_students=(scalar(keys(%$fullname)));
981: if ($num_students eq 0) {
982: $gradeTable='<br /> <span class="LC_warning">'.&mt('There are no students currently enrolled.').'</span>';
983: } else {
984: my $submissions='submissions';
985: if ($submitonly eq 'incorrect') { $submissions = 'incorrect submissions'; }
986: if ($submitonly eq 'graded' ) { $submissions = 'ungraded submissions'; }
987: if ($submitonly eq 'queued' ) { $submissions = 'queued submissions'; }
988: $gradeTable='<br /> <span class="LC_warning">'.
989: &mt('No '.$submissions.' found for this resource for any students. ([_1] students checked for '.$submissions.')',
990: $num_students).
991: '</span><br />';
992: }
993: } elsif ($ctr == 1) {
994: $gradeTable =~ s/type="checkbox"/type="checkbox" checked="checked"/;
995: }
996: $gradeTable.=&show_grading_menu_form($symb);
997: $request->print($gradeTable);
998: return '';
999: }
1000:
1001: #---- Called from the listStudents routine
1002:
1003: sub check_script {
1004: my ($form, $type)=@_;
1005: my $chkallscript='<script type="text/javascript">
1006: function checkall() {
1007: for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
1008: ele = document.forms.'.$form.'.elements[i];
1009: if (ele.name == "'.$type.'") {
1010: document.forms.'.$form.'.elements[i].checked=true;
1011: }
1012: }
1013: }
1014:
1015: function checksec() {
1016: for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
1017: ele = document.forms.'.$form.'.elements[i];
1018: string = document.forms.'.$form.'.chksec.value;
1019: if
1020: (ele.value.indexOf(":::SECTION"+string)>0) {
1021: document.forms.'.$form.'.elements[i].checked=true;
1022: }
1023: }
1024: }
1025:
1026:
1027: function uncheckall() {
1028: for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
1029: ele = document.forms.'.$form.'.elements[i];
1030: if (ele.name == "'.$type.'") {
1031: document.forms.'.$form.'.elements[i].checked=false;
1032: }
1033: }
1034: }
1035:
1036: </script>'."\n";
1037: return $chkallscript;
1038: }
1039:
1040: sub check_buttons {
1041: my $buttons.='<input type="button" onclick="checkall()" value="'.&mt('Check All').'" />';
1042: $buttons.='<input type="button" onclick="uncheckall()" value="'.&mt('Uncheck All').'" /> ';
1043: $buttons.='<input type="button" onclick="checksec()" value="'.&mt('Check Section/Group').'" />';
1044: $buttons.='<input type="text" size="5" name="chksec" /> ';
1045: return $buttons;
1046: }
1047:
1048: # Displays the submissions for one student or a group of students
1049: sub processGroup {
1050: my ($request) = shift;
1051: my $ctr = 0;
1052: my @stuchecked = &Apache::loncommon::get_env_multiple('form.stuinfo');
1053: my $total = scalar(@stuchecked)-1;
1054:
1055: foreach my $student (@stuchecked) {
1056: my ($uname,$udom,$fullname) = split(/:/,$student);
1057: $env{'form.student'} = $uname;
1058: $env{'form.userdom'} = $udom;
1059: $env{'form.fullname'} = $fullname;
1060: &submission($request,$ctr,$total);
1061: $ctr++;
1062: }
1063: return '';
1064: }
1065:
1066: #------------------------------------------------------------------------------------
1067: #
1068: #-------------------------- Next few routines handles grading by student, essentially
1069: # handles essay response type problem/part
1070: #
1071: #--- Javascript to handle the submission page functionality ---
1072: sub sub_page_js {
1073: my $request = shift;
1074: $request->print(<<SUBJAVASCRIPT);
1075: <script type="text/javascript" language="javascript">
1076: function updateRadio(formname,id,weight) {
1077: var gradeBox = formname["GD_BOX"+id];
1078: var radioButton = formname["RADVAL"+id];
1079: var oldpts = formname["oldpts"+id].value;
1080: var pts = checkSolved(formname,id) == 'update' ? gradeBox.value : oldpts;
1081: gradeBox.value = pts;
1082: var resetbox = false;
1083: if (isNaN(pts) || pts < 0) {
1084: alert("A number equal or greater than 0 is expected. Entered value = "+pts);
1085: for (var i=0; i<radioButton.length; i++) {
1086: if (radioButton[i].checked) {
1087: gradeBox.value = i;
1088: resetbox = true;
1089: }
1090: }
1091: if (!resetbox) {
1092: formtextbox.value = "";
1093: }
1094: return;
1095: }
1096:
1097: if (pts > weight) {
1098: var resp = confirm("You entered a value ("+pts+
1099: ") greater than the weight for the part. Accept?");
1100: if (resp == false) {
1101: gradeBox.value = oldpts;
1102: return;
1103: }
1104: }
1105:
1106: for (var i=0; i<radioButton.length; i++) {
1107: radioButton[i].checked=false;
1108: if (pts == i && pts != "") {
1109: radioButton[i].checked=true;
1110: }
1111: }
1112: updateSelect(formname,id);
1113: formname["stores"+id].value = "0";
1114: }
1115:
1116: function writeBox(formname,id,pts) {
1117: var gradeBox = formname["GD_BOX"+id];
1118: if (checkSolved(formname,id) == 'update') {
1119: gradeBox.value = pts;
1120: } else {
1121: var oldpts = formname["oldpts"+id].value;
1122: gradeBox.value = oldpts;
1123: var radioButton = formname["RADVAL"+id];
1124: for (var i=0; i<radioButton.length; i++) {
1125: radioButton[i].checked=false;
1126: if (i == oldpts) {
1127: radioButton[i].checked=true;
1128: }
1129: }
1130: }
1131: formname["stores"+id].value = "0";
1132: updateSelect(formname,id);
1133: return;
1134: }
1135:
1136: function clearRadBox(formname,id) {
1137: if (checkSolved(formname,id) == 'noupdate') {
1138: updateSelect(formname,id);
1139: return;
1140: }
1141: gradeSelect = formname["GD_SEL"+id];
1142: for (var i=0; i<gradeSelect.length; i++) {
1143: if (gradeSelect[i].selected) {
1144: var selectx=i;
1145: }
1146: }
1147: var stores = formname["stores"+id];
1148: if (selectx == stores.value) { return };
1149: var gradeBox = formname["GD_BOX"+id];
1150: gradeBox.value = "";
1151: var radioButton = formname["RADVAL"+id];
1152: for (var i=0; i<radioButton.length; i++) {
1153: radioButton[i].checked=false;
1154: }
1155: stores.value = selectx;
1156: }
1157:
1158: function checkSolved(formname,id) {
1159: if (formname["solved"+id].value == "correct_by_student" && formname.overRideScore.value == 'no') {
1160: var reply = confirm("This problem has been graded correct by the computer. Do you want to change the score?");
1161: if (!reply) {return "noupdate";}
1162: formname.overRideScore.value = 'yes';
1163: }
1164: return "update";
1165: }
1166:
1167: function updateSelect(formname,id) {
1168: formname["GD_SEL"+id][0].selected = true;
1169: return;
1170: }
1171:
1172: //=========== Check that a point is assigned for all the parts ============
1173: function checksubmit(formname,val,total,parttot) {
1174: formname.gradeOpt.value = val;
1175: if (val == "Save & Next") {
1176: for (i=0;i<=total;i++) {
1177: for (j=0;j<parttot;j++) {
1178: var partid = formname["partid"+i+"_"+j].value;
1179: if (formname["GD_SEL"+i+"_"+partid][0].selected) {
1180: var points = formname["GD_BOX"+i+"_"+partid].value;
1181: if (points == "") {
1182: var name = formname["name"+i].value;
1183: var studentID = (name != '' ? name : formname["unamedom"+i].value);
1184: var resp = confirm("You did not assign a score for "+studentID+
1185: ", part "+partid+". Continue?");
1186: if (resp == false) {
1187: formname["GD_BOX"+i+"_"+partid].focus();
1188: return false;
1189: }
1190: }
1191: }
1192:
1193: }
1194: }
1195:
1196: }
1197: if (val == "Grade Student") {
1198: formname.showgrading.value = "yes";
1199: if (formname.Status.value == "") {
1200: formname.Status.value = "Active";
1201: }
1202: formname.studentNo.value = total;
1203: }
1204: formname.submit();
1205: }
1206:
1207: //======= Check that a score is assigned for all the problems (page/sequence grading only) =========
1208: function checkSubmitPage(formname,total) {
1209: noscore = new Array(100);
1210: var ptr = 0;
1211: for (i=1;i<total;i++) {
1212: var partid = formname["q_"+i].value;
1213: if (formname["GD_SEL"+i+"_"+partid][0].selected) {
1214: var points = formname["GD_BOX"+i+"_"+partid].value;
1215: var status = formname["solved"+i+"_"+partid].value;
1216: if (points == "" && status != "correct_by_student") {
1217: noscore[ptr] = i;
1218: ptr++;
1219: }
1220: }
1221: }
1222: if (ptr != 0) {
1223: var sense = ptr == 1 ? ": " : "s: ";
1224: var prolist = "";
1225: if (ptr == 1) {
1226: prolist = noscore[0];
1227: } else {
1228: var i = 0;
1229: while (i < ptr-1) {
1230: prolist += noscore[i]+", ";
1231: i++;
1232: }
1233: prolist += "and "+noscore[i];
1234: }
1235: var resp = confirm("You did not assign any score for the following problem"+sense+prolist+". Continue?");
1236: if (resp == false) {
1237: return false;
1238: }
1239: }
1240:
1241: formname.submit();
1242: }
1243: </script>
1244: SUBJAVASCRIPT
1245: }
1246:
1247: #--- javascript for essay type problem --
1248: sub sub_page_kw_js {
1249: my $request = shift;
1250: my $iconpath = $request->dir_config('lonIconsURL');
1251: &commonJSfunctions($request);
1252:
1253: my $inner_js_msg_central=<<INNERJS;
1254: <script text="text/javascript">
1255: function checkInput() {
1256: opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value);
1257: var nmsg = opener.document.SCORE.savemsgN.value;
1258: var usrctr = document.msgcenter.usrctr.value;
1259: var newval = opener.document.SCORE["newmsg"+usrctr];
1260: newval.value = opener.checkEntities(document.msgcenter.newmsg.value);
1261:
1262: var msgchk = "";
1263: if (document.msgcenter.subchk.checked) {
1264: msgchk = "msgsub,";
1265: }
1266: var includemsg = 0;
1267: for (var i=1; i<=nmsg; i++) {
1268: var opnmsg = opener.document.SCORE["savemsg"+i];
1269: var frmmsg = document.msgcenter["msg"+i];
1270: opnmsg.value = opener.checkEntities(frmmsg.value);
1271: var showflg = opener.document.SCORE["shownOnce"+i];
1272: showflg.value = "1";
1273: var chkbox = document.msgcenter["msgn"+i];
1274: if (chkbox.checked) {
1275: msgchk += "savemsg"+i+",";
1276: includemsg = 1;
1277: }
1278: }
1279: if (document.msgcenter.newmsgchk.checked) {
1280: msgchk += "newmsg"+usrctr;
1281: includemsg = 1;
1282: }
1283: imgformname = opener.document.SCORE["mailicon"+usrctr];
1284: imgformname.src = "$iconpath/"+((includemsg) ? "mailto.gif" : "mailbkgrd.gif");
1285: var includemsg = opener.document.SCORE["includemsg"+usrctr];
1286: includemsg.value = msgchk;
1287:
1288: self.close()
1289:
1290: }
1291: </script>
1292: INNERJS
1293:
1294: my $inner_js_highlight_central=<<INNERJS;
1295: <script type="text/javascript">
1296: function updateChoice(flag) {
1297: opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr);
1298: opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize);
1299: opener.document.SCORE.kwstyle.value = opener.radioSelection(document.hlCenter.kwdstyle);
1300: opener.document.SCORE.refresh.value = "on";
1301: if (opener.document.SCORE.keywords.value!=""){
1302: opener.document.SCORE.submit();
1303: }
1304: self.close()
1305: }
1306: </script>
1307: INNERJS
1308:
1309: my $start_page_msg_central =
1310: &Apache::loncommon::start_page('Message Central',$inner_js_msg_central,
1311: {'js_ready' => 1,
1312: 'only_body' => 1,
1313: 'bgcolor' =>'#FFFFFF',});
1314: my $end_page_msg_central =
1315: &Apache::loncommon::end_page({'js_ready' => 1});
1316:
1317:
1318: my $start_page_highlight_central =
1319: &Apache::loncommon::start_page('Highlight Central',
1320: $inner_js_highlight_central,
1321: {'js_ready' => 1,
1322: 'only_body' => 1,
1323: 'bgcolor' =>'#FFFFFF',});
1324: my $end_page_highlight_central =
1325: &Apache::loncommon::end_page({'js_ready' => 1});
1326:
1327: my $docopen=&Apache::lonhtmlcommon::javascript_docopen();
1328: $docopen=~s/^document\.//;
1329: $request->print(<<SUBJAVASCRIPT);
1330: <script type="text/javascript" language="javascript">
1331:
1332: //===================== Show list of keywords ====================
1333: function keywords(formname) {
1334: var nret = prompt("Keywords list, separated by a space. Add/delete to list if desired.",formname.keywords.value);
1335: if (nret==null) return;
1336: formname.keywords.value = nret;
1337:
1338: if (formname.keywords.value != "") {
1339: formname.refresh.value = "on";
1340: formname.submit();
1341: }
1342: return;
1343: }
1344:
1345: //===================== Script to view submitted by ==================
1346: function viewSubmitter(submitter) {
1347: document.SCORE.refresh.value = "on";
1348: document.SCORE.NCT.value = "1";
1349: document.SCORE.unamedom0.value = submitter;
1350: document.SCORE.submit();
1351: return;
1352: }
1353:
1354: //===================== Script to add keyword(s) ==================
1355: function getSel() {
1356: if (document.getSelection) txt = document.getSelection();
1357: else if (document.selection) txt = document.selection.createRange().text;
1358: else return;
1359: var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");
1360: if (cleantxt=="") {
1361: alert("Please select a word or group of words from document and then click this link.");
1362: return;
1363: }
1364: var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt);
1365: if (nret==null) return;
1366: document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret;
1367: if (document.SCORE.keywords.value != "") {
1368: document.SCORE.refresh.value = "on";
1369: document.SCORE.submit();
1370: }
1371: return;
1372: }
1373:
1374: //====================== Script for composing message ==============
1375: // preload images
1376: img1 = new Image();
1377: img1.src = "$iconpath/mailbkgrd.gif";
1378: img2 = new Image();
1379: img2.src = "$iconpath/mailto.gif";
1380:
1381: function msgCenter(msgform,usrctr,fullname) {
1382: var Nmsg = msgform.savemsgN.value;
1383: savedMsgHeader(Nmsg,usrctr,fullname);
1384: var subject = msgform.msgsub.value;
1385: var msgchk = document.SCORE["includemsg"+usrctr].value;
1386: re = /msgsub/;
1387: var shwsel = "";
1388: if (re.test(msgchk)) { shwsel = "checked" }
1389: subject = (document.SCORE.shownSub.value == 0 ? checkEntities(subject) : subject);
1390: displaySubject(checkEntities(subject),shwsel);
1391: for (var i=1; i<=Nmsg; i++) {
1392: var testmsg = "savemsg"+i+",";
1393: re = new RegExp(testmsg,"g");
1394: shwsel = "";
1395: if (re.test(msgchk)) { shwsel = "checked" }
1396: var message = document.SCORE["savemsg"+i].value;
1397: message = (document.SCORE["shownOnce"+i].value == 0 ? checkEntities(message) : message);
1398: displaySavedMsg(i,message,shwsel); //I do not get it. w/o checkEntities on saved messages,
1399: //any < is already converted to <, etc. However, only once!!
1400: }
1401: newmsg = document.SCORE["newmsg"+usrctr].value;
1402: shwsel = "";
1403: re = /newmsg/;
1404: if (re.test(msgchk)) { shwsel = "checked" }
1405: newMsg(newmsg,shwsel);
1406: msgTail();
1407: return;
1408: }
1409:
1410: function checkEntities(strx) {
1411: if (strx.length == 0) return strx;
1412: var orgStr = ["&", "<", ">", '"'];
1413: var newStr = ["&", "<", ">", """];
1414: var counter = 0;
1415: while (counter < 4) {
1416: strx = strReplace(strx,orgStr[counter],newStr[counter]);
1417: counter++;
1418: }
1419: return strx;
1420: }
1421:
1422: function strReplace(strx, orgStr, newStr) {
1423: return strx.split(orgStr).join(newStr);
1424: }
1425:
1426: function savedMsgHeader(Nmsg,usrctr,fullname) {
1427: var height = 70*Nmsg+250;
1428: var scrollbar = "no";
1429: if (height > 600) {
1430: height = 600;
1431: scrollbar = "yes";
1432: }
1433: var xpos = (screen.width-600)/2;
1434: xpos = (xpos < 0) ? '0' : xpos;
1435: var ypos = (screen.height-height)/2-30;
1436: ypos = (ypos < 0) ? '0' : ypos;
1437:
1438: pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars='+scrollbar+',screenx='+xpos+',screeny='+ypos+',width=600,height='+height);
1439: pWin.focus();
1440: pDoc = pWin.document;
1441: pDoc.$docopen;
1442: pDoc.write('$start_page_msg_central');
1443:
1444: pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");
1445: pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");
1446: pDoc.write("<h3><span class=\\"LC_info\\"> Compose Message for \"+fullname+\"<\\/span><\\/h3><br /><br />");
1447:
1448: pDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
1449: pDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
1450: pDoc.write("<td><b>Type<\\/b><\\/td><td><b>Include<\\/b><\\/td><td><b>Message<\\/td><\\/tr>");
1451: }
1452: function displaySubject(msg,shwsel) {
1453: pDoc = pWin.document;
1454: pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
1455: pDoc.write("<td>Subject<\\/td>");
1456: pDoc.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
1457: pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"60\\" maxlength=\\"80\\"><\\/td><\\/tr>");
1458: }
1459:
1460: function displaySavedMsg(ctr,msg,shwsel) {
1461: pDoc = pWin.document;
1462: pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
1463: pDoc.write("<td align=\\"center\\">"+ctr+"<\\/td>");
1464: pDoc.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
1465: pDoc.write("<td><textarea name=\\"msg"+ctr+"\\" cols=\\"60\\" rows=\\"3\\">"+msg+"<\\/textarea><\\/td><\\/tr>");
1466: }
1467:
1468: function newMsg(newmsg,shwsel) {
1469: pDoc = pWin.document;
1470: pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
1471: pDoc.write("<td align=\\"center\\">New<\\/td>");
1472: pDoc.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
1473: pDoc.write("<td><textarea name=\\"newmsg\\" cols=\\"60\\" rows=\\"3\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" >"+newmsg+"<\\/textarea><\\/td><\\/tr>");
1474: }
1475:
1476: function msgTail() {
1477: pDoc = pWin.document;
1478: pDoc.write("<\\/table>");
1479: pDoc.write("<\\/td><\\/tr><\\/table> ");
1480: pDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:checkInput()\\"> ");
1481: pDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");
1482: pDoc.write("<\\/form>");
1483: pDoc.write('$end_page_msg_central');
1484: pDoc.close();
1485: }
1486:
1487: //====================== Script for keyword highlight options ==============
1488: function kwhighlight() {
1489: var kwclr = document.SCORE.kwclr.value;
1490: var kwsize = document.SCORE.kwsize.value;
1491: var kwstyle = document.SCORE.kwstyle.value;
1492: var redsel = "";
1493: var grnsel = "";
1494: var blusel = "";
1495: if (kwclr=="red") {var redsel="checked"};
1496: if (kwclr=="green") {var grnsel="checked"};
1497: if (kwclr=="blue") {var blusel="checked"};
1498: var sznsel = "";
1499: var sz1sel = "";
1500: var sz2sel = "";
1501: if (kwsize=="0") {var sznsel="checked"};
1502: if (kwsize=="+1") {var sz1sel="checked"};
1503: if (kwsize=="+2") {var sz2sel="checked"};
1504: var synsel = "";
1505: var syisel = "";
1506: var sybsel = "";
1507: if (kwstyle=="") {var synsel="checked"};
1508: if (kwstyle=="<i>") {var syisel="checked"};
1509: if (kwstyle=="<b>") {var sybsel="checked"};
1510: highlightCentral();
1511: highlightbody('red','red',redsel,'0','normal',sznsel,'','normal',synsel);
1512: highlightbody('green','green',grnsel,'+1','+1',sz1sel,'<i>','italic',syisel);
1513: highlightbody('blue','blue',blusel,'+2','+2',sz2sel,'<b>','bold',sybsel);
1514: highlightend();
1515: return;
1516: }
1517:
1518: function highlightCentral() {
1519: // if (window.hwdWin) window.hwdWin.close();
1520: var xpos = (screen.width-400)/2;
1521: xpos = (xpos < 0) ? '0' : xpos;
1522: var ypos = (screen.height-330)/2-30;
1523: ypos = (ypos < 0) ? '0' : ypos;
1524:
1525: hwdWin = window.open('', 'KeywordHighlightCentral', 'resizeable=yes,toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx='+xpos+',screeny='+ypos);
1526: hwdWin.focus();
1527: var hDoc = hwdWin.document;
1528: hDoc.$docopen;
1529: hDoc.write('$start_page_highlight_central');
1530: hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");
1531: hDoc.write("<h3><span class=\\"LC_info\\"> Keyword Highlight Options<\\/span><\\/h3><br /><br />");
1532:
1533: hDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">");
1534: hDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">");
1535: hDoc.write("<td><b>Text Color<\\/b><\\/td><td><b>Font Size<\\/b><\\/td><td><b>Font Style<\\/td><\\/tr>");
1536: }
1537:
1538: function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) {
1539: var hDoc = hwdWin.document;
1540: hDoc.write("<tr bgcolor=\\"#ffffdd\\">");
1541: hDoc.write("<td align=\\"left\\">");
1542: hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+"> "+clrtxt+"<\\/td>");
1543: hDoc.write("<td align=\\"left\\">");
1544: hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+"> "+sztxt+"<\\/td>");
1545: hDoc.write("<td align=\\"left\\">");
1546: hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+"> "+sytxt+"<\\/td>");
1547: hDoc.write("<\\/tr>");
1548: }
1549:
1550: function highlightend() {
1551: var hDoc = hwdWin.document;
1552: hDoc.write("<\\/table>");
1553: hDoc.write("<\\/td><\\/tr><\\/table> ");
1554: hDoc.write("<input type=\\"button\\" value=\\"Save\\" onClick=\\"javascript:updateChoice(1)\\"> ");
1555: hDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br /><br />");
1556: hDoc.write("<\\/form>");
1557: hDoc.write('$end_page_highlight_central');
1558: hDoc.close();
1559: }
1560:
1561: </script>
1562: SUBJAVASCRIPT
1563: }
1564:
1565: sub get_increment {
1566: my $increment = $env{'form.increment'};
1567: if ($increment != 1 && $increment != .5 && $increment != .25 &&
1568: $increment != .1) {
1569: $increment = 1;
1570: }
1571: return $increment;
1572: }
1573:
1574: #--- displays the grading box, used in essay type problem and grading by page/sequence
1575: sub gradeBox {
1576: my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;
1577: my $checkIcon = '<img alt="'.&mt('Check Mark').
1578: '" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
1579: my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);
1580: my $wgtmsg = ($wgt > 0) ? &mt('(problem weight)')
1581: : '<span class="LC_info">'.&mt('problem weight assigned by computer').'</span>';
1582: $wgt = ($wgt > 0 ? $wgt : '1');
1583: my $score = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
1584: '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));
1585: my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
1586: my $display_part= &get_display_part($partid,$symb);
1587: my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
1588: [$partid]);
1589: my $aggtries = $$record{'resource.'.$partid.'.tries'};
1590: if ($last_resets{$partid}) {
1591: $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
1592: }
1593: $result.='<table border="0"><tr>';
1594: my $ctr = 0;
1595: my $thisweight = 0;
1596: my $increment = &get_increment();
1597:
1598: my $radio.='<table border="0"><tr>'."\n"; # display radio buttons in a nice table 10 across
1599: while ($thisweight<=$wgt) {
1600: $radio.= '<td><span style="white-space: nowrap;"><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
1601: 'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.
1602: $thisweight.')" value="'.$thisweight.'" '.
1603: ($score eq $thisweight ? 'checked="checked"':'').' /> '.$thisweight."</label></span></td>\n";
1604: $radio.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
1605: $thisweight += $increment;
1606: $ctr++;
1607: }
1608: $radio.='</tr></table>';
1609:
1610: my $line.='<input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
1611: ($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
1612: 'onChange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','.
1613: $wgt.')" /></td>'."\n";
1614: $line.='<td>/'.$wgt.' '.$wgtmsg.
1615: ($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? ' '.$checkIcon : '').
1616: ' </td><td>'."\n";
1617: $line.='<select name="GD_SEL'.$counter.'_'.$partid.'" '.
1618: 'onChange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";
1619: if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
1620: $line.='<option></option>'.
1621: '<option value="excused" selected="selected">'.&mt('excused').'</option>';
1622: } else {
1623: $line.='<option selected="selected"></option>'.
1624: '<option value="excused" >'.&mt('excused').'</option>';
1625: }
1626: $line.='<option value="reset status">'.&mt('reset status').'</option></select>'."\n";
1627:
1628:
1629: $result .=
1630: &mt('<td><b>Part:</b></td><td>[_1]</td><td><b>Points:</b></td><td>[_2]</td><td>or</td><td>[_3]</td>',$display_part,$radio,$line);
1631:
1632:
1633: $result.='</tr></table>'."\n";
1634: $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n".
1635: '<input type="hidden" name="oldpts'.$counter.'_'.$partid.'" value="'.$score.'" />'."\n".
1636: '<input type="hidden" name="solved'.$counter.'_'.$partid.'" value="'.
1637: $$record{'resource.'.$partid.'.solved'}.'" />'."\n".
1638: '<input type="hidden" name="totaltries'.$counter.'_'.$partid.'" value="'.
1639: $$record{'resource.'.$partid.'.tries'}.'" />'."\n".
1640: '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.
1641: $aggtries.'" />'."\n";
1642: $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record);
1643: return $result;
1644: }
1645:
1646: sub handback_box {
1647: my ($symb,$uname,$udom,$counter,$partid,$record) = @_;
1648: my ($partlist,$handgrade,$responseType) = &response_type($symb);
1649: my (@respids);
1650: my @part_response_id = &flatten_responseType($responseType);
1651: foreach my $part_response_id (@part_response_id) {
1652: my ($part,$resp) = @{ $part_response_id };
1653: if ($part eq $partid) {
1654: push(@respids,$resp);
1655: }
1656: }
1657: my $result;
1658: foreach my $respid (@respids) {
1659: my $prefix = $counter.'_'.$partid.'_'.$respid.'_';
1660: my $files=&get_submitted_files($udom,$uname,$partid,$respid,$record);
1661: next if (!@$files);
1662: my $file_counter = 1;
1663: foreach my $file (@$files) {
1664: if ($file =~ /\/portfolio\//) {
1665: my ($file_path, $file_disp) = ($file =~ m|(.+/)(.+)$|);
1666: my ($name,$version,$ext) = &file_name_version_ext($file_disp);
1667: $file_disp = "$name.$ext";
1668: $file = $file_path.$file_disp;
1669: $result.=&mt('Return commented version of [_1] to student.',
1670: '<span class="LC_filename">'.$file_disp.'</span>');
1671: $result.='<input type="file" name="'.$prefix.'returndoc'.$file_counter.'" />'."\n";
1672: $result.='<input type="hidden" name="'.$prefix.'origdoc'.$file_counter.'" value="'.$file.'" /><br />';
1673: $result.='('.&mt('File will be uploaded when you click on Save & Next below.').')<br />';
1674: $file_counter++;
1675: }
1676: }
1677: }
1678: return $result;
1679: }
1680:
1681: sub show_problem {
1682: my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode,$form) = @_;
1683: my $rendered;
1684: my %form = ((ref($form) eq 'HASH')? %{$form} : ());
1685: &Apache::lonxml::remember_problem_counter();
1686: if ($mode eq 'both' or $mode eq 'text') {
1687: $rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,
1688: $env{'request.course.id'},
1689: undef,\%form);
1690: }
1691: if ($removeform) {
1692: $rendered=~s|<form(.*?)>||g;
1693: $rendered=~s|</form>||g;
1694: $rendered=~s|(<input[^>]*name\s*=\s*"?)(\w+)("?)|$1would_have_been_$2$3|g;
1695: }
1696: my $companswer;
1697: if ($mode eq 'both' or $mode eq 'answer') {
1698: &Apache::lonxml::restore_problem_counter();
1699: $companswer=
1700: &Apache::loncommon::get_student_answers($symb,$uname,$udom,
1701: $env{'request.course.id'},
1702: %form);
1703: }
1704: if ($removeform) {
1705: $companswer=~s|<form(.*?)>||g;
1706: $companswer=~s|</form>||g;
1707: $companswer=~s|name="submit"|name="would_have_been_submit"|g;
1708: }
1709: $rendered=
1710: '<div class="LC_grade_show_problem_header">'.
1711: &mt('View of the problem').
1712: '</div><div class="LC_grade_show_problem_problem">'.
1713: $rendered.
1714: '</div>';
1715: $companswer=
1716: '<div class="LC_grade_show_problem_header">'.
1717: &mt('Correct answer').
1718: '</div><div class="LC_grade_show_problem_problem">'.
1719: $companswer.
1720: '</div>';
1721: my $result;
1722: if ($mode eq 'both') {
1723: $result=$rendered.$companswer;
1724: } elsif ($mode eq 'text') {
1725: $result=$rendered;
1726: } elsif ($mode eq 'answer') {
1727: $result=$companswer;
1728: }
1729: $result='<div class="LC_grade_show_problem">'.$result.'</div>';
1730: return $result;
1731: }
1732:
1733: sub files_exist {
1734: my ($r, $symb) = @_;
1735: my @students = &Apache::loncommon::get_env_multiple('form.stuinfo');
1736:
1737: foreach my $student (@students) {
1738: my ($uname,$udom,$fullname) = split(/:/,$student);
1739: my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
1740: $udom,$uname);
1741: my ($string,$timestamp)= &get_last_submission(\%record);
1742: foreach my $submission (@$string) {
1743: my ($partid,$respid) =
1744: ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
1745: my $files=&get_submitted_files($udom,$uname,$partid,$respid,
1746: \%record);
1747: return 1 if (@$files);
1748: }
1749: }
1750: return 0;
1751: }
1752:
1753: sub download_all_link {
1754: my ($r,$symb) = @_;
1755: my $all_students =
1756: join("\n", &Apache::loncommon::get_env_multiple('form.stuinfo'));
1757:
1758: my $parts =
1759: join("\n",&Apache::loncommon::get_env_multiple('form.vPart'));
1760:
1761: my $identifier = &Apache::loncommon::get_cgi_id();
1762: &Apache::lonnet::appenv('cgi.'.$identifier.'.students' => $all_students,
1763: 'cgi.'.$identifier.'.symb' => $symb,
1764: 'cgi.'.$identifier.'.parts' => $parts,);
1765: $r->print('<a href="/cgi-bin/multidownload.pl?'.$identifier.'">'.
1766: &mt('Download All Submitted Documents').'</a>');
1767: return
1768: }
1769:
1770: sub build_section_inputs {
1771: my $section_inputs;
1772: if ($env{'form.section'} eq '') {
1773: $section_inputs .= '<input type="hidden" name="section" value="all" />'."\n";
1774: } else {
1775: my @sections = &Apache::loncommon::get_env_multiple('form.section');
1776: foreach my $section (@sections) {
1777: $section_inputs .= '<input type="hidden" name="section" value="'.$section.'" />'."\n";
1778: }
1779: }
1780: return $section_inputs;
1781: }
1782:
1783: # --------------------------- show submissions of a student, option to grade
1784: sub submission {
1785: my ($request,$counter,$total) = @_;
1786: my ($uname,$udom) = ($env{'form.student'},$env{'form.userdom'});
1787: $udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student?
1788: my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
1789: $env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq '';
1790: my $symb = &get_symb($request);
1791: if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; }
1792:
1793: if (!&canview($usec)) {
1794: $request->print('<span class="LC_warning">Unable to view requested student.('.
1795: $uname.':'.$udom.' in section '.$usec.' in course id '.
1796: $env{'request.course.id'}.')</span>');
1797: $request->print(&show_grading_menu_form($symb));
1798: return;
1799: }
1800:
1801: if (!$env{'form.lastSub'}) { $env{'form.lastSub'} = 'datesub'; }
1802: if (!$env{'form.vProb'}) { $env{'form.vProb'} = 'yes'; }
1803: if (!$env{'form.vAns'}) { $env{'form.vAns'} = 'yes'; }
1804: my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
1805: my $checkIcon = '<img alt="'.&mt('Check Mark').
1806: '" src="'.$request->dir_config('lonIconsURL').
1807: '/check.gif" height="16" border="0" />';
1808:
1809: my %old_essays;
1810: # header info
1811: if ($counter == 0) {
1812: &sub_page_js($request);
1813: &sub_page_kw_js($request) if ($env{'form.handgrade'} eq 'yes');
1814: $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ?
1815: &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
1816: if ($env{'form.handgrade'} eq 'yes' && &files_exist($request, $symb)) {
1817: &download_all_link($request, $symb);
1818: }
1819: $request->print('<h3> <span class="LC_info">'.&mt('Submission Record').'</span></h3>'."\n".
1820: '<h4> '.&mt('<b>Resource: </b> [_1]',$env{'form.probTitle'}).'</h4>'."\n");
1821:
1822: # option to display problem, only once else it cause problems
1823: # with the form later since the problem has a form.
1824: if ($env{'form.vProb'} eq 'yes' or $env{'form.vAns'} eq 'yes') {
1825: my $mode;
1826: if ($env{'form.vProb'} eq 'yes' && $env{'form.vAns'} eq 'yes') {
1827: $mode='both';
1828: } elsif ($env{'form.vProb'} eq 'yes') {
1829: $mode='text';
1830: } elsif ($env{'form.vAns'} eq 'yes') {
1831: $mode='answer';
1832: }
1833: &Apache::lonxml::clear_problem_counter();
1834: $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode));
1835: }
1836:
1837: # kwclr is the only variable that is guaranteed to be non blank
1838: # if this subroutine has been called once.
1839: my %keyhash = ();
1840: if ($env{'form.kwclr'} eq '' && $env{'form.handgrade'} eq 'yes') {
1841: %keyhash = &Apache::lonnet::dump('nohist_handgrade',
1842: $env{'course.'.$env{'request.course.id'}.'.domain'},
1843: $env{'course.'.$env{'request.course.id'}.'.num'});
1844:
1845: my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
1846: $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
1847: $env{'form.kwclr'} = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
1848: $env{'form.kwsize'} = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
1849: $env{'form.kwstyle'} = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
1850: $env{'form.msgsub'} = $keyhash{$symb.'_subject'} ne '' ?
1851: $keyhash{$symb.'_subject'} : $env{'form.probTitle'};
1852: $env{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0';
1853: }
1854: my $overRideScore = $env{'form.overRideScore'} eq '' ? 'no' : $env{'form.overRideScore'};
1855: my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
1856: $request->print('<form action="/adm/grades" method="post" name="SCORE" enctype="multipart/form-data">'."\n".
1857: '<input type="hidden" name="command" value="handgrade" />'."\n".
1858: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
1859: '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n".
1860: '<input type="hidden" name="overRideScore" value="'.$overRideScore.'" />'."\n".
1861: '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n".
1862: '<input type="hidden" name="refresh" value="off" />'."\n".
1863: '<input type="hidden" name="studentNo" value="" />'."\n".
1864: '<input type="hidden" name="gradeOpt" value="" />'."\n".
1865: '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
1866: '<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" />'."\n".
1867: '<input type="hidden" name="vProb" value="'.$env{'form.vProb'}.'" />'."\n".
1868: '<input type="hidden" name="vAns" value="'.$env{'form.vAns'}.'" />'."\n".
1869: '<input type="hidden" name="lastSub" value="'.$env{'form.lastSub'}.'" />'."\n".
1870: &build_section_inputs().
1871: '<input type="hidden" name="submitonly" value="'.$env{'form.submitonly'}.'" />'."\n".
1872: '<input type="hidden" name="handgrade" value="'.$env{'form.handgrade'}.'" />'."\n".
1873: '<input type="hidden" name="NCT"'.
1874: ' value="'.($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : $total+1).'" />'."\n");
1875: if ($env{'form.handgrade'} eq 'yes') {
1876: $request->print('<input type="hidden" name="keywords" value="'.$env{'form.keywords'}.'" />'."\n".
1877: '<input type="hidden" name="kwclr" value="'.$env{'form.kwclr'}.'" />'."\n".
1878: '<input type="hidden" name="kwsize" value="'.$env{'form.kwsize'}.'" />'."\n".
1879: '<input type="hidden" name="kwstyle" value="'.$env{'form.kwstyle'}.'" />'."\n".
1880: '<input type="hidden" name="msgsub" value="'.$env{'form.msgsub'}.'" />'."\n".
1881: '<input type="hidden" name="shownSub" value="0" />'."\n".
1882: '<input type="hidden" name="savemsgN" value="'.$env{'form.savemsgN'}.'" />'."\n");
1883: foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) {
1884: $request->print('<input type="hidden" name="vPart" value="'.$partid.'" />'."\n");
1885: }
1886: }
1887:
1888: my ($cts,$prnmsg) = (1,'');
1889: while ($cts <= $env{'form.savemsgN'}) {
1890: $prnmsg.='<input type="hidden" name="savemsg'.$cts.'" value="'.
1891: (!exists($keyhash{$symb.'_savemsg'.$cts}) ?
1892: &Apache::lonfeedback::clear_out_html($env{'form.savemsg'.$cts}) :
1893: &Apache::lonfeedback::clear_out_html($keyhash{$symb.'_savemsg'.$cts})).
1894: '" />'."\n".
1895: '<input type="hidden" name="shownOnce'.$cts.'" value="0" />'."\n";
1896: $cts++;
1897: }
1898: $request->print($prnmsg);
1899:
1900: if ($env{'form.handgrade'} eq 'yes' && $env{'form.showgrading'} eq 'yes') {
1901: #
1902: # Print out the keyword options line
1903: #
1904: $request->print(<<KEYWORDS);
1905: <b>Keyword Options:</b>
1906: <a href="javascript:keywords(document.SCORE);" target="_self">List</a>
1907: <a href="#" onMouseDown="javascript:getSel(); return false"
1908: CLASS="page">Paste Selection to List</a>
1909: <a href="javascript:kwhighlight();" target="_self">Highlight Attribute</a><br /><br />
1910: KEYWORDS
1911: #
1912: # Load the other essays for similarity check
1913: #
1914: my (undef,undef,$essayurl) = &Apache::lonnet::decode_symb($symb);
1915: my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);
1916: $apath=&escape($apath);
1917: $apath=~s/\W/\_/gs;
1918: %old_essays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
1919: }
1920: }
1921:
1922: # This is where output for one specific student would start
1923: my $add_class = ($counter%2) ? 'LC_grade_show_user_odd_row' : '';
1924: $request->print("\n\n".
1925: '<div class="LC_grade_show_user '.$add_class.'">'.
1926: '<div class="LC_grade_user_name">'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'</div>'.
1927: '<div class="LC_grade_show_user_body">'."\n");
1928:
1929: if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {
1930: my $mode;
1931: if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {
1932: $mode='both';
1933: } elsif ($env{'form.vProb'} eq 'all' ) {
1934: $mode='text';
1935: } elsif ($env{'form.vAns'} eq 'all') {
1936: $mode='answer';
1937: }
1938: &Apache::lonxml::clear_problem_counter();
1939: $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode,{'request.prefix' => 'ctr'.$counter}));
1940: }
1941:
1942: my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
1943: my ($partlist,$handgrade,$responseType) = &response_type($symb);
1944:
1945: # Display student info
1946: $request->print(($counter == 0 ? '' : '<br />'));
1947: my $result='<div class="LC_grade_submissions">';
1948:
1949: $result.='<div class="LC_grade_submissions_header">';
1950: $result.= &mt('Submissions');
1951: $result.='<input type="hidden" name="name'.$counter.
1952: '" value="'.$env{'form.fullname'}.'" />'."\n";
1953: if ($env{'form.handgrade'} eq 'no') {
1954: $result.='<span class="LC_grade_check_note">'.
1955: &mt('Part(s) graded correct by the computer is marked with a [_1] symbol.',$checkIcon)."</span>\n";
1956:
1957: }
1958:
1959:
1960:
1961: # If any part of the problem is an essay-response (handgraded), then check for collaborators
1962: my $fullname;
1963: my $col_fullnames = [];
1964: if ($env{'form.handgrade'} eq 'yes') {
1965: (my $sub_result,$fullname,$col_fullnames)=
1966: &check_collaborators($symb,$uname,$udom,\%record,$handgrade,
1967: $counter);
1968: $result.=$sub_result;
1969: }
1970: $request->print($result."\n");
1971: $request->print('</div>'."\n");
1972: # print student answer/submission
1973: # Options are (1) Handgaded submission only
1974: # (2) Last submission, includes submission that is not handgraded
1975: # (for multi-response type part)
1976: # (3) Last submission plus the parts info
1977: # (4) The whole record for this student
1978: if ($env{'form.lastSub'} =~ /^(lastonly|hdgrade)$/) {
1979: my ($string,$timestamp)= &get_last_submission(\%record);
1980:
1981: my $lastsubonly;
1982:
1983: if ($$timestamp eq '') {
1984: $lastsubonly.='<div class="LC_grade_submissions_body">'.$$string[0].'</div>';
1985: } else {
1986: $lastsubonly = '<div class="LC_grade_submissions_body"> <b>Date Submitted:</b> '.$$timestamp."\n";
1987:
1988: my %seenparts;
1989: my @part_response_id = &flatten_responseType($responseType);
1990: foreach my $part (@part_response_id) {
1991: next if ($env{'form.lastSub'} eq 'hdgrade'
1992: && $$handgrade{$$part[0].'_'.$$part[1]} ne 'yes');
1993:
1994: my ($partid,$respid) = @{ $part };
1995: my $display_part=&get_display_part($partid,$symb);
1996: if ($env{"form.$uname:$udom:$partid:submitted_by"}) {
1997: if (exists($seenparts{$partid})) { next; }
1998: $seenparts{$partid}=1;
1999: my $submitby='<b>Part:</b> '.$display_part.
2000: ' <b>Collaborative submission by:</b> '.
2001: '<a href="javascript:viewSubmitter(\''.
2002: $env{"form.$uname:$udom:$partid:submitted_by"}.
2003: '\');" target="_self">'.
2004: $$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a><br />';
2005: $request->print($submitby);
2006: next;
2007: }
2008: my $responsetype = $responseType->{$partid}->{$respid};
2009: if (!exists($record{"resource.$partid.$respid.submission"})) {
2010: $lastsubonly.="\n".'<div class="LC_grade_submission_part"><b>Part:</b> '.
2011: $display_part.' <span class="LC_internal_info">( ID '.$respid.
2012: ' )</span> '.
2013: '<span class="LC_warning">'.&mt('Nothing submitted - no attempts').'</span><br /><br /></div>';
2014: next;
2015: }
2016: foreach my $submission (@$string) {
2017: my ($partid,$respid) = ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
2018: if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }
2019: my ($ressub,$subval) = split(/:/,$submission,2);
2020: # Similarity check
2021: my $similar='';
2022: if($env{'form.checkPlag'}){
2023: my ($oname,$odom,$ocrsid,$oessay,$osim)=
2024: &most_similar($uname,$udom,$subval,\%old_essays);
2025: if ($osim) {
2026: $osim=int($osim*100.0);
2027: my %old_course_desc =
2028: &Apache::lonnet::coursedescription($ocrsid,
2029: {'one_time' => 1});
2030:
2031: $similar="<hr /><h3><span class=\"LC_warning\">".
2032: &mt('Essay is [_1]% similar to an essay by [_2] ([_3]:[_4]) in course [_5] (course id [_6]:[_7])',
2033: $osim,
2034: &Apache::loncommon::plainname($oname,$odom),
2035: $oname,$odom,
2036: $old_course_desc{'description'},
2037: $old_course_desc{'num'},
2038: $old_course_desc{'domain'}).
2039: '</span></h3><blockquote><i>'.
2040: &keywords_highlight($oessay).
2041: '</i></blockquote><hr />';
2042: }
2043: }
2044: my $order=&get_order($partid,$respid,$symb,$uname,$udom);
2045: if ($env{'form.lastSub'} eq 'lastonly' ||
2046: ($env{'form.lastSub'} eq 'hdgrade' &&
2047: $$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) {
2048: my $display_part=&get_display_part($partid,$symb);
2049: $lastsubonly.='<div class="LC_grade_submission_part"><b>Part:</b> '.
2050: $display_part.' <span class="LC_internal_info">( ID '.$respid.
2051: ' )</span> ';
2052: my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
2053: if (@$files) {
2054: $lastsubonly.='<br /><span class="LC_warning">'.&mt('Like all files provided by users, this file may contain virusses').'</span><br />';
2055: my $file_counter = 0;
2056: foreach my $file (@$files) {
2057: $file_counter++;
2058: &Apache::lonnet::allowuploaded('/adm/grades',$file);
2059: $lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border=0"> '.$file.'</a>';
2060: }
2061: $lastsubonly.='<br />';
2062: }
2063: $lastsubonly.='<b>'.&mt('Submitted Answer:').' </b>'.
2064: &cleanRecord($subval,$responsetype,$symb,$partid,
2065: $respid,\%record,$order);
2066: if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
2067: $lastsubonly.='</div>';
2068: }
2069: }
2070: }
2071: $lastsubonly.='</div>'."\n";
2072: }
2073: $request->print($lastsubonly);
2074: } elsif ($env{'form.lastSub'} eq 'datesub') {
2075: my (undef,$responseType,undef,$parts) = &showResourceInfo($symb);
2076: $request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom));
2077: } elsif ($env{'form.lastSub'} =~ /^(last|all)$/) {
2078: $request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,
2079: $env{'request.course.id'},
2080: $last,'.submission',
2081: 'Apache::grades::keywords_highlight'));
2082: }
2083:
2084: $request->print('<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'
2085: .$udom.'" />'."\n");
2086: # return if view submission with no grading option
2087: if ($env{'form.showgrading'} eq '' || (!&canmodify($usec))) {
2088: my $toGrade.='<input type="button" value="Grade Student" '.
2089: 'onClick="javascript:checksubmit(this.form,\'Grade Student\',\''
2090: .$counter.'\');" target="_self" /> '."\n" if (&canmodify($usec));
2091: $toGrade.='</div>'."\n";
2092: if (($env{'form.command'} eq 'submission') ||
2093: ($env{'form.command'} eq 'processGroup' && $counter == $total)) {
2094: $toGrade.='</form>'.&show_grading_menu_form($symb);
2095: }
2096: $request->print($toGrade);
2097: return;
2098: } else {
2099: $request->print('</div>'."\n");
2100: }
2101:
2102: # essay grading message center
2103: if ($env{'form.handgrade'} eq 'yes') {
2104: my $result='<div class="LC_grade_message_center">';
2105:
2106: $result.='<div class="LC_grade_message_center_header">'.
2107: &mt('Send Message').'</div><div class="LC_grade_message_center_body">';
2108: my ($lastname,$givenn) = split(/,/,$env{'form.fullname'});
2109: my $msgfor = $givenn.' '.$lastname;
2110: if (scalar(@$col_fullnames) > 0) {
2111: my $lastone = pop(@$col_fullnames);
2112: $msgfor .= ', '.(join ', ',@$col_fullnames).' and '.$lastone.'.';
2113: }
2114: $msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript
2115: $result.='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".
2116: '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n";
2117: $result.=' <a href="javascript:msgCenter(document.SCORE,'.$counter.
2118: ',\''.$msgfor.'\');" target="_self">'.
2119: &mt('Compose message to student').(scalar(@$col_fullnames) >= 1 ? 's' : '').'</a><label> ('.
2120: &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" /></label>)'.
2121: '<img src="'.$request->dir_config('lonIconsURL').
2122: '/mailbkgrd.gif" width="14" height="10" name="mailicon'.$counter.'" />'."\n".
2123: '<br /> ('.
2124: &mt('Message will be sent when you click on Save & Next below.').")\n";
2125: $result.='</div></div>';
2126: $request->print($result);
2127: }
2128:
2129: my %seen = ();
2130: my @partlist;
2131: my @gradePartRespid;
2132: my @part_response_id = &flatten_responseType($responseType);
2133: $request->print('<div class="LC_grade_assign">'.
2134:
2135: '<div class="LC_grade_assign_header">'.
2136: &mt('Assign Grades').'</div>'.
2137: '<div class="LC_grade_assign_body">');
2138: foreach my $part_response_id (@part_response_id) {
2139: my ($partid,$respid) = @{ $part_response_id };
2140: my $part_resp = join('_',@{ $part_response_id });
2141: next if ($seen{$partid} > 0);
2142: $seen{$partid}++;
2143: next if ($$handgrade{$part_resp} ne 'yes'
2144: && $env{'form.lastSub'} eq 'hdgrade');
2145: push @partlist,$partid;
2146: push @gradePartRespid,$partid.'.'.$respid;
2147: $request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
2148: }
2149: $request->print('</div></div>');
2150:
2151: $request->print('<div class="LC_grade_info_links">');
2152: if ($perm{'vgr'}) {
2153: $request->print(
2154: &Apache::loncommon::track_student_link(&mt('View recent activity'),
2155: $uname,$udom,'check'));
2156: }
2157: if ($perm{'opa'}) {
2158: $request->print(
2159: &Apache::loncommon::pprmlink(&mt('Set/Change parameters'),
2160: $uname,$udom,$symb,'check'));
2161: }
2162: $request->print('</div>');
2163:
2164: $result='<input type="hidden" name="partlist'.$counter.
2165: '" value="'.(join ":",@partlist).'" />'."\n";
2166: $result.='<input type="hidden" name="gradePartRespid'.
2167: '" value="'.(join ":",@gradePartRespid).'" />'."\n" if ($counter == 0);
2168: my $ctr = 0;
2169: while ($ctr < scalar(@partlist)) {
2170: $result.='<input type="hidden" name="partid'.$counter.'_'.$ctr.'" value="'.
2171: $partlist[$ctr].'" />'."\n";
2172: $ctr++;
2173: }
2174: $request->print($result.''."\n");
2175:
2176: # Done with printing info for one student
2177:
2178: $request->print('</div>');#LC_grade_show_user_body
2179: $request->print('</div>');#LC_grade_show_user
2180:
2181:
2182: # print end of form
2183: if ($counter == $total) {
2184: my $endform='<table border="0"><tr><td>'."\n";
2185: $endform.='<input type="button" value="'.&mt('Save & Next').'" '.
2186: 'onClick="javascript:checksubmit(this.form,\'Save & Next\','.
2187: $total.','.scalar(@partlist).');" target="_self" /> '."\n";
2188: my $ntstu ='<select name="NTSTU">'.
2189: '<option>1</option><option>2</option>'.
2190: '<option>3</option><option>5</option>'.
2191: '<option>7</option><option>10</option></select>'."\n";
2192: my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1');
2193: $ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</;
2194: $endform.=&mt('[_1]student(s)',$ntstu);
2195: $endform.=' <input type="button" value="'.&mt('Previous').'" '.
2196: 'onClick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> '."\n".
2197: '<input type="button" value="'.&mt('Next').'" '.
2198: 'onClick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> ';
2199: $endform.=&mt('(Next and Previous (student) do not save the scores.)')."\n" ;
2200: $endform.="<input type='hidden' value='".&get_increment().
2201: "' name='increment' />";
2202: $endform.='</td></tr></table></form>';
2203: $endform.=&show_grading_menu_form($symb);
2204: $request->print($endform);
2205: }
2206: return '';
2207: }
2208:
2209: sub check_collaborators {
2210: my ($symb,$uname,$udom,$record,$handgrade,$counter) = @_;
2211: my ($result,@col_fullnames);
2212: my ($classlist,undef,$fullname) = &getclasslist('all','0');
2213: foreach my $part (keys(%$handgrade)) {
2214: my $ncol = &Apache::lonnet::EXT('resource.'.$part.
2215: '.maxcollaborators',
2216: $symb,$udom,$uname);
2217: next if ($ncol <= 0);
2218: $part =~ s/\_/\./g;
2219: next if ($record->{'resource.'.$part.'.collaborators'} eq '');
2220: my (@good_collaborators, @bad_collaborators);
2221: foreach my $possible_collaborator
2222: (split(/,?\s+/,$record->{'resource.'.$part.'.collaborators'})) {
2223: $possible_collaborator =~ s/[\$\^\(\)]//g;
2224: next if ($possible_collaborator eq '');
2225: my ($co_name,$co_dom) = split(/\@|:/,$possible_collaborator);
2226: $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i);
2227: next if ($co_name eq $uname && $co_dom eq $udom);
2228: # Doing this grep allows 'fuzzy' specification
2229: my @matches = grep(/^\Q$co_name\E:\Q$co_dom\E$/i,
2230: keys(%$classlist));
2231: if (! scalar(@matches)) {
2232: push(@bad_collaborators, $possible_collaborator);
2233: } else {
2234: push(@good_collaborators, @matches);
2235: }
2236: }
2237: if (scalar(@good_collaborators) != 0) {
2238: $result.='<br />'.&mt('Collaborators: ');
2239: foreach my $name (@good_collaborators) {
2240: my ($lastname,$givenn) = split(/,/,$$fullname{$name});
2241: push(@col_fullnames, $givenn.' '.$lastname);
2242: $result.=$fullname->{$name}.' ';
2243: }
2244: $result.='<br />'."\n";
2245: my ($part)=split(/\./,$part);
2246: $result.='<input type="hidden" name="collaborator'.$counter.
2247: '" value="'.$part.':'.(join ':',@good_collaborators).'" />'.
2248: "\n";
2249: }
2250: if (scalar(@bad_collaborators) > 0) {
2251: $result.='<div class="LC_warning">';
2252: $result.=&mt('This student has submitted [quant,_1,invalid collaborator]: [_2]',scalar(@bad_collaborators),join(', ',@bad_collaborators));
2253: $result .= '</div>';
2254: }
2255: if (scalar(@bad_collaborators > $ncol)) {
2256: $result .= '<div class="LC_warning">';
2257: $result .= &mt('This student has submitted too many '.
2258: 'collaborators. Maximum is [_1].',$ncol);
2259: $result .= '</div>';
2260: }
2261: }
2262: return ($result,$fullname,\@col_fullnames);
2263: }
2264:
2265: #--- Retrieve the last submission for all the parts
2266: sub get_last_submission {
2267: my ($returnhash)=@_;
2268: my (@string,$timestamp);
2269: if ($$returnhash{'version'}) {
2270: my %lasthash=();
2271: my ($version);
2272: for ($version=1;$version<=$$returnhash{'version'};$version++) {
2273: foreach my $key (sort(split(/\:/,
2274: $$returnhash{$version.':keys'}))) {
2275: $lasthash{$key}=$$returnhash{$version.':'.$key};
2276: $timestamp =
2277: scalar(localtime($$returnhash{$version.':timestamp'}));
2278: }
2279: }
2280: foreach my $key (keys(%lasthash)) {
2281: next if ($key !~ /\.submission$/);
2282:
2283: my ($partid,$foo) = split(/submission$/,$key);
2284: my $draft = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ?
2285: '<span class="LC_warning">Draft Copy</span> ' : '';
2286: push(@string, join(':', $key, $draft.$lasthash{$key}));
2287: }
2288: }
2289: if (!@string) {
2290: $string[0] =
2291: '<span class="LC_warning">Nothing submitted - no attempts.</span>';
2292: }
2293: return (\@string,\$timestamp);
2294: }
2295:
2296: #--- High light keywords, with style choosen by user.
2297: sub keywords_highlight {
2298: my $string = shift;
2299: my $size = $env{'form.kwsize'} eq '0' ? '' : 'size='.$env{'form.kwsize'};
2300: my $styleon = $env{'form.kwstyle'} eq '' ? '' : $env{'form.kwstyle'};
2301: (my $styleoff = $styleon) =~ s/\</\<\//;
2302: my @keylist = split(/[,\s+]/,$env{'form.keywords'});
2303: foreach my $keyword (@keylist) {
2304: $string =~ s/\b\Q$keyword\E(\b|\.)/<font color\=$env{'form.kwclr'} $size\>$styleon$keyword$styleoff<\/font>/gi;
2305: }
2306: return $string;
2307: }
2308:
2309: #--- Called from submission routine
2310: sub processHandGrade {
2311: my ($request) = shift;
2312: my $symb = &get_symb($request);
2313: my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
2314: my $button = $env{'form.gradeOpt'};
2315: my $ngrade = $env{'form.NCT'};
2316: my $ntstu = $env{'form.NTSTU'};
2317: my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
2318: my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
2319:
2320: if ($button eq 'Save & Next') {
2321: my $ctr = 0;
2322: while ($ctr < $ngrade) {
2323: my ($uname,$udom) = split(/:/,$env{'form.unamedom'.$ctr});
2324: my ($errorflag,$pts,$wgt) = &saveHandGrade($request,$symb,$uname,$udom,$ctr);
2325: if ($errorflag eq 'no_score') {
2326: $ctr++;
2327: next;
2328: }
2329: if ($errorflag eq 'not_allowed') {
2330: $request->print("<span class=\"LC_warning\">Not allowed to modify grades for $uname:$udom</span>");
2331: $ctr++;
2332: next;
2333: }
2334: my $includemsg = $env{'form.includemsg'.$ctr};
2335: my ($subject,$message,$msgstatus) = ('','','');
2336: my $restitle = &Apache::lonnet::gettitle($symb);
2337: my ($feedurl,$showsymb) =
2338: &get_feedurl_and_symb($symb,$uname,$udom);
2339: my $messagetail;
2340: if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) {
2341: $subject = $env{'form.msgsub'} if ($includemsg =~ /msgsub/);
2342: unless ($subject=~/\w/) { $subject=&mt('Grading Feedback'); }
2343: $subject.=' ['.$restitle.']';
2344: my (@msgnum) = split(/,/,$includemsg);
2345: foreach (@msgnum) {
2346: $message.=$env{'form.'.$_} if ($_ =~ /savemsg|newmsg/ && $_ ne '');
2347: }
2348: $message =&Apache::lonfeedback::clear_out_html($message);
2349: if ($env{'form.withgrades'.$ctr}) {
2350: $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt;
2351: $messagetail = " for <a href=\"".
2352: $feedurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
2353: }
2354: $msgstatus =
2355: &Apache::lonmsg::user_normal_msg($uname,$udom,$subject,
2356: $message.$messagetail,
2357: undef,$feedurl,undef,
2358: undef,undef,$showsymb,
2359: $restitle);
2360: $request->print('<br />'.&mt('Sending message to [_1]:[_2]',$uname,$udom).': '.
2361: $msgstatus);
2362: }
2363: if ($env{'form.collaborator'.$ctr}) {
2364: my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr");
2365: foreach my $collabstr (@collabstrs) {
2366: my ($part,@collaborators) = split(/:/,$collabstr);
2367: foreach my $collaborator (@collaborators) {
2368: my ($errorflag,$pts,$wgt) =
2369: &saveHandGrade($request,$symb,$collaborator,$udom,$ctr,
2370: $env{'form.unamedom'.$ctr},$part);
2371: if ($errorflag eq 'not_allowed') {
2372: $request->print("<span class=\"LC_error\">".&mt('Not allowed to modify grades for [_1]',"$collaborator:$udom")."</span>");
2373: next;
2374: } elsif ($message ne '') {
2375: my ($baseurl,$showsymb) =
2376: &get_feedurl_and_symb($symb,$collaborator,
2377: $udom);
2378: if ($env{'form.withgrades'.$ctr}) {
2379: $messagetail = " for <a href=\"".
2380: $baseurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
2381: }
2382: $msgstatus =
2383: &Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
2384: }
2385: }
2386: }
2387: }
2388: $ctr++;
2389: }
2390: }
2391:
2392: if ($env{'form.handgrade'} eq 'yes') {
2393: # Keywords sorted in alphabatical order
2394: my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
2395: my %keyhash = ();
2396: $env{'form.keywords'} =~ s/,\s{0,}|\s+/ /g;
2397: $env{'form.keywords'} =~ s/^\s+|\s+$//;
2398: my (@keywords) = sort(split(/\s+/,$env{'form.keywords'}));
2399: $env{'form.keywords'} = join(' ',@keywords);
2400: $keyhash{$symb.'_keywords'} = $env{'form.keywords'};
2401: $keyhash{$symb.'_subject'} = $env{'form.msgsub'};
2402: $keyhash{$loginuser.'_kwclr'} = $env{'form.kwclr'};
2403: $keyhash{$loginuser.'_kwsize'} = $env{'form.kwsize'};
2404: $keyhash{$loginuser.'_kwstyle'} = $env{'form.kwstyle'};
2405:
2406: # message center - Order of message gets changed. Blank line is eliminated.
2407: # New messages are saved in env for the next student.
2408: # All messages are saved in nohist_handgrade.db
2409: my ($ctr,$idx) = (1,1);
2410: while ($ctr <= $env{'form.savemsgN'}) {
2411: if ($env{'form.savemsg'.$ctr} ne '') {
2412: $keyhash{$symb.'_savemsg'.$idx} = $env{'form.savemsg'.$ctr};
2413: $idx++;
2414: }
2415: $ctr++;
2416: }
2417: $ctr = 0;
2418: while ($ctr < $ngrade) {
2419: if ($env{'form.newmsg'.$ctr} ne '') {
2420: $keyhash{$symb.'_savemsg'.$idx} = $env{'form.newmsg'.$ctr};
2421: $env{'form.savemsg'.$idx} = $env{'form.newmsg'.$ctr};
2422: $idx++;
2423: }
2424: $ctr++;
2425: }
2426: $env{'form.savemsgN'} = --$idx;
2427: $keyhash{$symb.'_savemsgN'} = $env{'form.savemsgN'};
2428: my $putresult = &Apache::lonnet::put
2429: ('nohist_handgrade',\%keyhash,$cdom,$cnum);
2430: }
2431: # Called by Save & Refresh from Highlight Attribute Window
2432: my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
2433: if ($env{'form.refresh'} eq 'on') {
2434: my ($ctr,$total) = (0,0);
2435: while ($ctr < $ngrade) {
2436: $total++ if $env{'form.unamedom'.$ctr} ne '';
2437: $ctr++;
2438: }
2439: $env{'form.NTSTU'}=$ngrade;
2440: $ctr = 0;
2441: while ($ctr < $total) {
2442: my $processUser = $env{'form.unamedom'.$ctr};
2443: ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
2444: $env{'form.fullname'} = $$fullname{$processUser};
2445: &submission($request,$ctr,$total-1);
2446: $ctr++;
2447: }
2448: return '';
2449: }
2450:
2451: # Go directly to grade student - from submission or link from chart page
2452: if ($button eq 'Grade Student') {
2453: (undef,undef,$env{'form.handgrade'},undef,undef) = &showResourceInfo($symb);
2454: my $processUser = $env{'form.unamedom'.$env{'form.studentNo'}};
2455: ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
2456: $env{'form.fullname'} = $$fullname{$processUser};
2457: &submission($request,0,0);
2458: return '';
2459: }
2460:
2461: # Get the next/previous one or group of students
2462: my $firststu = $env{'form.unamedom0'};
2463: my $laststu = $env{'form.unamedom'.($ngrade-1)};
2464: my $ctr = 2;
2465: while ($laststu eq '') {
2466: $laststu = $env{'form.unamedom'.($ngrade-$ctr)};
2467: $ctr++;
2468: $laststu = $firststu if ($ctr > $ngrade);
2469: }
2470:
2471: my (@parsedlist,@nextlist);
2472: my ($nextflg) = 0;
2473: foreach (sort
2474: {
2475: if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
2476: return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
2477: }
2478: return $a cmp $b;
2479: } (keys(%$fullname))) {
2480: if ($nextflg == 1 && $button =~ /Next$/) {
2481: push @parsedlist,$_;
2482: }
2483: $nextflg = 1 if ($_ eq $laststu);
2484: if ($button eq 'Previous') {
2485: last if ($_ eq $firststu);
2486: push @parsedlist,$_;
2487: }
2488: }
2489: $ctr = 0;
2490: @parsedlist = reverse @parsedlist if ($button eq 'Previous');
2491: my ($partlist) = &response_type($symb);
2492: foreach my $student (@parsedlist) {
2493: my $submitonly=$env{'form.submitonly'};
2494: my ($uname,$udom) = split(/:/,$student);
2495:
2496: if ($submitonly eq 'queued') {
2497: my %queue_status =
2498: &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
2499: $udom,$uname);
2500: next if (!defined($queue_status{'gradingqueue'}));
2501: }
2502:
2503: if ($submitonly =~ /^(yes|graded|incorrect)$/) {
2504: # my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
2505: my %status=&student_gradeStatus($symb,$udom,$uname,$partlist);
2506: my $submitted = 0;
2507: my $ungraded = 0;
2508: my $incorrect = 0;
2509: foreach (keys(%status)) {
2510: $submitted = 1 if ($status{$_} ne 'nothing');
2511: $ungraded = 1 if ($status{$_} =~ /^ungraded/);
2512: $incorrect = 1 if ($status{$_} =~ /^incorrect/);
2513: my ($foo,$partid,$foo1) = split(/\./,$_);
2514: if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
2515: $submitted = 0;
2516: }
2517: }
2518: next if (!$submitted && ($submitonly eq 'yes' ||
2519: $submitonly eq 'incorrect' ||
2520: $submitonly eq 'graded'));
2521: next if (!$ungraded && ($submitonly eq 'graded'));
2522: next if (!$incorrect && $submitonly eq 'incorrect');
2523: }
2524: push @nextlist,$student if ($ctr < $ntstu);
2525: last if ($ctr == $ntstu);
2526: $ctr++;
2527: }
2528:
2529: $ctr = 0;
2530: my $total = scalar(@nextlist)-1;
2531:
2532: foreach (sort @nextlist) {
2533: my ($uname,$udom,$submitter) = split(/:/);
2534: $env{'form.student'} = $uname;
2535: $env{'form.userdom'} = $udom;
2536: $env{'form.fullname'} = $$fullname{$_};
2537: &submission($request,$ctr,$total);
2538: $ctr++;
2539: }
2540: if ($total < 0) {
2541: my $the_end = '<h3><span class="LC_info">'.&mt('LON-CAPA User Message').'</span></h3><br />'."\n";
2542: $the_end.=&mt('<b>Message: </b> No more students for this section or class.').'<br /><br />'."\n";
2543: $the_end.=&mt('Click on the button below to return to the grading menu.').'<br /><br />'."\n";
2544: $the_end.=&show_grading_menu_form($symb);
2545: $request->print($the_end);
2546: }
2547: return '';
2548: }
2549:
2550: #---- Save the score and award for each student, if changed
2551: sub saveHandGrade {
2552: my ($request,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_;
2553: my @version_parts;
2554: my $usec = &Apache::lonnet::getsection($domain,$stuname,
2555: $env{'request.course.id'});
2556: if (!&canmodify($usec)) { return('not_allowed'); }
2557: my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$stuname);
2558: my @parts_graded;
2559: my %newrecord = ();
2560: my ($pts,$wgt) = ('','');
2561: my %aggregate = ();
2562: my $aggregateflag = 0;
2563: my @parts = split(/:/,$env{'form.partlist'.$newflg});
2564: foreach my $new_part (@parts) {
2565: #collaborator ($submi may vary for different parts
2566: if ($submitter && $new_part ne $part) { next; }
2567: my $dropMenu = $env{'form.GD_SEL'.$newflg.'_'.$new_part};
2568: if ($dropMenu eq 'excused') {
2569: if ($record{'resource.'.$new_part.'.solved'} ne 'excused') {
2570: $newrecord{'resource.'.$new_part.'.solved'} = 'excused';
2571: if (exists($record{'resource.'.$new_part.'.awarded'})) {
2572: $newrecord{'resource.'.$new_part.'.awarded'} = '';
2573: }
2574: $newrecord{'resource.'.$new_part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
2575: }
2576: } elsif ($dropMenu eq 'reset status'
2577: && exists($record{'resource.'.$new_part.'.solved'})) { #don't bother if no old records -> no attempts
2578: foreach my $key (keys (%record)) {
2579: if ($key=~/^resource\.\Q$new_part\E\./) { $newrecord{$key} = ''; }
2580: }
2581: $newrecord{'resource.'.$new_part.'.regrader'}=
2582: "$env{'user.name'}:$env{'user.domain'}";
2583: my $totaltries = $record{'resource.'.$part.'.tries'};
2584:
2585: my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
2586: [$new_part]);
2587: my $aggtries =$totaltries;
2588: if ($last_resets{$new_part}) {
2589: $aggtries = &get_num_tries(\%record,$last_resets{$new_part},
2590: $new_part);
2591: }
2592:
2593: my $solvedstatus = $record{'resource.'.$new_part.'.solved'};
2594: if ($aggtries > 0) {
2595: &decrement_aggs($symb,$new_part,\%aggregate,$aggtries,$totaltries,$solvedstatus);
2596: $aggregateflag = 1;
2597: }
2598: } elsif ($dropMenu eq '') {
2599: $pts = ($env{'form.GD_BOX'.$newflg.'_'.$new_part} ne '' ?
2600: $env{'form.GD_BOX'.$newflg.'_'.$new_part} :
2601: $env{'form.RADVAL'.$newflg.'_'.$new_part});
2602: if ($pts eq '' && $env{'form.GD_SEL'.$newflg.'_'.$new_part} eq '') {
2603: next;
2604: }
2605: $wgt = $env{'form.WGT'.$newflg.'_'.$new_part} eq '' ? 1 :
2606: $env{'form.WGT'.$newflg.'_'.$new_part};
2607: my $partial= $pts/$wgt;
2608: if ($partial eq $record{'resource.'.$new_part.'.awarded'}) {
2609: #do not update score for part if not changed.
2610: &handback_files($request,$symb,$stuname,$domain,$newflg,$new_part,\%newrecord);
2611: next;
2612: } else {
2613: push @parts_graded, $new_part;
2614: }
2615: if ($record{'resource.'.$new_part.'.awarded'} ne $partial) {
2616: $newrecord{'resource.'.$new_part.'.awarded'} = $partial;
2617: }
2618: my $reckey = 'resource.'.$new_part.'.solved';
2619: if ($partial == 0) {
2620: if ($record{$reckey} ne 'incorrect_by_override') {
2621: $newrecord{$reckey} = 'incorrect_by_override';
2622: }
2623: } else {
2624: if ($record{$reckey} ne 'correct_by_override') {
2625: $newrecord{$reckey} = 'correct_by_override';
2626: }
2627: }
2628: if ($submitter &&
2629: ($record{'resource.'.$new_part.'.submitted_by'} ne $submitter)) {
2630: $newrecord{'resource.'.$new_part.'.submitted_by'} = $submitter;
2631: }
2632: $newrecord{'resource.'.$new_part.'.regrader'}=
2633: "$env{'user.name'}:$env{'user.domain'}";
2634: }
2635: # unless problem has been graded, set flag to version the submitted files
2636: unless ($record{'resource.'.$new_part.'.solved'} =~ /^correct_/ ||
2637: $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override' ||
2638: $dropMenu eq 'reset status')
2639: {
2640: push (@version_parts,$new_part);
2641: }
2642: }
2643: my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
2644: my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
2645:
2646: if (%newrecord) {
2647: if (@version_parts) {
2648: my @changed_keys = &version_portfiles(\%record, \@parts_graded,
2649: $env{'request.course.id'}, $symb, $domain, $stuname, \@version_parts);
2650: @newrecord{@changed_keys} = @record{@changed_keys};
2651: foreach my $new_part (@version_parts) {
2652: &handback_files($request,$symb,$stuname,$domain,$newflg,
2653: $new_part,\%newrecord);
2654: }
2655: }
2656: &Apache::lonnet::cstore(\%newrecord,$symb,
2657: $env{'request.course.id'},$domain,$stuname);
2658: &check_and_remove_from_queue(\@parts,\%record,\%newrecord,$symb,
2659: $cdom,$cnum,$domain,$stuname);
2660: }
2661: if ($aggregateflag) {
2662: &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
2663: $cdom,$cnum);
2664: }
2665: return ('',$pts,$wgt);
2666: }
2667:
2668: sub check_and_remove_from_queue {
2669: my ($parts,$record,$newrecord,$symb,$cdom,$cnum,$domain,$stuname) = @_;
2670: my @ungraded_parts;
2671: foreach my $part (@{$parts}) {
2672: if ( $record->{ 'resource.'.$part.'.awarded'} eq ''
2673: && $record->{ 'resource.'.$part.'.solved' } ne 'excused'
2674: && $newrecord->{'resource.'.$part.'.awarded'} eq ''
2675: && $newrecord->{'resource.'.$part.'.solved' } ne 'excused'
2676: ) {
2677: push(@ungraded_parts, $part);
2678: }
2679: }
2680: if ( !@ungraded_parts ) {
2681: &Apache::bridgetask::remove_from_queue('gradingqueue',$symb,$cdom,
2682: $cnum,$domain,$stuname);
2683: }
2684: }
2685:
2686: sub handback_files {
2687: my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_;
2688: my $portfolio_root = &propath($domain,$stuname).'/userfiles/portfolio';
2689: my ($partlist,$handgrade,$responseType) = &response_type($symb);
2690:
2691: my @part_response_id = &flatten_responseType($responseType);
2692: foreach my $part_response_id (@part_response_id) {
2693: my ($part_id,$resp_id) = @{ $part_response_id };
2694: my $part_resp = join('_',@{ $part_response_id });
2695: if (($env{'form.'.$newflg.'_'.$part_resp.'_returndoc1'}) && ($new_part == $part_id)) {
2696: # if multiple files are uploaded names will be 'returndoc2','returndoc3'
2697: my $file_counter = 1;
2698: my $file_msg;
2699: while ($env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter}) {
2700: my $fname=$env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'.filename'};
2701: my ($directory,$answer_file) =
2702: ($env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$file_counter} =~ /^(.*?)([^\/]*)$/);
2703: my ($answer_name,$answer_ver,$answer_ext) =
2704: &file_name_version_ext($answer_file);
2705: my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/);
2706: my @dir_list = &Apache::lonnet::dirlist($portfolio_path,$domain,$stuname,$portfolio_root);
2707: my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
2708: # fix file name
2709: my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/);
2710: my $result=&Apache::lonnet::finishuserfileupload($stuname,$domain,
2711: $newflg.'_'.$part_resp.'_returndoc'.$file_counter,
2712: $save_file_name);
2713: if ($result !~ m|^/uploaded/|) {
2714: $request->print('<span class="LC_error">An error occurred ('.$result.
2715: ') while trying to upload '.$newflg.'_'.$part_resp.'_returndoc'.$file_counter.'</span><br />');
2716: } else {
2717: # mark the file as read only
2718: my @files = ($save_file_name);
2719: my @what = ($symb,$env{'request.course.id'},'handback');
2720: &Apache::lonnet::mark_as_readonly($domain,$stuname,\@files,\@what);
2721: if (exists($$newrecord{"resource.$new_part.$resp_id.handback"})) {
2722: $$newrecord{"resource.$new_part.$resp_id.handback"}.=',';
2723: }
2724: $$newrecord{"resource.$new_part.$resp_id.handback"} .= $save_file_name;
2725: $file_msg.= "\n".'<br /><span class="LC_filename"><a href="/uploaded/'."$domain/$stuname/".$save_file_name.'">'.$save_file_name."</a></span><br />";
2726:
2727: }
2728: $request->print("<br />".$fname." will be the uploaded file name");
2729: $request->print(" ".$env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$file_counter});
2730: $file_counter++;
2731: }
2732: my $subject = "File Handed Back by Instructor ";
2733: my $message = "A file has been returned that was originally submitted in reponse to: <br />";
2734: $message .= "<strong>".&Apache::lonnet::gettitle($symb)."</strong><br />";
2735: $message .= ' The returned file(s) are named: '. $file_msg;
2736: $message .= " and can be found in your portfolio space.";
2737: my ($feedurl,$showsymb) =
2738: &get_feedurl_and_symb($symb,$domain,$stuname);
2739: my $restitle = &Apache::lonnet::gettitle($symb);
2740: my $msgstatus =
2741: &Apache::lonmsg::user_normal_msg($stuname,$domain,$subject.
2742: ' (File Returned) ['.$restitle.']',$message,undef,
2743: $feedurl,undef,undef,undef,$showsymb,$restitle);
2744: }
2745: }
2746: return;
2747: }
2748:
2749: sub get_feedurl_and_symb {
2750: my ($symb,$uname,$udom) = @_;
2751: my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
2752: $url = &Apache::lonnet::clutter($url);
2753: my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl',
2754: $symb,$udom,$uname);
2755: if ($encrypturl =~ /^yes$/i) {
2756: &Apache::lonenc::encrypted(\$url,1);
2757: &Apache::lonenc::encrypted(\$symb,1);
2758: }
2759: return ($url,$symb);
2760: }
2761:
2762: sub get_submitted_files {
2763: my ($udom,$uname,$partid,$respid,$record) = @_;
2764: my @files;
2765: if ($$record{"resource.$partid.$respid.portfiles"}) {
2766: my $file_url = '/uploaded/'.$udom.'/'.$uname.'/portfolio';
2767: foreach my $file (split(',',$$record{"resource.$partid.$respid.portfiles"})) {
2768: push(@files,$file_url.$file);
2769: }
2770: }
2771: if ($$record{"resource.$partid.$respid.uploadedurl"}) {
2772: push(@files,$$record{"resource.$partid.$respid.uploadedurl"});
2773: }
2774: return (\@files);
2775: }
2776:
2777: # ----------- Provides number of tries since last reset.
2778: sub get_num_tries {
2779: my ($record,$last_reset,$part) = @_;
2780: my $timestamp = '';
2781: my $num_tries = 0;
2782: if ($$record{'version'}) {
2783: for (my $version=$$record{'version'};$version>=1;$version--) {
2784: if (exists($$record{$version.':resource.'.$part.'.solved'})) {
2785: $timestamp = $$record{$version.':timestamp'};
2786: if ($timestamp > $last_reset) {
2787: $num_tries ++;
2788: } else {
2789: last;
2790: }
2791: }
2792: }
2793: }
2794: return $num_tries;
2795: }
2796:
2797: # ----------- Determine decrements required in aggregate totals
2798: sub decrement_aggs {
2799: my ($symb,$part,$aggregate,$aggtries,$totaltries,$solvedstatus) = @_;
2800: my %decrement = (
2801: attempts => 0,
2802: users => 0,
2803: correct => 0
2804: );
2805: $decrement{'attempts'} = $aggtries;
2806: if ($solvedstatus =~ /^correct/) {
2807: $decrement{'correct'} = 1;
2808: }
2809: if ($aggtries == $totaltries) {
2810: $decrement{'users'} = 1;
2811: }
2812: foreach my $type (keys (%decrement)) {
2813: $$aggregate{$symb."\0".$part."\0".$type} = -$decrement{$type};
2814: }
2815: return;
2816: }
2817:
2818: # ----------- Determine timestamps for last reset of aggregate totals for parts
2819: sub get_last_resets {
2820: my ($symb,$courseid,$partids) =@_;
2821: my %last_resets;
2822: my $cdom = $env{'course.'.$courseid.'.domain'};
2823: my $cname = $env{'course.'.$courseid.'.num'};
2824: my @keys;
2825: foreach my $part (@{$partids}) {
2826: push(@keys,"$symb\0$part\0resettime");
2827: }
2828: my %results=&Apache::lonnet::get('nohist_resourcetracker',\@keys,
2829: $cdom,$cname);
2830: foreach my $part (@{$partids}) {
2831: $last_resets{$part}=$results{"$symb\0$part\0resettime"};
2832: }
2833: return %last_resets;
2834: }
2835:
2836: # ----------- Handles creating versions for portfolio files as answers
2837: sub version_portfiles {
2838: my ($record, $parts_graded, $courseid, $symb, $domain, $stu_name, $v_flag) = @_;
2839: my $version_parts = join('|',@$v_flag);
2840: my @returned_keys;
2841: my $parts = join('|', @$parts_graded);
2842: my $portfolio_root = &propath($domain,$stu_name).
2843: '/userfiles/portfolio';
2844: foreach my $key (keys(%$record)) {
2845: my $new_portfiles;
2846: if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {
2847: my @versioned_portfiles;
2848: my @portfiles = split(/\s*,\s*/,$$record{$key});
2849: foreach my $file (@portfiles) {
2850: &Apache::lonnet::unmark_as_readonly($domain,$stu_name,[$symb,$env{'request.course.id'}],$file);
2851: my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*)$/);
2852: my ($answer_name,$answer_ver,$answer_ext) =
2853: &file_name_version_ext($answer_file);
2854: my @dir_list = &Apache::lonnet::dirlist($directory,$domain,$stu_name,$portfolio_root);
2855: my $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
2856: my $new_answer = &version_selected_portfile($domain, $stu_name, $directory, $answer_file, $version);
2857: if ($new_answer ne 'problem getting file') {
2858: push(@versioned_portfiles, $directory.$new_answer);
2859: &Apache::lonnet::mark_as_readonly($domain,$stu_name,
2860: [$directory.$new_answer],
2861: [$symb,$env{'request.course.id'},'graded']);
2862: }
2863: }
2864: $$record{$key} = join(',',@versioned_portfiles);
2865: push(@returned_keys,$key);
2866: }
2867: }
2868: return (@returned_keys);
2869: }
2870:
2871: sub get_next_version {
2872: my ($answer_name, $answer_ext, $dir_list) = @_;
2873: my $version;
2874: foreach my $row (@$dir_list) {
2875: my ($file) = split(/\&/,$row,2);
2876: my ($file_name,$file_version,$file_ext) =
2877: &file_name_version_ext($file);
2878: if (($file_name eq $answer_name) &&
2879: ($file_ext eq $answer_ext)) {
2880: # gets here if filename and extension match, regardless of version
2881: if ($file_version ne '') {
2882: # a versioned file is found so save it for later
2883: if ($file_version > $version) {
2884: $version = $file_version;
2885: }
2886: }
2887: }
2888: }
2889: $version ++;
2890: return($version);
2891: }
2892:
2893: sub version_selected_portfile {
2894: my ($domain,$stu_name,$directory,$file_name,$version) = @_;
2895: my ($answer_name,$answer_ver,$answer_ext) =
2896: &file_name_version_ext($file_name);
2897: my $new_answer;
2898: $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stu_name/portfolio$directory$file_name");
2899: if($env{'form.copy'} eq '-1') {
2900: $new_answer = 'problem getting file';
2901: } else {
2902: $new_answer = $answer_name.'.'.$version.'.'.$answer_ext;
2903: my $copy_result = &Apache::lonnet::finishuserfileupload(
2904: $stu_name,$domain,'copy',
2905: '/portfolio'.$directory.$new_answer);
2906: }
2907: return ($new_answer);
2908: }
2909:
2910: sub file_name_version_ext {
2911: my ($file)=@_;
2912: my @file_parts = split(/\./, $file);
2913: my ($name,$version,$ext);
2914: if (@file_parts > 1) {
2915: $ext=pop(@file_parts);
2916: if (@file_parts > 1 && $file_parts[-1] =~ /^\d+$/) {
2917: $version=pop(@file_parts);
2918: }
2919: $name=join('.',@file_parts);
2920: } else {
2921: $name=join('.',@file_parts);
2922: }
2923: return($name,$version,$ext);
2924: }
2925:
2926: #--------------------------------------------------------------------------------------
2927: #
2928: #-------------------------- Next few routines handles grading by section or whole class
2929: #
2930: #--- Javascript to handle grading by section or whole class
2931: sub viewgrades_js {
2932: my ($request) = shift;
2933:
2934: $request->print(<<VIEWJAVASCRIPT);
2935: <script type="text/javascript" language="javascript">
2936: function writePoint(partid,weight,point) {
2937: var radioButton = document.classgrade["RADVAL_"+partid];
2938: var textbox = document.classgrade["TEXTVAL_"+partid];
2939: if (point == "textval") {
2940: point = document.classgrade["TEXTVAL_"+partid].value;
2941: if (isNaN(point) || parseFloat(point) < 0) {
2942: alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point));
2943: var resetbox = false;
2944: for (var i=0; i<radioButton.length; i++) {
2945: if (radioButton[i].checked) {
2946: textbox.value = i;
2947: resetbox = true;
2948: }
2949: }
2950: if (!resetbox) {
2951: textbox.value = "";
2952: }
2953: return;
2954: }
2955: if (parseFloat(point) > parseFloat(weight)) {
2956: var resp = confirm("You entered a value ("+parseFloat(point)+
2957: ") greater than the weight for the part. Accept?");
2958: if (resp == false) {
2959: textbox.value = "";
2960: return;
2961: }
2962: }
2963: for (var i=0; i<radioButton.length; i++) {
2964: radioButton[i].checked=false;
2965: if (parseFloat(point) == i) {
2966: radioButton[i].checked=true;
2967: }
2968: }
2969:
2970: } else {
2971: textbox.value = parseFloat(point);
2972: }
2973: for (i=0;i<document.classgrade.total.value;i++) {
2974: var user = document.classgrade["ctr"+i].value;
2975: user = user.replace(new RegExp(':', 'g'),"_");
2976: var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
2977: var saveval = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
2978: var selname = document.classgrade["GD_"+user+"_"+partid+"_solved"];
2979: if (saveval != "correct") {
2980: scorename.value = point;
2981: if (selname[0].selected != true) {
2982: selname[0].selected = true;
2983: }
2984: }
2985: }
2986: document.classgrade["SELVAL_"+partid][0].selected = true;
2987: }
2988:
2989: function writeRadText(partid,weight) {
2990: var selval = document.classgrade["SELVAL_"+partid];
2991: var radioButton = document.classgrade["RADVAL_"+partid];
2992: var override = document.classgrade["FORCE_"+partid].checked;
2993: var textbox = document.classgrade["TEXTVAL_"+partid];
2994: if (selval[1].selected || selval[2].selected) {
2995: for (var i=0; i<radioButton.length; i++) {
2996: radioButton[i].checked=false;
2997:
2998: }
2999: textbox.value = "";
3000:
3001: for (i=0;i<document.classgrade.total.value;i++) {
3002: var user = document.classgrade["ctr"+i].value;
3003: user = user.replace(new RegExp(':', 'g'),"_");
3004: var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
3005: var saveval = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
3006: var selname = document.classgrade["GD_"+user+"_"+partid+"_solved"];
3007: if ((saveval != "correct") || override) {
3008: scorename.value = "";
3009: if (selval[1].selected) {
3010: selname[1].selected = true;
3011: } else {
3012: selname[2].selected = true;
3013: if (Number(document.classgrade["GD_"+user+"_"+partid+"_tries"].value))
3014: {document.classgrade["GD_"+user+"_"+partid+"_tries"].value = '0';}
3015: }
3016: }
3017: }
3018: } else {
3019: for (i=0;i<document.classgrade.total.value;i++) {
3020: var user = document.classgrade["ctr"+i].value;
3021: user = user.replace(new RegExp(':', 'g'),"_");
3022: var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
3023: var saveval = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
3024: var selname = document.classgrade["GD_"+user+"_"+partid+"_solved"];
3025: if ((saveval != "correct") || override) {
3026: scorename.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
3027: selname[0].selected = true;
3028: }
3029: }
3030: }
3031: }
3032:
3033: function changeSelect(partid,user) {
3034: var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
3035: var textbox = document.classgrade["GD_"+user+'_'+partid+"_awarded"];
3036: var point = textbox.value;
3037: var weight = document.classgrade["weight_"+partid].value;
3038:
3039: if (isNaN(point) || parseFloat(point) < 0) {
3040: alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point));
3041: textbox.value = "";
3042: return;
3043: }
3044: if (parseFloat(point) > parseFloat(weight)) {
3045: var resp = confirm("You entered a value ("+parseFloat(point)+
3046: ") greater than the weight of the part. Accept?");
3047: if (resp == false) {
3048: textbox.value = "";
3049: return;
3050: }
3051: }
3052: selval[0].selected = true;
3053: }
3054:
3055: function changeOneScore(partid,user) {
3056: var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
3057: if (selval[1].selected || selval[2].selected) {
3058: document.classgrade["GD_"+user+'_'+partid+"_awarded"].value = "";
3059: if (selval[2].selected) {
3060: document.classgrade["GD_"+user+'_'+partid+"_tries"].value = "0";
3061: }
3062: }
3063: }
3064:
3065: function resetEntry(numpart) {
3066: for (ctpart=0;ctpart<numpart;ctpart++) {
3067: var partid = document.classgrade["partid_"+ctpart].value;
3068: var radioButton = document.classgrade["RADVAL_"+partid];
3069: var textbox = document.classgrade["TEXTVAL_"+partid];
3070: var selval = document.classgrade["SELVAL_"+partid];
3071: for (var i=0; i<radioButton.length; i++) {
3072: radioButton[i].checked=false;
3073:
3074: }
3075: textbox.value = "";
3076: selval[0].selected = true;
3077:
3078: for (i=0;i<document.classgrade.total.value;i++) {
3079: var user = document.classgrade["ctr"+i].value;
3080: user = user.replace(new RegExp(':', 'g'),"_");
3081: var resetscore = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
3082: resetscore.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
3083: var resettries = document.classgrade["GD_"+user+"_"+partid+"_tries"];
3084: resettries.value = document.classgrade["GD_"+user+"_"+partid+"_tries_s"].value;
3085: var saveselval = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
3086: var selname = document.classgrade["GD_"+user+"_"+partid+"_solved"];
3087: if (saveselval == "excused") {
3088: if (selname[1].selected == false) { selname[1].selected = true;}
3089: } else {
3090: if (selname[0].selected == false) {selname[0].selected = true};
3091: }
3092: }
3093: }
3094: }
3095:
3096: </script>
3097: VIEWJAVASCRIPT
3098: }
3099:
3100: #--- show scores for a section or whole class w/ option to change/update a score
3101: sub viewgrades {
3102: my ($request) = shift;
3103: &viewgrades_js($request);
3104:
3105: my ($symb) = &get_symb($request);
3106: #need to make sure we have the correct data for later EXT calls,
3107: #thus invalidate the cache
3108: &Apache::lonnet::devalidatecourseresdata(
3109: $env{'course.'.$env{'request.course.id'}.'.num'},
3110: $env{'course.'.$env{'request.course.id'}.'.domain'});
3111: &Apache::lonnet::clear_EXT_cache_status();
3112:
3113: my $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>';
3114: $result.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4>'."\n";
3115:
3116: #view individual student submission form - called using Javascript viewOneStudent
3117: $result.=&jscriptNform($symb);
3118:
3119: #beginning of class grading form
3120: my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
3121: $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n".
3122: '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
3123: '<input type="hidden" name="command" value="editgrades" />'."\n".
3124: &build_section_inputs().
3125: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
3126: '<input type="hidden" name="Status" value="'.$env{'stu_status'}.'" />'."\n".
3127: '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
3128:
3129: my $sectionClass;
3130: my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
3131: if ($env{'form.section'} eq 'all') {
3132: $sectionClass='Class';
3133: } elsif ($env{'form.section'} eq 'none') {
3134: $sectionClass='Students in no Section';
3135: } else {
3136: $sectionClass='Students in Section(s) [_1]';
3137: }
3138: $result.=
3139: '<h3>'.
3140: &mt("Assign Common Grade To $sectionClass",$section_display).'</h3>';
3141: $result.= &Apache::loncommon::start_data_table();
3142: #radio buttons/text box for assigning points for a section or class.
3143: #handles different parts of a problem
3144: my ($partlist,$handgrade,$responseType) = &response_type($symb);
3145: my %weight = ();
3146: my $ctsparts = 0;
3147: my %seen = ();
3148: my @part_response_id = &flatten_responseType($responseType);
3149: foreach my $part_response_id (@part_response_id) {
3150: my ($partid,$respid) = @{ $part_response_id };
3151: my $part_resp = join('_',@{ $part_response_id });
3152: next if $seen{$partid};
3153: $seen{$partid}++;
3154: my $handgrade=$$handgrade{$part_resp};
3155: my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb);
3156: $weight{$partid} = $wgt eq '' ? '1' : $wgt;
3157:
3158: my $display_part=&get_display_part($partid,$symb);
3159: my $radio.='<table border="0"><tr>';
3160: my $ctr = 0;
3161: while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across
3162: $radio.= '<td><label><input type="radio" name="RADVAL_'.$partid.'" '.
3163: 'onclick="javascript:writePoint(\''.$partid.'\','.$weight{$partid}.
3164: ','.$ctr.')" />'.$ctr."</label></td>\n";
3165: $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
3166: $ctr++;
3167: }
3168: $radio.='</tr></table>';
3169: my $line = '<input type="text" name="TEXTVAL_'.
3170: $partid.'" size="4" '.'onChange="javascript:writePoint(\''.
3171: $partid.'\','.$weight{$partid}.',\'textval\')" /> /'.
3172: $weight{$partid}.' (problem weight)</td>'."\n";
3173: $line.= '<td><select name="SELVAL_'.$partid.'"'.
3174: 'onChange="javascript:writeRadText(\''.$partid.'\','.
3175: $weight{$partid}.')"> '.
3176: '<option selected="selected"> </option>'.
3177: '<option value="excused">'.&mt('excused').'</option>'.
3178: '<option value="reset status">'.&mt('reset status').'</option>'.
3179: '</select></td>'.
3180: '<td><label><input type="checkbox" name="FORCE_'.$partid.'" />'.&mt('Override "Correct"').'</label>';
3181: $line.='<input type="hidden" name="partid_'.
3182: $ctsparts.'" value="'.$partid.'" />'."\n";
3183: $line.='<input type="hidden" name="weight_'.
3184: $partid.'" value="'.$weight{$partid}.'" />'."\n";
3185:
3186: $result.=
3187: &Apache::loncommon::start_data_table_row()."\n".
3188: &mt('<td><b>Part:</b></td><td>[_1]</td><td><b>Points:</b></td><td>[_2]</td><td>or</td><td>[_3]</td>',$display_part,$radio,$line).
3189: &Apache::loncommon::end_data_table_row()."\n";
3190: $ctsparts++;
3191: }
3192: $result.=&Apache::loncommon::end_data_table()."\n".
3193: '<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';
3194: $result.='<input type="button" value="'.&mt('Revert to Default').'" '.
3195: 'onClick="javascript:resetEntry('.$ctsparts.');" />';
3196:
3197: #table listing all the students in a section/class
3198: #header of table
3199: $result.= '<h3>'.&mt('Assign Grade to Specific Students in '.$sectionClass,
3200: $section_display).'</h3>';
3201: $result.= &Apache::loncommon::start_data_table().
3202: &Apache::loncommon::start_data_table_header_row().
3203: '<th>'.&mt('No.').'</th>'.
3204: '<th>'.&nameUserString('header')."</th>\n";
3205: my (@parts) = sort(&getpartlist($symb));
3206: my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
3207: my @partids = ();
3208: foreach my $part (@parts) {
3209: my $display=&Apache::lonnet::metadata($url,$part.'.display');
3210: $display =~ s|^Number of Attempts|Tries<br />|; # makes the column narrower
3211: if (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }
3212: my ($partid) = &split_part_type($part);
3213: push(@partids, $partid);
3214: my $display_part=&get_display_part($partid,$symb);
3215: if ($display =~ /^Partial Credit Factor/) {
3216: $result.='<th>'.
3217: &mt('Score Part: [_1]<br /> (weight = [_2])',
3218: $display_part,$weight{$partid}).'</th>'."\n";
3219: next;
3220:
3221: } else {
3222: if ($display =~ /Problem Status/) {
3223: my $grade_status_mt = &mt('Grade Status');
3224: $display =~ s{Problem Status}{$grade_status_mt<br />};
3225: }
3226: my $part_mt = &mt('Part:');
3227: $display =~s{\[Part: \Q$partid\E\]}{$part_mt $display_part};
3228: }
3229:
3230: $result.='<th>'.$display.'</th>'."\n";
3231: }
3232: $result.=&Apache::loncommon::end_data_table_header_row();
3233:
3234: my %last_resets =
3235: &get_last_resets($symb,$env{'request.course.id'},\@partids);
3236:
3237: #get info for each student
3238: #list all the students - with points and grade status
3239: my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
3240: my $ctr = 0;
3241: foreach (sort
3242: {
3243: if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
3244: return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
3245: }
3246: return $a cmp $b;
3247: } (keys(%$fullname))) {
3248: $ctr++;
3249: $result.=&viewstudentgrade($symb,$env{'request.course.id'},
3250: $_,$$fullname{$_},\@parts,\%weight,$ctr,\%last_resets);
3251: }
3252: $result.=&Apache::loncommon::end_data_table();
3253: $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";
3254: $result.='<input type="button" value="'.&mt('Save').'" '.
3255: 'onClick="javascript:submit();" target="_self" /></form>'."\n";
3256: if (scalar(%$fullname) eq 0) {
3257: my $colspan=3+scalar(@parts);
3258: my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
3259: my $stu_status = join(' or ',&Apache::loncommon::get_env_multiple('form.Status'));
3260: $result='<span class="LC_warning">'.
3261: &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade.',
3262: $section_display, $stu_status).
3263: '</span>';
3264: }
3265: $result.=&show_grading_menu_form($symb);
3266: return $result;
3267: }
3268:
3269: #--- call by previous routine to display each student
3270: sub viewstudentgrade {
3271: my ($symb,$courseid,$student,$fullname,$parts,$weight,$ctr,$last_resets) = @_;
3272: my ($uname,$udom) = split(/:/,$student);
3273: my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
3274: my %aggregates = ();
3275: my $result=&Apache::loncommon::start_data_table_row().'<td align="right">'.
3276: '<input type="hidden" name="ctr'.($ctr-1).'" value="'.$student.'" />'.
3277: "\n".$ctr.' </td><td> '.
3278: '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
3279: '\');" target="_self">'.$fullname.'</a> '.
3280: '<span class="LC_internal_info">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</span></td>'."\n";
3281: $student=~s/:/_/; # colon doen't work in javascript for names
3282: foreach my $apart (@$parts) {
3283: my ($part,$type) = &split_part_type($apart);
3284: my $score=$record{"resource.$part.$type"};
3285: $result.='<td align="center">';
3286: my ($aggtries,$totaltries);
3287: unless (exists($aggregates{$part})) {
3288: $totaltries = $record{'resource.'.$part.'.tries'};
3289:
3290: $aggtries = $totaltries;
3291: if ($$last_resets{$part}) {
3292: $aggtries = &get_num_tries(\%record,$$last_resets{$part},
3293: $part);
3294: }
3295: $result.='<input type="hidden" name="'.
3296: 'GD_'.$student.'_'.$part.'_aggtries" value="'.$aggtries.'" />'."\n";
3297: $result.='<input type="hidden" name="'.
3298: 'GD_'.$student.'_'.$part.'_totaltries" value="'.$totaltries.'" />'."\n";
3299: $aggregates{$part} = 1;
3300: }
3301: if ($type eq 'awarded') {
3302: my $pts = $score eq '' ? '' : &compute_points($score,$$weight{$part});
3303: $result.='<input type="hidden" name="'.
3304: 'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";
3305: $result.='<input type="text" name="'.
3306: 'GD_'.$student.'_'.$part.'_awarded" '.
3307: 'onChange="javascript:changeSelect(\''.$part.'\',\''.$student.
3308: '\')" value="'.$pts.'" size="4" /></td>'."\n";
3309: } elsif ($type eq 'solved') {
3310: my ($status,$foo)=split(/_/,$score,2);
3311: $status = 'nothing' if ($status eq '');
3312: $result.='<input type="hidden" name="'.'GD_'.$student.'_'.
3313: $part.'_solved_s" value="'.$status.'" />'."\n";
3314: $result.=' <select name="'.
3315: 'GD_'.$student.'_'.$part.'_solved" '.
3316: 'onChange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n";
3317: $result.= (($status eq 'excused') ? '<option> </option><option selected="selected" value="excused">'.&mt('excused').'</option>'
3318: : '<option selected="selected"> </option><option value="excused">'.&mt('excused').'</option>')."\n";
3319: $result.='<option value="reset status">'.&mt('reset status').'</option>';
3320: $result.="</select> </td>\n";
3321: } else {
3322: $result.='<input type="hidden" name="'.
3323: 'GD_'.$student.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'.
3324: "\n";
3325: $result.='<input type="text" name="'.
3326: 'GD_'.$student.'_'.$part.'_'.$type.'" '.
3327: 'value="'.$score.'" size="4" /></td>'."\n";
3328: }
3329: }
3330: $result.=&Apache::loncommon::end_data_table_row();
3331: return $result;
3332: }
3333:
3334: #--- change scores for all the students in a section/class
3335: # record does not get update if unchanged
3336: sub editgrades {
3337: my ($request) = @_;
3338:
3339: my $symb=&get_symb($request);
3340: my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
3341: my $title='<h2>'.&mt('Current Grade Status').'</h2>';
3342: $title.='<h4>'.&mt('<b>Current Resource: </b>[_1]',$env{'form.probTitle'}).'</h4>'."\n";
3343: $title.='<h4>'.&mt('<b>Section: </b>[_1]',$section_display).'</h4>'."\n";
3344:
3345: my $result= &Apache::loncommon::start_data_table().
3346: &Apache::loncommon::start_data_table_header_row().
3347: '<th rowspan="2" valign="middle">'.&mt('No.').'</th>'.
3348: '<th rowspan="2" valign="middle">'.&nameUserString('header')."</th>\n";
3349: my %scoreptr = (
3350: 'correct' =>'correct_by_override',
3351: 'incorrect'=>'incorrect_by_override',
3352: 'excused' =>'excused',
3353: 'ungraded' =>'ungraded_attempted',
3354: 'nothing' => '',
3355: );
3356: my ($classlist,undef,$fullname) = &getclasslist($env{'form.section'},'0');
3357:
3358: my (@partid);
3359: my %weight = ();
3360: my %columns = ();
3361: my ($i,$ctr,$count,$rec_update) = (0,0,0,0);
3362:
3363: my (@parts) = sort(&getpartlist($symb));
3364: my $header;
3365: while ($ctr < $env{'form.totalparts'}) {
3366: my $partid = $env{'form.partid_'.$ctr};
3367: push @partid,$partid;
3368: $weight{$partid} = $env{'form.weight_'.$partid};
3369: $ctr++;
3370: }
3371: my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
3372: foreach my $partid (@partid) {
3373: $header .= '<th align="center">'.&mt('Old Score').'</th>'.
3374: '<th align="center">'.&mt('New Score').'</th>';
3375: $columns{$partid}=2;
3376: foreach my $stores (@parts) {
3377: my ($part,$type) = &split_part_type($stores);
3378: if ($part !~ m/^\Q$partid\E/) { next;}
3379: if ($type eq 'awarded' || $type eq 'solved') { next; }
3380: my $display=&Apache::lonnet::metadata($url,$stores.'.display');
3381: $display =~ s/\[Part: (\w)+\]//;
3382: $display =~ s/Number of Attempts/Tries/;
3383: $header .= '<th align="center">'.&mt('Old '.$display).'</th>'.
3384: '<th align="center">'.&mt('New '.$display).'</th>';
3385: $columns{$partid}+=2;
3386: }
3387: }
3388: foreach my $partid (@partid) {
3389: my $display_part=&get_display_part($partid,$symb);
3390: $result .= '<th colspan="'.$columns{$partid}.'" align="center">'.
3391: &mt('Part: [_1] (Weight = [_2])',$display_part,$weight{$partid}).
3392: '</th>';
3393:
3394: }
3395: $result .= &Apache::loncommon::end_data_table_header_row().
3396: &Apache::loncommon::start_data_table_header_row().
3397: $header.
3398: &Apache::loncommon::end_data_table_header_row();
3399: my @noupdate;
3400: my ($updateCtr,$noupdateCtr) = (1,1);
3401: for ($i=0; $i<$env{'form.total'}; $i++) {
3402: my $line;
3403: my $user = $env{'form.ctr'.$i};
3404: my ($uname,$udom)=split(/:/,$user);
3405: my %newrecord;
3406: my $updateflag = 0;
3407: $line .= '<td>'.&nameUserString(undef,$$fullname{$user},$uname,$udom).'</td>';
3408: my $usec=$classlist->{"$uname:$udom"}[5];
3409: if (!&canmodify($usec)) {
3410: my $numcols=scalar(@partid)*4+2;
3411: push(@noupdate,
3412: $line."<td colspan=\"$numcols\"><span class=\"LC_warning\">".
3413: &mt('Not allowed to modify student')."</span></td></tr>");
3414: next;
3415: }
3416: my %aggregate = ();
3417: my $aggregateflag = 0;
3418: $user=~s/:/_/; # colon doen't work in javascript for names
3419: foreach (@partid) {
3420: my $old_aw = $env{'form.GD_'.$user.'_'.$_.'_awarded_s'};
3421: my $old_part_pcr = $old_aw/($weight{$_} ne '0' ? $weight{$_}:1);
3422: my $old_part = $old_aw eq '' ? '' : $old_part_pcr;
3423: my $old_score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
3424: my $awarded = $env{'form.GD_'.$user.'_'.$_.'_awarded'};
3425: my $pcr = $awarded/($weight{$_} ne '0' ? $weight{$_} : 1);
3426: my $partial = $awarded eq '' ? '' : $pcr;
3427: my $score;
3428: if ($partial eq '') {
3429: $score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
3430: } elsif ($partial > 0) {
3431: $score = 'correct_by_override';
3432: } elsif ($partial == 0) {
3433: $score = 'incorrect_by_override';
3434: }
3435: my $dropMenu = $env{'form.GD_'.$user.'_'.$_.'_solved'};
3436: $score = 'excused' if (($dropMenu eq 'excused') && ($score ne 'excused'));
3437:
3438: $newrecord{'resource.'.$_.'.regrader'}=
3439: "$env{'user.name'}:$env{'user.domain'}";
3440: if ($dropMenu eq 'reset status' &&
3441: $old_score ne '') { # ignore if no previous attempts => nothing to reset
3442: $newrecord{'resource.'.$_.'.tries'} = '';
3443: $newrecord{'resource.'.$_.'.solved'} = '';
3444: $newrecord{'resource.'.$_.'.award'} = '';
3445: $newrecord{'resource.'.$_.'.awarded'} = '';
3446: $updateflag = 1;
3447: if ($env{'form.GD_'.$user.'_'.$_.'_aggtries'} > 0) {
3448: my $aggtries = $env{'form.GD_'.$user.'_'.$_.'_aggtries'};
3449: my $totaltries = $env{'form.GD_'.$user.'_'.$_.'_totaltries'};
3450: my $solvedstatus = $env{'form.GD_'.$user.'_'.$_.'_solved_s'};
3451: &decrement_aggs($symb,$_,\%aggregate,$aggtries,$totaltries,$solvedstatus);
3452: $aggregateflag = 1;
3453: }
3454: } elsif (!($old_part eq $partial && $old_score eq $score)) {
3455: $updateflag = 1;
3456: $newrecord{'resource.'.$_.'.awarded'} = $partial if $partial ne '';
3457: $newrecord{'resource.'.$_.'.solved'} = $score;
3458: $rec_update++;
3459: }
3460:
3461: $line .= '<td align="center">'.$old_aw.' </td>'.
3462: '<td align="center">'.$awarded.
3463: ($score eq 'excused' ? $score : '').' </td>';
3464:
3465:
3466: my $partid=$_;
3467: foreach my $stores (@parts) {
3468: my ($part,$type) = &split_part_type($stores);
3469: if ($part !~ m/^\Q$partid\E/) { next;}
3470: if ($type eq 'awarded' || $type eq 'solved') { next; }
3471: my $old_aw = $env{'form.GD_'.$user.'_'.$part.'_'.$type.'_s'};
3472: my $awarded = $env{'form.GD_'.$user.'_'.$part.'_'.$type};
3473: if ($awarded ne '' && $awarded ne $old_aw) {
3474: $newrecord{'resource.'.$part.'.'.$type}= $awarded;
3475: $newrecord{'resource.'.$part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
3476: $updateflag=1;
3477: }
3478: $line .= '<td align="center">'.$old_aw.' </td>'.
3479: '<td align="center">'.$awarded.' </td>';
3480: }
3481: }
3482: $line.="\n";
3483:
3484: my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
3485: my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
3486:
3487: if ($updateflag) {
3488: $count++;
3489: &Apache::lonnet::cstore(\%newrecord,$symb,$env{'request.course.id'},
3490: $udom,$uname);
3491:
3492: if (&Apache::bridgetask::in_queue('gradingqueue',$symb,$cdom,
3493: $cnum,$udom,$uname)) {
3494: # need to figure out if should be in queue.
3495: my %record =
3496: &Apache::lonnet::restore($symb,$env{'request.course.id'},
3497: $udom,$uname);
3498: my $all_graded = 1;
3499: my $none_graded = 1;
3500: foreach my $part (@parts) {
3501: if ( $record{'resource.'.$part.'.awarded'} eq '' ) {
3502: $all_graded = 0;
3503: } else {
3504: $none_graded = 0;
3505: }
3506: }
3507:
3508: if ($all_graded || $none_graded) {
3509: &Apache::bridgetask::remove_from_queue('gradingqueue',
3510: $symb,$cdom,$cnum,
3511: $udom,$uname);
3512: }
3513: }
3514:
3515: $result.=&Apache::loncommon::start_data_table_row().
3516: '<td align="right"> '.$updateCtr.' </td>'.$line.
3517: &Apache::loncommon::end_data_table_row();
3518: $updateCtr++;
3519: } else {
3520: push(@noupdate,
3521: '<td align="right"> '.$noupdateCtr.' </td>'.$line);
3522: $noupdateCtr++;
3523: }
3524: if ($aggregateflag) {
3525: &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
3526: $cdom,$cnum);
3527: }
3528: }
3529: if (@noupdate) {
3530: # my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3;
3531: my $numcols=scalar(@partid)*4+2;
3532: $result .= &Apache::loncommon::start_data_table_row('LC_empty_row').
3533: '<td align="center" colspan="'.$numcols.'">'.
3534: &mt('No Changes Occurred For the Students Below').
3535: '</td>'.
3536: &Apache::loncommon::end_data_table_row();
3537: foreach my $line (@noupdate) {
3538: $result.=
3539: &Apache::loncommon::start_data_table_row().
3540: $line.
3541: &Apache::loncommon::end_data_table_row();
3542: }
3543: }
3544: $result .= &Apache::loncommon::end_data_table().
3545: &show_grading_menu_form($symb);
3546: my $msg = '<p><b>'.
3547: &mt('Number of records updated = [_1] for [quant,_2,student].',
3548: $rec_update,$count).'</b><br />'.
3549: '<b>'.&mt('Total number of students = [_1]',$env{'form.total'}).
3550: '</b></p>';
3551: return $title.$msg.$result;
3552: }
3553:
3554: sub split_part_type {
3555: my ($partstr) = @_;
3556: my ($temp,@allparts)=split(/_/,$partstr);
3557: my $type=pop(@allparts);
3558: my $part=join('_',@allparts);
3559: return ($part,$type);
3560: }
3561:
3562: #------------- end of section for handling grading by section/class ---------
3563: #
3564: #----------------------------------------------------------------------------
3565:
3566:
3567: #----------------------------------------------------------------------------
3568: #
3569: #-------------------------- Next few routines handles grading by csv upload
3570: #
3571: #--- Javascript to handle csv upload
3572: sub csvupload_javascript_reverse_associate {
3573: my $error1=&mt('You need to specify the username or ID');
3574: my $error2=&mt('You need to specify at least one grading field');
3575: return(<<ENDPICK);
3576: function verify(vf) {
3577: var foundsomething=0;
3578: var founduname=0;
3579: var foundID=0;
3580: for (i=0;i<=vf.nfields.value;i++) {
3581: tw=eval('vf.f'+i+'.selectedIndex');
3582: if (i==0 && tw!=0) { foundID=1; }
3583: if (i==1 && tw!=0) { founduname=1; }
3584: if (i!=0 && i!=1 && i!=2 && tw!=0) { foundsomething=1; }
3585: }
3586: if (founduname==0 && foundID==0) {
3587: alert('$error1');
3588: return;
3589: }
3590: if (foundsomething==0) {
3591: alert('$error2');
3592: return;
3593: }
3594: vf.submit();
3595: }
3596: function flip(vf,tf) {
3597: var nw=eval('vf.f'+tf+'.selectedIndex');
3598: var i;
3599: for (i=0;i<=vf.nfields.value;i++) {
3600: //can not pick the same destination field for both name and domain
3601: if (((i ==0)||(i ==1)) &&
3602: ((tf==0)||(tf==1)) &&
3603: (i!=tf) &&
3604: (eval('vf.f'+i+'.selectedIndex')==nw)) {
3605: eval('vf.f'+i+'.selectedIndex=0;')
3606: }
3607: }
3608: }
3609: ENDPICK
3610: }
3611:
3612: sub csvupload_javascript_forward_associate {
3613: my $error1=&mt('You need to specify the username or ID');
3614: my $error2=&mt('You need to specify at least one grading field');
3615: return(<<ENDPICK);
3616: function verify(vf) {
3617: var foundsomething=0;
3618: var founduname=0;
3619: var foundID=0;
3620: for (i=0;i<=vf.nfields.value;i++) {
3621: tw=eval('vf.f'+i+'.selectedIndex');
3622: if (tw==1) { foundID=1; }
3623: if (tw==2) { founduname=1; }
3624: if (tw>3) { foundsomething=1; }
3625: }
3626: if (founduname==0 && foundID==0) {
3627: alert('$error1');
3628: return;
3629: }
3630: if (foundsomething==0) {
3631: alert('$error2');
3632: return;
3633: }
3634: vf.submit();
3635: }
3636: function flip(vf,tf) {
3637: var nw=eval('vf.f'+tf+'.selectedIndex');
3638: var i;
3639: //can not pick the same destination field twice
3640: for (i=0;i<=vf.nfields.value;i++) {
3641: if ((i!=tf) && (eval('vf.f'+i+'.selectedIndex')==nw)) {
3642: eval('vf.f'+i+'.selectedIndex=0;')
3643: }
3644: }
3645: }
3646: ENDPICK
3647: }
3648:
3649: sub csvuploadmap_header {
3650: my ($request,$symb,$datatoken,$distotal)= @_;
3651: my $javascript;
3652: if ($env{'form.upfile_associate'} eq 'reverse') {
3653: $javascript=&csvupload_javascript_reverse_associate();
3654: } else {
3655: $javascript=&csvupload_javascript_forward_associate();
3656: }
3657:
3658: my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
3659: my $checked=(($env{'form.noFirstLine'})?' checked="checked"':'');
3660: my $ignore=&mt('Ignore First Line');
3661: $symb = &Apache::lonenc::check_encrypt($symb);
3662: $request->print(<<ENDPICK);
3663: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
3664: <h3><span class="LC_info">Uploading Class Grades</span></h3>
3665: $result
3666: <hr />
3667: <h3>Identify fields</h3>
3668: Total number of records found in file: $distotal <hr />
3669: Enter as many fields as you can. The system will inform you and bring you back
3670: to this page if the data selected is insufficient to run your class.<hr />
3671: <input type="button" value="Reverse Association" onClick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" />
3672: <label><input type="checkbox" name="noFirstLine" $checked />$ignore</label>
3673: <input type="hidden" name="associate" value="" />
3674: <input type="hidden" name="phase" value="three" />
3675: <input type="hidden" name="datatoken" value="$datatoken" />
3676: <input type="hidden" name="fileupload" value="$env{'form.fileupload'}" />
3677: <input type="hidden" name="upfiletype" value="$env{'form.upfiletype'}" />
3678: <input type="hidden" name="upfile_associate"
3679: value="$env{'form.upfile_associate'}" />
3680: <input type="hidden" name="symb" value="$symb" />
3681: <input type="hidden" name="saveState" value="$env{'form.saveState'}" />
3682: <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
3683: <input type="hidden" name="command" value="csvuploadoptions" />
3684: <hr />
3685: <script type="text/javascript" language="Javascript">
3686: $javascript
3687: </script>
3688: ENDPICK
3689: return '';
3690:
3691: }
3692:
3693: sub csvupload_fields {
3694: my ($symb) = @_;
3695: my (@parts) = &getpartlist($symb);
3696: my @fields=(['ID','Student ID'],
3697: ['username','Student Username'],
3698: ['domain','Student Domain']);
3699: my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
3700: foreach my $part (sort(@parts)) {
3701: my @datum;
3702: my $display=&Apache::lonnet::metadata($url,$part.'.display');
3703: my $name=$part;
3704: if (!$display) { $display = $name; }
3705: @datum=($name,$display);
3706: if ($name=~/^stores_(.*)_awarded/) {
3707: push(@fields,['stores_'.$1.'_points',"Points [Part: $1]"]);
3708: }
3709: push(@fields,\@datum);
3710: }
3711: return (@fields);
3712: }
3713:
3714: sub csvuploadmap_footer {
3715: my ($request,$i,$keyfields) =@_;
3716: $request->print(<<ENDPICK);
3717: </table>
3718: <input type="hidden" name="nfields" value="$i" />
3719: <input type="hidden" name="keyfields" value="$keyfields" />
3720: <input type="button" onClick="javascript:verify(this.form)" value="Assign Grades" /><br />
3721: </form>
3722: ENDPICK
3723: }
3724:
3725: sub checkforfile_js {
3726: my $result =<<CSVFORMJS;
3727: <script type="text/javascript" language="javascript">
3728: function checkUpload(formname) {
3729: if (formname.upfile.value == "") {
3730: alert("Please use the browse button to select a file from your local directory.");
3731: return false;
3732: }
3733: formname.submit();
3734: }
3735: </script>
3736: CSVFORMJS
3737: return $result;
3738: }
3739:
3740: sub upcsvScores_form {
3741: my ($request) = shift;
3742: my ($symb)=&get_symb($request);
3743: if (!$symb) {return '';}
3744: my $result=&checkforfile_js();
3745: $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
3746: my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});
3747: $result.=$table;
3748: $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";
3749: $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";
3750: $result.=' <b>'.&mt('Specify a file containing the class scores for current resource').
3751: '.</b></td></tr>'."\n";
3752: $result.='<tr bgcolor=#ffffe6><td>'."\n";
3753: my $upload=&mt("Upload Scores");
3754: my $upfile_select=&Apache::loncommon::upfile_select_html();
3755: my $ignore=&mt('Ignore First Line');
3756: $symb = &Apache::lonenc::check_encrypt($symb);
3757: $result.=<<ENDUPFORM;
3758: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
3759: <input type="hidden" name="symb" value="$symb" />
3760: <input type="hidden" name="command" value="csvuploadmap" />
3761: <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
3762: <input type="hidden" name="saveState" value="$env{'form.saveState'}" />
3763: $upfile_select
3764: <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="$upload" />
3765: <label><input type="checkbox" name="noFirstLine" />$ignore</label>
3766: </form>
3767: ENDUPFORM
3768: $result.=&Apache::loncommon::help_open_topic("Course_Convert_To_CSV",
3769: &mt("How do I create a CSV file from a spreadsheet"))
3770: .'</td></tr></table>'."\n";
3771: $result.='</td></tr></table><br /><br />'."\n";
3772: $result.=&show_grading_menu_form($symb);
3773: return $result;
3774: }
3775:
3776:
3777: sub csvuploadmap {
3778: my ($request)= @_;
3779: my ($symb)=&get_symb($request);
3780: if (!$symb) {return '';}
3781:
3782: my $datatoken;
3783: if (!$env{'form.datatoken'}) {
3784: $datatoken=&Apache::loncommon::upfile_store($request);
3785: } else {
3786: $datatoken=$env{'form.datatoken'};
3787: &Apache::loncommon::load_tmp_file($request);
3788: }
3789: my @records=&Apache::loncommon::upfile_record_sep();
3790: if ($env{'form.noFirstLine'}) { shift(@records); }
3791: &csvuploadmap_header($request,$symb,$datatoken,$#records+1);
3792: my ($i,$keyfields);
3793: if (@records) {
3794: my @fields=&csvupload_fields($symb);
3795:
3796: if ($env{'form.upfile_associate'} eq 'reverse') {
3797: &Apache::loncommon::csv_print_samples($request,\@records);
3798: $i=&Apache::loncommon::csv_print_select_table($request,\@records,
3799: \@fields);
3800: foreach (@fields) { $keyfields.=$_->[0].','; }
3801: chop($keyfields);
3802: } else {
3803: unshift(@fields,['none','']);
3804: $i=&Apache::loncommon::csv_samples_select_table($request,\@records,
3805: \@fields);
3806: foreach my $rec (@records) {
3807: my %temp = &Apache::loncommon::record_sep($rec);
3808: if (%temp) {
3809: $keyfields=join(',',sort(keys(%temp)));
3810: last;
3811: }
3812: }
3813: }
3814: }
3815: &csvuploadmap_footer($request,$i,$keyfields);
3816: $request->print(&show_grading_menu_form($symb));
3817:
3818: return '';
3819: }
3820:
3821: sub csvuploadoptions {
3822: my ($request)= @_;
3823: my ($symb)=&get_symb($request);
3824: my $checked=(($env{'form.noFirstLine'})?'1':'0');
3825: my $ignore=&mt('Ignore First Line');
3826: $request->print(<<ENDPICK);
3827: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
3828: <h3><span class="LC_info">Uploading Class Grade Options</span></h3>
3829: <input type="hidden" name="command" value="csvuploadassign" />
3830: <!--
3831: <p>
3832: <label>
3833: <input type="checkbox" name="show_full_results" />
3834: Show a table of all changes
3835: </label>
3836: </p>
3837: -->
3838: <p>
3839: <label>
3840: <input type="checkbox" name="overwite_scores" checked="checked" />
3841: Overwrite any existing score
3842: </label>
3843: </p>
3844: ENDPICK
3845: my %fields=&get_fields();
3846: if (!defined($fields{'domain'})) {
3847: my $domform = &Apache::loncommon::select_dom_form($env{'request.role.domain'},'default_domain');
3848: $request->print("\n<p> Users are in domain: ".$domform."</p>\n");
3849: }
3850: foreach my $key (sort(keys(%env))) {
3851: if ($key !~ /^form\.(.*)$/) { next; }
3852: my $cleankey=$1;
3853: if ($cleankey eq 'command') { next; }
3854: $request->print('<input type="hidden" name="'.$cleankey.
3855: '" value="'.$env{$key}.'" />'."\n");
3856: }
3857: # FIXME do a check for any duplicated user ids...
3858: # FIXME do a check for any invalid user ids?...
3859: $request->print('<input type="submit" value="Assign Grades" /><br />
3860: <hr /></form>'."\n");
3861: $request->print(&show_grading_menu_form($symb));
3862: return '';
3863: }
3864:
3865: sub get_fields {
3866: my %fields;
3867: my @keyfields = split(/\,/,$env{'form.keyfields'});
3868: for (my $i=0; $i<=$env{'form.nfields'}; $i++) {
3869: if ($env{'form.upfile_associate'} eq 'reverse') {
3870: if ($env{'form.f'.$i} ne 'none') {
3871: $fields{$keyfields[$i]}=$env{'form.f'.$i};
3872: }
3873: } else {
3874: if ($env{'form.f'.$i} ne 'none') {
3875: $fields{$env{'form.f'.$i}}=$keyfields[$i];
3876: }
3877: }
3878: }
3879: return %fields;
3880: }
3881:
3882: sub csvuploadassign {
3883: my ($request)= @_;
3884: my ($symb)=&get_symb($request);
3885: if (!$symb) {return '';}
3886: my $error_msg = '';
3887: &Apache::loncommon::load_tmp_file($request);
3888: my @gradedata = &Apache::loncommon::upfile_record_sep();
3889: if ($env{'form.noFirstLine'}) { shift(@gradedata); }
3890: my %fields=&get_fields();
3891: $request->print('<h3>Assigning Grades</h3>');
3892: my $courseid=$env{'request.course.id'};
3893: my ($classlist) = &getclasslist('all',0);
3894: my @notallowed;
3895: my @skipped;
3896: my $countdone=0;
3897: foreach my $grade (@gradedata) {
3898: my %entries=&Apache::loncommon::record_sep($grade);
3899: my $domain;
3900: if ($entries{$fields{'domain'}}) {
3901: $domain=$entries{$fields{'domain'}};
3902: } else {
3903: $domain=$env{'form.default_domain'};
3904: }
3905: $domain=~s/\s//g;
3906: my $username=$entries{$fields{'username'}};
3907: $username=~s/\s//g;
3908: if (!$username) {
3909: my $id=$entries{$fields{'ID'}};
3910: $id=~s/\s//g;
3911: my %ids=&Apache::lonnet::idget($domain,$id);
3912: $username=$ids{$id};
3913: }
3914: if (!exists($$classlist{"$username:$domain"})) {
3915: my $id=$entries{$fields{'ID'}};
3916: $id=~s/\s//g;
3917: if ($id) {
3918: push(@skipped,"$id:$domain");
3919: } else {
3920: push(@skipped,"$username:$domain");
3921: }
3922: next;
3923: }
3924: my $usec=$classlist->{"$username:$domain"}[5];
3925: if (!&canmodify($usec)) {
3926: push(@notallowed,"$username:$domain");
3927: next;
3928: }
3929: my %points;
3930: my %grades;
3931: foreach my $dest (keys(%fields)) {
3932: if ($dest eq 'ID' || $dest eq 'username' ||
3933: $dest eq 'domain') { next; }
3934: if ($entries{$fields{$dest}} =~ /^\s*$/) { next; }
3935: if ($dest=~/stores_(.*)_points/) {
3936: my $part=$1;
3937: my $wgt =&Apache::lonnet::EXT('resource.'.$part.'.weight',
3938: $symb,$domain,$username);
3939: if ($wgt) {
3940: $entries{$fields{$dest}}=~s/\s//g;
3941: my $pcr=$entries{$fields{$dest}} / $wgt;
3942: my $award=($pcr == 0) ? 'incorrect_by_override'
3943: : 'correct_by_override';
3944: $grades{"resource.$part.awarded"}=$pcr;
3945: $grades{"resource.$part.solved"}=$award;
3946: $points{$part}=1;
3947: } else {
3948: $error_msg = "<br />" .
3949: &mt("Some point values were assigned"
3950: ." for problems with a weight "
3951: ."of zero. These values were "
3952: ."ignored.");
3953: }
3954: } else {
3955: if ($dest=~/stores_(.*)_awarded/) { if ($points{$1}) {next;} }
3956: if ($dest=~/stores_(.*)_solved/) { if ($points{$1}) {next;} }
3957: my $store_key=$dest;
3958: $store_key=~s/^stores/resource/;
3959: $store_key=~s/_/\./g;
3960: $grades{$store_key}=$entries{$fields{$dest}};
3961: }
3962: }
3963: if (! %grades) { push(@skipped,"$username:$domain no data to save"); }
3964: $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
3965: my $result=&Apache::lonnet::cstore(\%grades,$symb,
3966: $env{'request.course.id'},
3967: $domain,$username);
3968: if ($result eq 'ok') {
3969: $request->print('.');
3970: } else {
3971: $request->print("<p>
3972: <span class=\"LC_error\">
3973: Failed to save student $username:$domain.
3974: Message when trying to save was ($result)
3975: </span>
3976: </p>" );
3977: }
3978: $request->rflush();
3979: $countdone++;
3980: }
3981: $request->print("<br />Saved $countdone students\n");
3982: if (@skipped) {
3983: $request->print('<p><h4><b>Skipped Students</b></h4></p>');
3984: foreach my $student (@skipped) { $request->print("$student<br />\n"); }
3985: }
3986: if (@notallowed) {
3987: $request->print('<p><span class="LC_error">Students Not Allowed to Modify</span></p>');
3988: foreach my $student (@notallowed) { $request->print("$student<br />\n"); }
3989: }
3990: $request->print("<br />\n");
3991: $request->print(&show_grading_menu_form($symb));
3992: return $error_msg;
3993: }
3994: #------------- end of section for handling csv file upload ---------
3995: #
3996: #-------------------------------------------------------------------
3997: #
3998: #-------------- Next few routines handle grading by page/sequence
3999: #
4000: #--- Select a page/sequence and a student to grade
4001: sub pickStudentPage {
4002: my ($request) = shift;
4003:
4004: $request->print(<<LISTJAVASCRIPT);
4005: <script type="text/javascript" language="javascript">
4006:
4007: function checkPickOne(formname) {
4008: if (radioSelection(formname.student) == null) {
4009: alert("Please select the student you wish to grade.");
4010: return;
4011: }
4012: ptr = pullDownSelection(formname.selectpage);
4013: formname.page.value = formname["page"+ptr].value;
4014: formname.title.value = formname["title"+ptr].value;
4015: formname.submit();
4016: }
4017:
4018: </script>
4019: LISTJAVASCRIPT
4020: &commonJSfunctions($request);
4021: my ($symb) = &get_symb($request);
4022: my $cdom = $env{"course.$env{'request.course.id'}.domain"};
4023: my $cnum = $env{"course.$env{'request.course.id'}.num"};
4024: my $getsec = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
4025:
4026: my $result='<h3><span class="LC_info"> '.
4027: &mt('Manual Grading by Page or Sequence').'</span></h3>';
4028:
4029: $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";
4030: my ($titles,$symbx) = &getSymbMap();
4031: my ($curpage) =&Apache::lonnet::decode_symb($symb);
4032: # my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb);
4033: # my $type=($curpage =~ /\.(page|sequence)/);
4034: my $select = '<select name="selectpage">'."\n";
4035: my $ctr=0;
4036: foreach (@$titles) {
4037: my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
4038: $select.='<option value="'.$ctr.'" '.
4039: ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
4040: '>'.$showtitle.'</option>'."\n";
4041: $ctr++;
4042: }
4043: $select.= '</select>';
4044: $result.=&mt(' <b>Problems from:</b> [_1]',$select)."<br />\n";
4045:
4046: $ctr=0;
4047: foreach (@$titles) {
4048: my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
4049: $result.='<input type="hidden" name="page'.$ctr.'" value="'.$$symbx{$_}.'" />'."\n";
4050: $result.='<input type="hidden" name="title'.$ctr.'" value="'.$showtitle.'" />'."\n";
4051: $ctr++;
4052: }
4053: $result.='<input type="hidden" name="page" />'."\n".
4054: '<input type="hidden" name="title" />'."\n";
4055:
4056: my $options =
4057: '<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n".
4058: '<label><input type="radio" name="vProb" value="yes" /> '.&mt('yes').' </label>'."<br />\n";
4059: $result.=' '.&mt('<b>View Problems Text: </b> [_1]',$options);
4060:
4061: $options =
4062: '<label><input type="radio" name="lastSub" value="none" /> '.&mt('none').' </label>'."\n".
4063: '<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> '.&mt('by dates and submissions').'</label>'."\n".
4064: '<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').' </label>'."\n";
4065: $result.=' '.&mt('<b>Submission Details: </b>[_1]',$options);
4066:
4067: $result.=&build_section_inputs();
4068: my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
4069: $result.='<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n".
4070: '<input type="hidden" name="command" value="displayPage" />'."\n".
4071: '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
4072: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n";
4073:
4074: $result.=' '.&mt('<b>Use CODE: [_1] </b>',
4075: '<input type="text" name="CODE" value="" />').
4076: '<br />'."\n";
4077:
4078: $result.=' <input type="button" '.
4079: 'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next->').'" /><br />'."\n";
4080:
4081: $request->print($result);
4082:
4083: my $studentTable.=' <b>'.&mt('Select a student you wish to grade and then click on the Next button.').'</b><br />'.
4084: &Apache::loncommon::start_data_table().
4085: &Apache::loncommon::start_data_table_header_row().
4086: '<th align="right"> '.&mt('No.').'</th>'.
4087: '<th>'.&nameUserString('header').'</th>'.
4088: '<th align="right"> '.&mt('No.').'</th>'.
4089: '<th>'.&nameUserString('header').'</th>'.
4090: &Apache::loncommon::end_data_table_header_row();
4091:
4092: my (undef,undef,$fullname) = &getclasslist($getsec,'1');
4093: my $ptr = 1;
4094: foreach my $student (sort
4095: {
4096: if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
4097: return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
4098: }
4099: return $a cmp $b;
4100: } (keys(%$fullname))) {
4101: my ($uname,$udom) = split(/:/,$student);
4102: $studentTable.=($ptr%2==1 ? &Apache::loncommon::start_data_table_row()
4103: : '</td>');
4104: $studentTable.='<td align="right">'.$ptr.' </td>';
4105: $studentTable.='<td> <label><input type="radio" name="student" value="'.$student.'" /> '
4106: .&nameUserString(undef,$$fullname{$student},$uname,$udom)."</label>\n";
4107: $studentTable.=
4108: ($ptr%2 == 0 ? '</td>'.&Apache::loncommon::end_data_table_row()
4109: : '');
4110: $ptr++;
4111: }
4112: if ($ptr%2 == 0) {
4113: $studentTable.='</td><td> </td><td> </td>'.
4114: &Apache::loncommon::end_data_table_row();
4115: }
4116: $studentTable.=&Apache::loncommon::end_data_table()."\n";
4117: $studentTable.='<input type="button" '.
4118: 'onClick="javascript:checkPickOne(this.form);" value="'.&mt('Next->').'" /></form>'."\n";
4119:
4120: $studentTable.=&show_grading_menu_form($symb);
4121: $request->print($studentTable);
4122:
4123: return '';
4124: }
4125:
4126: sub getSymbMap {
4127: my $navmap = Apache::lonnavmaps::navmap->new();
4128:
4129: my %symbx = ();
4130: my @titles = ();
4131: my $minder = 0;
4132:
4133: # Gather every sequence that has problems.
4134: my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); },
4135: 1,0,1);
4136: for my $sequence ($navmap->getById('0.0'), @sequences) {
4137: if ($navmap->hasResource($sequence, sub { shift->is_problem(); }, 0) ) {
4138: my $title = $minder.'.'.
4139: &HTML::Entities::encode($sequence->compTitle(),'"\'&');
4140: push(@titles, $title); # minder in case two titles are identical
4141: $symbx{$title} = &HTML::Entities::encode($sequence->symb(),'"\'&');
4142: $minder++;
4143: }
4144: }
4145: return \@titles,\%symbx;
4146: }
4147:
4148: #
4149: #--- Displays a page/sequence w/wo problems, w/wo submissions
4150: sub displayPage {
4151: my ($request) = shift;
4152:
4153: my ($symb) = &get_symb($request);
4154: my $cdom = $env{"course.$env{'request.course.id'}.domain"};
4155: my $cnum = $env{"course.$env{'request.course.id'}.num"};
4156: my $getsec = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
4157: my $pageTitle = $env{'form.page'};
4158: my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
4159: my ($uname,$udom) = split(/:/,$env{'form.student'});
4160: my $usec=$classlist->{$env{'form.student'}}[5];
4161:
4162: #need to make sure we have the correct data for later EXT calls,
4163: #thus invalidate the cache
4164: &Apache::lonnet::devalidatecourseresdata(
4165: $env{'course.'.$env{'request.course.id'}.'.num'},
4166: $env{'course.'.$env{'request.course.id'}.'.domain'});
4167: &Apache::lonnet::clear_EXT_cache_status();
4168:
4169: if (!&canview($usec)) {
4170: $request->print('<span class="LC_warning">'.&mt('Unable to view requested student. ([_1])',$env{'form.student'}).'</span>');
4171: $request->print(&show_grading_menu_form($symb));
4172: return;
4173: }
4174: my $result='<h3><span class="LC_info"> '.$env{'form.title'}.'</span></h3>';
4175: $result.='<h3> '.&mt('Student: [_1]',&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom)).
4176: '</h3>'."\n";
4177: $env{'form.CODE'} = uc($env{'form.CODE'});
4178: if (&Apache::lonnet::validCODE(uc($env{'form.CODE'}))) {
4179: $result.='<h3> '.&mt('CODE: [_1]',$env{'form.CODE'}).'</h3>'."\n";
4180: } else {
4181: delete($env{'form.CODE'});
4182: }
4183: &sub_page_js($request);
4184: $request->print($result);
4185:
4186: my $navmap = Apache::lonnavmaps::navmap->new();
4187: my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'});
4188: my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
4189: if (!$map) {
4190: $request->print('<span class="LC_warning">'.&mt('Unable to view requested sequence. ([_1])',$resUrl).'</span>');
4191: $request->print(&show_grading_menu_form($symb));
4192: return;
4193: }
4194: my $iterator = $navmap->getIterator($map->map_start(),
4195: $map->map_finish());
4196:
4197: my $studentTable='<form action="/adm/grades" method="post" name="gradePage">'."\n".
4198: '<input type="hidden" name="command" value="gradeByPage" />'."\n".
4199: '<input type="hidden" name="fullname" value="'.$$fullname{$env{'form.student'}}.'" />'."\n".
4200: '<input type="hidden" name="student" value="'.$env{'form.student'}.'" />'."\n".
4201: '<input type="hidden" name="page" value="'.$pageTitle.'" />'."\n".
4202: '<input type="hidden" name="title" value="'.$env{'form.title'}.'" />'."\n".
4203: '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
4204: '<input type="hidden" name="overRideScore" value="no" />'."\n".
4205: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n";
4206:
4207: if (defined($env{'form.CODE'})) {
4208: $studentTable.=
4209: '<input type="hidden" name="CODE" value="'.$env{'form.CODE'}.'" />'."\n";
4210: }
4211: my $checkIcon = '<img alt="'.&mt('Check Mark').
4212: '" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
4213:
4214: $studentTable.=' '.&mt('<b>Note:</b> Problems graded correct by the computer are marked with a [_1] symbol.',$checkIcon)."\n".
4215: &Apache::loncommon::start_data_table().
4216: &Apache::loncommon::start_data_table_header_row().
4217: '<th align="center"> Prob. </th>'.
4218: '<th> '.($env{'form.vProb'} eq 'no' ? &mt('Title') : &mt('Problem Text')).'/'.&mt('Grade').'</th>'.
4219: &Apache::loncommon::end_data_table_header_row();
4220:
4221: &Apache::lonxml::clear_problem_counter();
4222: my ($depth,$question,$prob) = (1,1,1);
4223: $iterator->next(); # skip the first BEGIN_MAP
4224: my $curRes = $iterator->next(); # for "current resource"
4225: while ($depth > 0) {
4226: if($curRes == $iterator->BEGIN_MAP) { $depth++; }
4227: if($curRes == $iterator->END_MAP) { $depth--; }
4228:
4229: if (ref($curRes) && $curRes->is_problem()) {
4230: my $parts = $curRes->parts();
4231: my $title = $curRes->compTitle();
4232: my $symbx = $curRes->symb();
4233: $studentTable.=
4234: &Apache::loncommon::start_data_table_row().
4235: '<td align="center" valign="top" >'.$prob.
4236: (scalar(@{$parts}) == 1 ? ''
4237: : '<br />('.&mt('[_1] parts)',
4238: scalar(@{$parts}))
4239: ).
4240: '</td>';
4241: $studentTable.='<td valign="top">';
4242: my %form = ('CODE' => $env{'form.CODE'},);
4243: if ($env{'form.vProb'} eq 'yes' ) {
4244: $studentTable.=&show_problem($request,$symbx,$uname,$udom,1,
4245: undef,'both',\%form);
4246: } else {
4247: my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'},%form);
4248: $companswer =~ s|<form(.*?)>||g;
4249: $companswer =~ s|</form>||g;
4250: # while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a>
4251: # $companswer =~ s/$1/ /ms;
4252: # $request->print('match='.$1."<br />\n");
4253: # }
4254: # $companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;
4255: $studentTable.=' <b>'.$title.'</b> <br /> '.&mt('<b>Correct answer:</b><br />[_1]',$companswer);
4256: }
4257:
4258: my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
4259:
4260: if ($env{'form.lastSub'} eq 'datesub') {
4261: if ($record{'version'} eq '') {
4262: $studentTable.='<br /> <span class="LC_warning">'.&mt('No recorded submission for this problem.').'</span><br />';
4263: } else {
4264: my %responseType = ();
4265: foreach my $partid (@{$parts}) {
4266: my @responseIds =$curRes->responseIds($partid);
4267: my @responseType =$curRes->responseType($partid);
4268: my %responseIds;
4269: for (my $i=0;$i<=$#responseIds;$i++) {
4270: $responseIds{$responseIds[$i]}=$responseType[$i];
4271: }
4272: $responseType{$partid} = \%responseIds;
4273: }
4274: $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom);
4275:
4276: }
4277: } elsif ($env{'form.lastSub'} eq 'all') {
4278: my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
4279: $studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom,
4280: $env{'request.course.id'},
4281: '','.submission');
4282:
4283: }
4284: if (&canmodify($usec)) {
4285: foreach my $partid (@{$parts}) {
4286: $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record);
4287: $studentTable.='<input type="hidden" name="q_'.$question.'" value="'.$partid.'" />'."\n";
4288: $question++;
4289: }
4290: $prob++;
4291: }
4292: $studentTable.='</td></tr>';
4293:
4294: }
4295: $curRes = $iterator->next();
4296: }
4297:
4298: $studentTable.='</table>'."\n".
4299: '<input type="button" value="'.&mt('Save').'" '.
4300: 'onClick="javascript:checkSubmitPage(this.form,'.$question.');" />'.
4301: '</form>'."\n";
4302: $studentTable.=&show_grading_menu_form($symb);
4303: $request->print($studentTable);
4304:
4305: return '';
4306: }
4307:
4308: sub displaySubByDates {
4309: my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;
4310: my $isCODE=0;
4311: my $isTask = ($symb =~/\.task$/);
4312: if (exists($record->{'resource.CODE'})) { $isCODE=1; }
4313: my $studentTable=&Apache::loncommon::start_data_table().
4314: &Apache::loncommon::start_data_table_header_row().
4315: '<th>'.&mt('Date/Time').'</th>'.
4316: ($isCODE?'<th>'.&mt('CODE').'</th>':'').
4317: '<th>'.&mt('Submission').'</th>'.
4318: '<th>'.&mt('Status').'</th>'.
4319: &Apache::loncommon::end_data_table_header_row();
4320: my ($version);
4321: my %mark;
4322: my %orders;
4323: $mark{'correct_by_student'} = $checkIcon;
4324: if (!exists($$record{'1:timestamp'})) {
4325: return '<br /> <span class="LC_warning">'.&mt('Nothing submitted - no attempts').'</span><br />';
4326: }
4327:
4328: my $interaction;
4329: for ($version=1;$version<=$$record{'version'};$version++) {
4330: my $timestamp =
4331: &Apache::lonlocal::locallocaltime($$record{$version.':timestamp'});
4332: if (exists($$record{$version.':resource.0.version'})) {
4333: $interaction = $$record{$version.':resource.0.version'};
4334: }
4335:
4336: my $where = ($isTask ? "$version:resource.$interaction"
4337: : "$version:resource");
4338: $studentTable.=&Apache::loncommon::start_data_table_row().
4339: '<td>'.$timestamp.'</td>';
4340: if ($isCODE) {
4341: $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';
4342: }
4343: my @versionKeys = split(/\:/,$$record{$version.':keys'});
4344: my @displaySub = ();
4345: foreach my $partid (@{$parts}) {
4346: my @matchKey = ($isTask ? sort(grep /^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys)
4347: : sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys));
4348:
4349:
4350: # next if ($$record{"$version:resource.$partid.solved"} eq '');
4351: my $display_part=&get_display_part($partid,$symb);
4352: foreach my $matchKey (@matchKey) {
4353: if (exists($$record{$version.':'.$matchKey}) &&
4354: $$record{$version.':'.$matchKey} ne '') {
4355:
4356: my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
4357: : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
4358: $displaySub[0].='<b>'.&mt('Part:').'</b> '.$display_part.' ';
4359: $displaySub[0].='<span class="LC_internal_info">('.&mt('ID').' '.
4360: $responseId.')</span> <b>';
4361: if ($$record{"$where.$partid.tries"} eq '') {
4362: $displaySub[0].=&mt('Trial not counted');
4363: } else {
4364: $displaySub[0].=&mt('Trial [_1]',
4365: $$record{"$where.$partid.tries"});
4366: }
4367: my $responseType=($isTask ? 'Task'
4368: : $responseType->{$partid}->{$responseId});
4369: if (!exists($orders{$partid})) { $orders{$partid}={}; }
4370: if (!exists($orders{$partid}->{$responseId})) {
4371: $orders{$partid}->{$responseId}=
4372: &get_order($partid,$responseId,$symb,$uname,$udom);
4373: }
4374: $displaySub[0].='</b> '.
4375: &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom).'<br />';
4376: }
4377: }
4378: if (exists($$record{"$where.$partid.checkedin"})) {
4379: $displaySub[1].=&mt('Checked in by [_1] into slot [_2]',
4380: $$record{"$where.$partid.checkedin"},
4381: $$record{"$where.$partid.checkedin.slot"}).
4382: '<br />';
4383: }
4384: if (exists $$record{"$where.$partid.award"}) {
4385: $displaySub[1].='<b>'.&mt('Part:').'</b> '.$display_part.' '.
4386: lc($$record{"$where.$partid.award"}).' '.
4387: $mark{$$record{"$where.$partid.solved"}}.
4388: '<br />';
4389: }
4390: if (exists $$record{"$where.$partid.regrader"}) {
4391: $displaySub[2].=$$record{"$where.$partid.regrader"}.
4392: ' (<b>'.&mt('Part').':</b> '.$display_part.')';
4393: } elsif ($$record{"$version:resource.$partid.regrader"} =~ /\S/) {
4394: $displaySub[2].=
4395: $$record{"$version:resource.$partid.regrader"}.
4396: ' (<b>'.&mt('Part').':</b> '.$display_part.')';
4397: }
4398: }
4399: # needed because old essay regrader has not parts info
4400: if (exists $$record{"$version:resource.regrader"}) {
4401: $displaySub[2].=$$record{"$version:resource.regrader"};
4402: }
4403: $studentTable.='<td>'.$displaySub[0].' </td><td>'.$displaySub[1];
4404: if ($displaySub[2]) {
4405: $studentTable.=&mt('Manually graded by [_1]',$displaySub[2]);
4406: }
4407: $studentTable.=' </td>'.
4408: &Apache::loncommon::end_data_table_row();
4409: }
4410: $studentTable.=&Apache::loncommon::end_data_table();
4411: return $studentTable;
4412: }
4413:
4414: sub updateGradeByPage {
4415: my ($request) = shift;
4416:
4417: my $cdom = $env{"course.$env{'request.course.id'}.domain"};
4418: my $cnum = $env{"course.$env{'request.course.id'}.num"};
4419: my $getsec = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
4420: my $pageTitle = $env{'form.page'};
4421: my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
4422: my ($uname,$udom) = split(/:/,$env{'form.student'});
4423: my $usec=$classlist->{$env{'form.student'}}[5];
4424: if (!&canmodify($usec)) {
4425: $request->print('<span class="LC_warning">Unable to modify requested student.('.$env{'form.student'}.'</span>');
4426: $request->print(&show_grading_menu_form($env{'form.symb'}));
4427: return;
4428: }
4429: my $result='<h3><span class="LC_info"> '.$env{'form.title'}.'</span></h3>';
4430: $result.='<h3> Student: '.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
4431: '</h3>'."\n";
4432:
4433: $request->print($result);
4434:
4435: my $navmap = Apache::lonnavmaps::navmap->new();
4436: my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'});
4437: my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
4438: if (!$map) {
4439: $request->print('<span class="LC_warning">Unable to grade requested sequence. ('.$resUrl.')</span>');
4440: my ($symb)=&get_symb($request);
4441: $request->print(&show_grading_menu_form($symb));
4442: return;
4443: }
4444: my $iterator = $navmap->getIterator($map->map_start(),
4445: $map->map_finish());
4446:
4447: my $studentTable=
4448: &Apache::loncommon::start_data_table().
4449: &Apache::loncommon::start_data_table_header_row().
4450: '<th align="center"> '.&mt('Prob.').' </th>'.
4451: '<th> '.&mt('Title').' </th>'.
4452: '<th> '.&mt('Previous Score').' </th>'.
4453: '<th> '.&mt('New Score').' </th>'.
4454: &Apache::loncommon::end_data_table_header_row();
4455:
4456: $iterator->next(); # skip the first BEGIN_MAP
4457: my $curRes = $iterator->next(); # for "current resource"
4458: my ($depth,$question,$prob,$changeflag)= (1,1,1,0);
4459: while ($depth > 0) {
4460: if($curRes == $iterator->BEGIN_MAP) { $depth++; }
4461: if($curRes == $iterator->END_MAP) { $depth--; }
4462:
4463: if (ref($curRes) && $curRes->is_problem()) {
4464: my $parts = $curRes->parts();
4465: my $title = $curRes->compTitle();
4466: my $symbx = $curRes->symb();
4467: $studentTable.=
4468: &Apache::loncommon::start_data_table_row().
4469: '<td align="center" valign="top" >'.$prob.
4470: (scalar(@{$parts}) == 1 ? ''
4471: : '<br />('.&mt('[quant,_1, parts]',scalar(@{$parts}))
4472: ).')</td>';
4473: $studentTable.='<td valign="top"> <b>'.$title.'</b> </td>';
4474:
4475: my %newrecord=();
4476: my @displayPts=();
4477: my %aggregate = ();
4478: my $aggregateflag = 0;
4479: foreach my $partid (@{$parts}) {
4480: my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};
4481: my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};
4482:
4483: my $wgt = $env{'form.WGT'.$question.'_'.$partid} != 0 ?
4484: $env{'form.WGT'.$question.'_'.$partid} : 1;
4485: my $partial = $newpts/$wgt;
4486: my $score;
4487: if ($partial > 0) {
4488: $score = 'correct_by_override';
4489: } elsif ($newpts ne '') { #empty is taken as 0
4490: $score = 'incorrect_by_override';
4491: }
4492: my $dropMenu = $env{'form.GD_SEL'.$question.'_'.$partid};
4493: if ($dropMenu eq 'excused') {
4494: $partial = '';
4495: $score = 'excused';
4496: } elsif ($dropMenu eq 'reset status'
4497: && $env{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists
4498: $newrecord{'resource.'.$partid.'.tries'} = 0;
4499: $newrecord{'resource.'.$partid.'.solved'} = '';
4500: $newrecord{'resource.'.$partid.'.award'} = '';
4501: $newrecord{'resource.'.$partid.'.awarded'} = 0;
4502: $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}";
4503: $changeflag++;
4504: $newpts = '';
4505:
4506: my $aggtries = $env{'form.aggtries'.$question.'_'.$partid};
4507: my $totaltries = $env{'form.totaltries'.$question.'_'.$partid};
4508: my $solvedstatus = $env{'form.solved'.$question.'_'.$partid};
4509: if ($aggtries > 0) {
4510: &decrement_aggs($symbx,$partid,\%aggregate,$aggtries,$totaltries,$solvedstatus);
4511: $aggregateflag = 1;
4512: }
4513: }
4514: my $display_part=&get_display_part($partid,$curRes->symb());
4515: my $oldstatus = $env{'form.solved'.$question.'_'.$partid};
4516: $displayPts[0].=' <b>Part:</b> '.$display_part.' = '.
4517: (($oldstatus eq 'excused') ? 'excused' : $oldpts).
4518: ' <br />';
4519: $displayPts[1].=' <b>Part:</b> '.$display_part.' = '.
4520: (($score eq 'excused') ? 'excused' : $newpts).
4521: ' <br />';
4522: $question++;
4523: next if ($dropMenu eq 'reset status' || ($newpts eq $oldpts && $score ne 'excused'));
4524:
4525: $newrecord{'resource.'.$partid.'.awarded'} = $partial if $partial ne '';
4526: $newrecord{'resource.'.$partid.'.solved'} = $score if $score ne '';
4527: $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}"
4528: if (scalar(keys(%newrecord)) > 0);
4529:
4530: $changeflag++;
4531: }
4532: if (scalar(keys(%newrecord)) > 0) {
4533: my %record =
4534: &Apache::lonnet::restore($symbx,$env{'request.course.id'},
4535: $udom,$uname);
4536:
4537: if (&Apache::lonnet::validCODE($env{'form.CODE'})) {
4538: $newrecord{'resource.CODE'} = $env{'form.CODE'};
4539: } elsif (&Apache::lonnet::validCODE($record{'resource.CODE'})) {
4540: $newrecord{'resource.CODE'} = '';
4541: }
4542: &Apache::lonnet::cstore(\%newrecord,$symbx,$env{'request.course.id'},
4543: $udom,$uname);
4544: %record = &Apache::lonnet::restore($symbx,
4545: $env{'request.course.id'},
4546: $udom,$uname);
4547: &check_and_remove_from_queue($parts,\%record,undef,$symbx,
4548: $cdom,$cnum,$udom,$uname);
4549: }
4550:
4551: if ($aggregateflag) {
4552: &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
4553: $env{'course.'.$env{'request.course.id'}.'.domain'},
4554: $env{'course.'.$env{'request.course.id'}.'.num'});
4555: }
4556:
4557: $studentTable.='<td valign="top">'.$displayPts[0].'</td>'.
4558: '<td valign="top">'.$displayPts[1].'</td>'.
4559: &Apache::loncommon::end_data_table_row();
4560:
4561: $prob++;
4562: }
4563: $curRes = $iterator->next();
4564: }
4565:
4566: $studentTable.=&Apache::loncommon::end_data_table();
4567: $studentTable.=&show_grading_menu_form($env{'form.symb'});
4568: my $grademsg=($changeflag == 0 ? 'No score was changed or updated.' :
4569: 'The scores were changed for '.
4570: $changeflag.' problem'.($changeflag == 1 ? '.' : 's.'));
4571: $request->print($grademsg.$studentTable);
4572:
4573: return '';
4574: }
4575:
4576: #-------- end of section for handling grading by page/sequence ---------
4577: #
4578: #-------------------------------------------------------------------
4579:
4580: #--------------------Scantron Grading-----------------------------------
4581: #
4582: #------ start of section for handling grading by page/sequence ---------
4583:
4584: =pod
4585:
4586: =head1 Bubble sheet grading routines
4587:
4588: For this documentation:
4589:
4590: 'scanline' refers to the full line of characters
4591: from the file that we are parsing that represents one entire sheet
4592:
4593: 'bubble line' refers to the data
4594: representing the line of bubbles that are on the physical bubble sheet
4595:
4596:
4597: The overall process is that a scanned in bubble sheet data is uploaded
4598: into a course. When a user wants to grade, they select a
4599: sequence/folder of resources, a file of bubble sheet info, and pick
4600: one of the predefined configurations for what each scanline looks
4601: like.
4602:
4603: Next each scanline is checked for any errors of either 'missing
4604: bubbles' (it's an error because it may have been mis-scanned
4605: because too light bubbling), 'double bubble' (each bubble line should
4606: have no more that one letter picked), invalid or duplicated CODE,
4607: invalid student ID
4608:
4609: If the CODE option is used that determines the randomization of the
4610: homework problems, either way the student ID is looked up into a
4611: username:domain.
4612:
4613: During the validation phase the instructor can choose to skip scanlines.
4614:
4615: After the validation phase, there are now 3 bubble sheet files
4616:
4617: scantron_original_filename (unmodified original file)
4618: scantron_corrected_filename (file where the corrected information has replaced the original information)
4619: scantron_skipped_filename (contains the exact text of scanlines that where skipped)
4620:
4621: Also there is a separate hash nohist_scantrondata that contains extra
4622: correction information that isn't representable in the bubble sheet
4623: file (see &scantron_getfile() for more information)
4624:
4625: After all scanlines are either valid, marked as valid or skipped, then
4626: foreach line foreach problem in the picked sequence, an ssi request is
4627: made that simulates a user submitting their selected letter(s) against
4628: the homework problem.
4629:
4630: =over 4
4631:
4632:
4633:
4634: =item defaultFormData
4635:
4636: Returns html hidden inputs used to hold context/default values.
4637:
4638: Arguments:
4639: $symb - $symb of the current resource
4640:
4641: =cut
4642:
4643: sub defaultFormData {
4644: my ($symb)=@_;
4645: return '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
4646: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
4647: '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
4648: }
4649:
4650:
4651: =pod
4652:
4653: =item getSequenceDropDown
4654:
4655: Return html dropdown of possible sequences to grade
4656:
4657: Arguments:
4658: $symb - $symb of the current resource
4659:
4660: =cut
4661:
4662: sub getSequenceDropDown {
4663: my ($symb)=@_;
4664: my $result='<select name="selectpage">'."\n";
4665: my ($titles,$symbx) = &getSymbMap();
4666: my ($curpage)=&Apache::lonnet::decode_symb($symb);
4667: my $ctr=0;
4668: foreach (@$titles) {
4669: my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
4670: $result.='<option value="'.$$symbx{$_}.'" '.
4671: ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
4672: '>'.$showtitle.'</option>'."\n";
4673: $ctr++;
4674: }
4675: $result.= '</select>';
4676: return $result;
4677: }
4678:
4679: my %bubble_lines_per_response; # no. bubble lines for each response.
4680: # index is "symb.part_id"
4681:
4682: my %first_bubble_line; # First bubble line no. for each bubble.
4683:
4684: my %subdivided_bubble_lines; # no. bubble lines for optionresponse
4685: # or matchresponse where an individual
4686: # response can have multiple lines
4687:
4688: my %responsetype_per_response; # responsetype for each response
4689:
4690: # Save and restore the bubble lines array to the form env.
4691:
4692:
4693: sub save_bubble_lines {
4694: foreach my $line (keys(%bubble_lines_per_response)) {
4695: $env{"form.scantron.bubblelines.$line"} = $bubble_lines_per_response{$line};
4696: $env{"form.scantron.first_bubble_line.$line"} =
4697: $first_bubble_line{$line};
4698: $env{"form.scantron.sub_bubblelines.$line"} =
4699: $subdivided_bubble_lines{$line};
4700: $env{"form.scantron.responsetype.$line"} =
4701: $responsetype_per_response{$line};
4702: }
4703: }
4704:
4705:
4706: sub restore_bubble_lines {
4707: my $line = 0;
4708: %bubble_lines_per_response = ();
4709: while ($env{"form.scantron.bubblelines.$line"}) {
4710: my $value = $env{"form.scantron.bubblelines.$line"};
4711: $bubble_lines_per_response{$line} = $value;
4712: $first_bubble_line{$line} =
4713: $env{"form.scantron.first_bubble_line.$line"};
4714: $subdivided_bubble_lines{$line} =
4715: $env{"form.scantron.sub_bubblelines.$line"};
4716: $responsetype_per_response{$line} =
4717: $env{"form.scantron.responsetype.$line"};
4718: $line++;
4719: }
4720:
4721: }
4722:
4723: # Given the parsed scanline, get the response for
4724: # 'answer' number n:
4725:
4726: sub get_response_bubbles {
4727: my ($parsed_line, $response) = @_;
4728:
4729:
4730: my $bubble_line = $first_bubble_line{$response-1} +1;
4731: my $bubble_lines= $bubble_lines_per_response{$response-1};
4732:
4733: my $selected = "";
4734:
4735: for (my $bline = 0; $bline < $bubble_lines; $bline++) {
4736: $selected .= $$parsed_line{"scantron.$bubble_line.answer"}.":";
4737: $bubble_line++;
4738: }
4739: return $selected;
4740: }
4741:
4742: =pod
4743:
4744: =item scantron_filenames
4745:
4746: Returns a list of the scantron files in the current course
4747:
4748: =cut
4749:
4750: sub scantron_filenames {
4751: my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
4752: my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
4753: my @files=&Apache::lonnet::dirlist('userfiles',$cdom,$cname,
4754: &propath($cdom,$cname));
4755: my @possiblenames;
4756: foreach my $filename (sort(@files)) {
4757: ($filename)=split(/&/,$filename);
4758: if ($filename!~/^scantron_orig_/) { next ; }
4759: $filename=~s/^scantron_orig_//;
4760: push(@possiblenames,$filename);
4761: }
4762: return @possiblenames;
4763: }
4764:
4765: =pod
4766:
4767: =item scantron_uploads
4768:
4769: Returns html drop-down list of scantron files in current course.
4770:
4771: Arguments:
4772: $file2grade - filename to set as selected in the dropdown
4773:
4774: =cut
4775:
4776: sub scantron_uploads {
4777: my ($file2grade) = @_;
4778: my $result= '<select name="scantron_selectfile">';
4779: $result.="<option></option>";
4780: foreach my $filename (sort(&scantron_filenames())) {
4781: $result.="<option".($filename eq $file2grade ? ' selected="selected"':'').">$filename</option>\n";
4782: }
4783: $result.="</select>";
4784: return $result;
4785: }
4786:
4787: =pod
4788:
4789: =item scantron_scantab
4790:
4791: Returns html drop down of the scantron formats in the scantronformat.tab
4792: file.
4793:
4794: =cut
4795:
4796: sub scantron_scantab {
4797: my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
4798: my $result='<select name="scantron_format">'."\n";
4799: $result.='<option></option>'."\n";
4800: foreach my $line (<$fh>) {
4801: my ($name,$descrip)=split(/:/,$line);
4802: if ($name =~ /^\#/) { next; }
4803: $result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";
4804: }
4805: $result.='</select>'."\n";
4806:
4807: return $result;
4808: }
4809:
4810: =pod
4811:
4812: =item scantron_CODElist
4813:
4814: Returns html drop down of the saved CODE lists from current course,
4815: generated from earlier printings.
4816:
4817: =cut
4818:
4819: sub scantron_CODElist {
4820: my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
4821: my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
4822: my @names=&Apache::lonnet::getkeys('CODEs',$cdom,$cnum);
4823: my $namechoice='<option></option>';
4824: foreach my $name (sort {uc($a) cmp uc($b)} @names) {
4825: if ($name =~ /^error: 2 /) { next; }
4826: if ($name =~ /^type\0/) { next; }
4827: $namechoice.='<option value="'.$name.'">'.$name.'</option>';
4828: }
4829: $namechoice='<select name="scantron_CODElist">'.$namechoice.'</select>';
4830: return $namechoice;
4831: }
4832:
4833: =pod
4834:
4835: =item scantron_CODEunique
4836:
4837: Returns the html for "Each CODE to be used once" radio.
4838:
4839: =cut
4840:
4841: sub scantron_CODEunique {
4842: my $result='<span style="white-space: nowrap;">
4843: <label><input type="radio" name="scantron_CODEunique"
4844: value="yes" checked="checked" />'.&mt('Yes').' </label>
4845: </span>
4846: <span style="white-space: nowrap;">
4847: <label><input type="radio" name="scantron_CODEunique"
4848: value="no" />'.&mt('No').' </label>
4849: </span>';
4850: return $result;
4851: }
4852:
4853: =pod
4854:
4855: =item scantron_selectphase
4856:
4857: Generates the initial screen to start the bubble sheet process.
4858: Allows for - starting a grading run.
4859: - downloading existing scan data (original, corrected
4860: or skipped info)
4861:
4862: - uploading new scan data
4863:
4864: Arguments:
4865: $r - The Apache request object
4866: $file2grade - name of the file that contain the scanned data to score
4867:
4868: =cut
4869:
4870: sub scantron_selectphase {
4871: my ($r,$file2grade) = @_;
4872: my ($symb)=&get_symb($r);
4873: if (!$symb) {return '';}
4874: my $sequence_selector=&getSequenceDropDown($symb);
4875: my $default_form_data=&defaultFormData($symb);
4876: my $grading_menu_button=&show_grading_menu_form($symb);
4877: my $file_selector=&scantron_uploads($file2grade);
4878: my $format_selector=&scantron_scantab();
4879: my $CODE_selector=&scantron_CODElist();
4880: my $CODE_unique=&scantron_CODEunique();
4881: my $result;
4882:
4883: # Chunk of form to prompt for a file to grade and how:
4884:
4885: $result.= '
4886: <br />
4887: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">
4888: <input type="hidden" name="command" value="scantron_warning" />
4889: '.$default_form_data.'
4890: '.&Apache::loncommon::start_data_table('LC_scantron_action').'
4891: '.&Apache::loncommon::start_data_table_header_row().'
4892: <th colspan="2">
4893: '.&mt('Specify file and which Folder/Sequence to grade').'
4894: </th>
4895: '.&Apache::loncommon::end_data_table_header_row().'
4896: '.&Apache::loncommon::start_data_table_row().'
4897: <td> '.&mt('Sequence to grade:').' </td><td> '.$sequence_selector.' </td>
4898: '.&Apache::loncommon::end_data_table_row().'
4899: '.&Apache::loncommon::start_data_table_row().'
4900: <td> '.&mt('Filename of scoring office file:').' </td><td> '.$file_selector.' </td>
4901: '.&Apache::loncommon::end_data_table_row().'
4902: '.&Apache::loncommon::start_data_table_row().'
4903: <td> '.&mt('Format of data file:').' </td><td> '.$format_selector.' </td>
4904: '.&Apache::loncommon::end_data_table_row().'
4905: '.&Apache::loncommon::start_data_table_row().'
4906: <td> '.&mt('Saved CODEs to validate against:').' </td><td> '.$CODE_selector.' </td>
4907: '.&Apache::loncommon::end_data_table_row().'
4908: '.&Apache::loncommon::start_data_table_row().'
4909: <td> '.&mt('Each CODE is only to be used once:').'</td><td> '.$CODE_unique.' </td>
4910: '.&Apache::loncommon::end_data_table_row().'
4911: '.&Apache::loncommon::start_data_table_row().'
4912: <td> '.&mt('Options:').' </td>
4913: <td>
4914: <label><input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> '.&mt('Do only previously skipped records').'</label> <br />
4915: <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> '.&mt('Remove all existing corrections').'</label> <br />
4916: <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> '.&mt('Skip hidden resources when grading').'</label>
4917: </td>
4918: '.&Apache::loncommon::end_data_table_row().'
4919: '.&Apache::loncommon::start_data_table_row().'
4920: <td colspan="2">
4921: <input type="submit" value="'.&mt('Grading: Validate Scantron Records').'" />
4922: </td>
4923: '.&Apache::loncommon::end_data_table_row().'
4924: '.&Apache::loncommon::end_data_table().'
4925: </form>
4926: ';
4927:
4928: $r->print($result);
4929:
4930: if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
4931: &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
4932:
4933: # Chunk of form to prompt for a scantron file upload.
4934:
4935: $r->print('
4936: <br />
4937: '.&Apache::loncommon::start_data_table('LC_scantron_action').'
4938: '.&Apache::loncommon::start_data_table_header_row().'
4939: <th>
4940: '.&mt('Specify a Scantron data file to upload.').'
4941: </th>
4942: '.&Apache::loncommon::end_data_table_header_row().'
4943: '.&Apache::loncommon::start_data_table_row().'
4944: <td>
4945: ');
4946: my $default_form_data=&defaultFormData(&get_symb($r,1));
4947: my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
4948: my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};
4949: $r->print('
4950: <script type="text/javascript" language="javascript">
4951: function checkUpload(formname) {
4952: if (formname.upfile.value == "") {
4953: alert("'.&mt('Please use the browse button to select a file from your local directory.').'");
4954: return false;
4955: }
4956: formname.submit();
4957: }
4958: </script>
4959:
4960: <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
4961: '.$default_form_data.'
4962: <input name="courseid" type="hidden" value="'.$cnum.'" />
4963: <input name="domainid" type="hidden" value="'.$cdom.'" />
4964: <input name="command" value="scantronupload_save" type="hidden" />
4965: '.&mt('File to upload: [_1]','<input type="file" name="upfile" size="50" />').'
4966: <br />
4967: <input type="button" onClick="javascript:checkUpload(this.form);" value="'.&mt('Upload Scantron Data').'" />
4968: </form>
4969: ');
4970:
4971: $r->print('
4972: </td>
4973: '.&Apache::loncommon::end_data_table_row().'
4974: '.&Apache::loncommon::end_data_table().'
4975: ');
4976: }
4977:
4978: # Chunk of the form that prompts to view a scoring office file,
4979: # corrected file, skipped records in a file.
4980:
4981: $r->print('
4982: <br />
4983: <form action="/adm/grades" name="scantron_download">
4984: '.$default_form_data.'
4985: <input type="hidden" name="command" value="scantron_download" />
4986: '.&Apache::loncommon::start_data_table('LC_scantron_action').'
4987: '.&Apache::loncommon::start_data_table_header_row().'
4988: <th>
4989: '.&mt('Download a scoring office file').'
4990: </th>
4991: '.&Apache::loncommon::end_data_table_header_row().'
4992: '.&Apache::loncommon::start_data_table_row().'
4993: <td> '.&mt('Filename of scoring office file: [_1]',$file_selector).'
4994: <br />
4995: <input type="submit" value="'.&mt('Download: Show List of Associated Files').'" />
4996: '.&Apache::loncommon::end_data_table_row().'
4997: '.&Apache::loncommon::end_data_table().'
4998: </form>
4999: <br />
5000: ');
5001:
5002: &Apache::lonpickcode::code_list($r,2);
5003: $r->print($grading_menu_button);
5004: return
5005: }
5006:
5007: =pod
5008:
5009: =item get_scantron_config
5010:
5011: Parse and return the scantron configuration line selected as a
5012: hash of configuration file fields.
5013:
5014: Arguments:
5015: which - the name of the configuration to parse from the file.
5016:
5017:
5018: Returns:
5019: If the named configuration is not in the file, an empty
5020: hash is returned.
5021: a hash with the fields
5022: name - internal name for the this configuration setup
5023: description - text to display to operator that describes this config
5024: CODElocation - if 0 or the string 'none'
5025: - no CODE exists for this config
5026: if -1 || the string 'letter'
5027: - a CODE exists for this config and is
5028: a string of letters
5029: Unsupported value (but planned for future support)
5030: if a positive integer
5031: - The CODE exists as the first n items from
5032: the question section of the form
5033: if the string 'number'
5034: - The CODE exists for this config and is
5035: a string of numbers
5036: CODEstart - (only matter if a CODE exists) column in the line where
5037: the CODE starts
5038: CODElength - length of the CODE
5039: IDstart - column where the student ID number starts
5040: IDlength - length of the student ID info
5041: Qstart - column where the information from the bubbled
5042: 'questions' start
5043: Qlength - number of columns comprising a single bubble line from
5044: the sheet. (usually either 1 or 10)
5045: Qon - either a single character representing the character used
5046: to signal a bubble was chosen in the positional setup, or
5047: the string 'letter' if the letter of the chosen bubble is
5048: in the final, or 'number' if a number representing the
5049: chosen bubble is in the file (1->A 0->J)
5050: Qoff - the character used to represent that a bubble was
5051: left blank
5052: PaperID - if the scanning process generates a unique number for each
5053: sheet scanned the column that this ID number starts in
5054: PaperIDlength - number of columns that comprise the unique ID number
5055: for the sheet of paper
5056: FirstName - column that the first name starts in
5057: FirstNameLength - number of columns that the first name spans
5058:
5059: LastName - column that the last name starts in
5060: LastNameLength - number of columns that the last name spans
5061:
5062: =cut
5063:
5064: sub get_scantron_config {
5065: my ($which) = @_;
5066: my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
5067: my %config;
5068: #FIXME probably should move to XML it has already gotten a bit much now
5069: foreach my $line (<$fh>) {
5070: my ($name,$descrip)=split(/:/,$line);
5071: if ($name ne $which ) { next; }
5072: chomp($line);
5073: my @config=split(/:/,$line);
5074: $config{'name'}=$config[0];
5075: $config{'description'}=$config[1];
5076: $config{'CODElocation'}=$config[2];
5077: $config{'CODEstart'}=$config[3];
5078: $config{'CODElength'}=$config[4];
5079: $config{'IDstart'}=$config[5];
5080: $config{'IDlength'}=$config[6];
5081: $config{'Qstart'}=$config[7];
5082: $config{'Qlength'}=$config[8];
5083: $config{'Qoff'}=$config[9];
5084: $config{'Qon'}=$config[10];
5085: $config{'PaperID'}=$config[11];
5086: $config{'PaperIDlength'}=$config[12];
5087: $config{'FirstName'}=$config[13];
5088: $config{'FirstNamelength'}=$config[14];
5089: $config{'LastName'}=$config[15];
5090: $config{'LastNamelength'}=$config[16];
5091: last;
5092: }
5093: return %config;
5094: }
5095:
5096: =pod
5097:
5098: =item username_to_idmap
5099:
5100: creates a hash keyed by student id with values of the corresponding
5101: student username:domain.
5102:
5103: Arguments:
5104:
5105: $classlist - reference to the class list hash. This is a hash
5106: keyed by student name:domain whose elements are references
5107: to arrays containing various chunks of information
5108: about the student. (See loncoursedata for more info).
5109:
5110: Returns
5111: %idmap - the constructed hash
5112:
5113: =cut
5114:
5115: sub username_to_idmap {
5116: my ($classlist)= @_;
5117: my %idmap;
5118: foreach my $student (keys(%$classlist)) {
5119: $idmap{$classlist->{$student}->[&Apache::loncoursedata::CL_ID]}=
5120: $student;
5121: }
5122: return %idmap;
5123: }
5124:
5125: =pod
5126:
5127: =item scantron_fixup_scanline
5128:
5129: Process a requested correction to a scanline.
5130:
5131: Arguments:
5132: $scantron_config - hash from &get_scantron_config()
5133: $scan_data - hash of correction information
5134: (see &scantron_getfile())
5135: $line - existing scanline
5136: $whichline - line number of the passed in scanline
5137: $field - type of change to process
5138: (either
5139: 'ID' -> correct the student ID number
5140: 'CODE' -> correct the CODE
5141: 'answer' -> fixup the submitted answers)
5142:
5143: $args - hash of additional info,
5144: - 'ID'
5145: 'newid' -> studentID to use in replacement
5146: of existing one
5147: - 'CODE'
5148: 'CODE_ignore_dup' - set to true if duplicates
5149: should be ignored.
5150: 'CODE' - is new code or 'use_unfound'
5151: if the existing unfound code should
5152: be used as is
5153: - 'answer'
5154: 'response' - new answer or 'none' if blank
5155: 'question' - the bubble line to change
5156: 'questionnum' - the question identifier,
5157: may include subquestion.
5158:
5159: Returns:
5160: $line - the modified scanline
5161:
5162: Side effects:
5163: $scan_data - may be updated
5164:
5165: =cut
5166:
5167:
5168: sub scantron_fixup_scanline {
5169: my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
5170: if ($field eq 'ID') {
5171: if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {
5172: return ($line,1,'New value too large');
5173: }
5174: if (length($args->{'newid'}) < $$scantron_config{'IDlength'}) {
5175: $args->{'newid'}=sprintf('%-'.$$scantron_config{'IDlength'}.'s',
5176: $args->{'newid'});
5177: }
5178: substr($line,$$scantron_config{'IDstart'}-1,
5179: $$scantron_config{'IDlength'})=$args->{'newid'};
5180: if ($args->{'newid'}=~/^\s*$/) {
5181: &scan_data($scan_data,"$whichline.user",
5182: $args->{'username'}.':'.$args->{'domain'});
5183: }
5184: } elsif ($field eq 'CODE') {
5185: if ($args->{'CODE_ignore_dup'}) {
5186: &scan_data($scan_data,"$whichline.CODE_ignore_dup",'1');
5187: }
5188: &scan_data($scan_data,"$whichline.useCODE",'1');
5189: if ($args->{'CODE'} ne 'use_unfound') {
5190: if (length($args->{'CODE'}) > $$scantron_config{'CODElength'}) {
5191: return ($line,1,'New CODE value too large');
5192: }
5193: if (length($args->{'CODE'}) < $$scantron_config{'CODElength'}) {
5194: $args->{'CODE'}=sprintf('%-'.$$scantron_config{'CODElength'}.'s',$args->{'CODE'});
5195: }
5196: substr($line,$$scantron_config{'CODEstart'}-1,
5197: $$scantron_config{'CODElength'})=$args->{'CODE'};
5198: }
5199: } elsif ($field eq 'answer') {
5200: my $length=$scantron_config->{'Qlength'};
5201: my $off=$scantron_config->{'Qoff'};
5202: my $on=$scantron_config->{'Qon'};
5203: my $answer=${off}x$length;
5204: if ($args->{'response'} eq 'none') {
5205: &scan_data($scan_data,
5206: "$whichline.no_bubble.".$args->{'questionnum'},'1');
5207: } else {
5208: if ($on eq 'letter') {
5209: my @alphabet=('A'..'Z');
5210: $answer=$alphabet[$args->{'response'}];
5211: } elsif ($on eq 'number') {
5212: $answer=$args->{'response'}+1;
5213: if ($answer == 10) { $answer = '0'; }
5214: } else {
5215: substr($answer,$args->{'response'},1)=$on;
5216: }
5217: &scan_data($scan_data,
5218: "$whichline.no_bubble.".$args->{'questionnum'},undef,'1');
5219: }
5220: my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'};
5221: substr($line,$where-1,$length)=$answer;
5222: }
5223: return $line;
5224: }
5225:
5226: =pod
5227:
5228: =item scan_data
5229:
5230: Edit or look up an item in the scan_data hash.
5231:
5232: Arguments:
5233: $scan_data - The hash (see scantron_getfile)
5234: $key - shorthand of the key to edit (actual key is
5235: scantronfilename_key).
5236: $data - New value of the hash entry.
5237: $delete - If true, the entry is removed from the hash.
5238:
5239: Returns:
5240: The new value of the hash table field (undefined if deleted).
5241:
5242: =cut
5243:
5244:
5245: sub scan_data {
5246: my ($scan_data,$key,$value,$delete)=@_;
5247: my $filename=$env{'form.scantron_selectfile'};
5248: if (defined($value)) {
5249: $scan_data->{$filename.'_'.$key} = $value;
5250: }
5251: if ($delete) { delete($scan_data->{$filename.'_'.$key}); }
5252: return $scan_data->{$filename.'_'.$key};
5253: }
5254:
5255: # ----- These first few routines are general use routines.----
5256:
5257: # Return the number of occurences of a pattern in a string.
5258:
5259: sub occurence_count {
5260: my ($string, $pattern) = @_;
5261:
5262: my @matches = ($string =~ /$pattern/g);
5263:
5264: return scalar(@matches);
5265: }
5266:
5267:
5268: # Take a string known to have digits and convert all the
5269: # digits into letters in the range J,A..I.
5270:
5271: sub digits_to_letters {
5272: my ($input) = @_;
5273:
5274: my @alphabet = ('J', 'A'..'I');
5275:
5276: my @input = split(//, $input);
5277: my $output ='';
5278: for (my $i = 0; $i < scalar(@input); $i++) {
5279: if ($input[$i] =~ /\d/) {
5280: $output .= $alphabet[$input[$i]];
5281: } else {
5282: $output .= $input[$i];
5283: }
5284: }
5285: return $output;
5286: }
5287:
5288: =pod
5289:
5290: =item scantron_parse_scanline
5291:
5292: Decodes a scanline from the selected scantron file
5293:
5294: Arguments:
5295: line - The text of the scantron file line to process
5296: whichline - Line number
5297: scantron_config - Hash describing the format of the scantron lines.
5298: scan_data - Hash of extra information about the scanline
5299: (see scantron_getfile for more information)
5300: just_header - True if should not process question answers but only
5301: the stuff to the left of the answers.
5302: Returns:
5303: Hash containing the result of parsing the scanline
5304:
5305: Keys are all proceeded by the string 'scantron.'
5306:
5307: CODE - the CODE in use for this scanline
5308: useCODE - 1 if the CODE is invalid but it usage has been forced
5309: by the operator
5310: CODE_ignore_dup - 1 if the CODE is a duplicated use when unique
5311: CODEs were selected, but the usage has been
5312: forced by the operator
5313: ID - student ID
5314: PaperID - if used, the ID number printed on the sheet when the
5315: paper was scanned
5316: FirstName - first name from the sheet
5317: LastName - last name from the sheet
5318:
5319: if just_header was not true these key may also exist
5320:
5321: missingerror - a list of bubble ranges that are considered to be answers
5322: to a single question that don't have any bubbles filled in.
5323: Of the form questionnumber:firstbubblenumber:count.
5324: doubleerror - a list of bubble ranges that are considered to be answers
5325: to a single question that have more than one bubble filled in.
5326: Of the form questionnumber::firstbubblenumber:count
5327:
5328: In the above, count is the number of bubble responses in the
5329: input line needed to represent the possible answers to the question.
5330: e.g. a radioresponse with 15 choices in an answer sheet with 10 choices
5331: per line would have count = 2.
5332:
5333: maxquest - the number of the last bubble line that was parsed
5334:
5335: (<number> starts at 1)
5336: <number>.answer - zero or more letters representing the selected
5337: letters from the scanline for the bubble line
5338: <number>.
5339: if blank there was either no bubble or there where
5340: multiple bubbles, (consult the keys missingerror and
5341: doubleerror if this is an error condition)
5342:
5343: =cut
5344:
5345: sub scantron_parse_scanline {
5346: my ($line,$whichline,$scantron_config,$scan_data,$just_header)=@_;
5347:
5348: my %record;
5349: my $questions=substr($line,$$scantron_config{'Qstart'}-1); # Answers
5350: my $data=substr($line,0,$$scantron_config{'Qstart'}-1); # earlier stuff
5351: if (!($$scantron_config{'CODElocation'} eq 0 ||
5352: $$scantron_config{'CODElocation'} eq 'none')) {
5353: if ($$scantron_config{'CODElocation'} < 0 ||
5354: $$scantron_config{'CODElocation'} eq 'letter' ||
5355: $$scantron_config{'CODElocation'} eq 'number') {
5356: $record{'scantron.CODE'}=substr($data,
5357: $$scantron_config{'CODEstart'}-1,
5358: $$scantron_config{'CODElength'});
5359: if (&scan_data($scan_data,"$whichline.useCODE")) {
5360: $record{'scantron.useCODE'}=1;
5361: }
5362: if (&scan_data($scan_data,"$whichline.CODE_ignore_dup")) {
5363: $record{'scantron.CODE_ignore_dup'}=1;
5364: }
5365: } else {
5366: #FIXME interpret first N questions
5367: }
5368: }
5369: $record{'scantron.ID'}=substr($data,$$scantron_config{'IDstart'}-1,
5370: $$scantron_config{'IDlength'});
5371: $record{'scantron.PaperID'}=
5372: substr($data,$$scantron_config{'PaperID'}-1,
5373: $$scantron_config{'PaperIDlength'});
5374: $record{'scantron.FirstName'}=
5375: substr($data,$$scantron_config{'FirstName'}-1,
5376: $$scantron_config{'FirstNamelength'});
5377: $record{'scantron.LastName'}=
5378: substr($data,$$scantron_config{'LastName'}-1,
5379: $$scantron_config{'LastNamelength'});
5380: if ($just_header) { return \%record; }
5381:
5382: my @alphabet=('A'..'Z');
5383: my $questnum=0;
5384: my $ansnum =1; # Multiple 'answer lines'/question.
5385:
5386: chomp($questions); # Get rid of any trailing \n.
5387: $questions =~ s/\r$//; # Get rid of trailing \r too (MAC or Win uploads).
5388: while (length($questions)) {
5389: my $answers_needed = $bubble_lines_per_response{$questnum};
5390: my $answer_length = ($$scantron_config{'Qlength'} * $answers_needed)
5391: || 1;
5392: $questnum++;
5393: my $quest_id = $questnum;
5394: my $currentquest = substr($questions,0,$answer_length);
5395: $questions = substr($questions,$answer_length);
5396: if (length($currentquest) < $answer_length) { next; }
5397:
5398: if ($subdivided_bubble_lines{$questnum-1} =~ /,/) {
5399: my $subquestnum = 1;
5400: my $subquestions = $currentquest;
5401: my @subanswers_needed =
5402: split(/,/,$subdivided_bubble_lines{$questnum-1});
5403: foreach my $subans (@subanswers_needed) {
5404: my $subans_length =
5405: ($$scantron_config{'Qlength'} * $subans) || 1;
5406: my $currsubquest = substr($subquestions,0,$subans_length);
5407: $subquestions = substr($subquestions,$subans_length);
5408: $quest_id = "$questnum.$subquestnum";
5409: if (($$scantron_config{'Qon'} eq 'letter') ||
5410: ($$scantron_config{'Qon'} eq 'number')) {
5411: $ansnum = &scantron_validator_lettnum($ansnum,
5412: $questnum,$quest_id,$subans,$currsubquest,$whichline,
5413: \@alphabet,\%record,$scantron_config,$scan_data);
5414: } else {
5415: $ansnum = &scantron_validator_positional($ansnum,
5416: $questnum,$quest_id,$subans,$currsubquest,$whichline, \@alphabet,\%record,$scantron_config,$scan_data);
5417: }
5418: $subquestnum ++;
5419: }
5420: } else {
5421: if (($$scantron_config{'Qon'} eq 'letter') ||
5422: ($$scantron_config{'Qon'} eq 'number')) {
5423: $ansnum = &scantron_validator_lettnum($ansnum,$questnum,
5424: $quest_id,$answers_needed,$currentquest,$whichline,
5425: \@alphabet,\%record,$scantron_config,$scan_data);
5426: } else {
5427: $ansnum = &scantron_validator_positional($ansnum,$questnum,
5428: $quest_id,$answers_needed,$currentquest,$whichline,
5429: \@alphabet,\%record,$scantron_config,$scan_data);
5430: }
5431: }
5432: }
5433: $record{'scantron.maxquest'}=$questnum;
5434: return \%record;
5435: }
5436:
5437: sub scantron_validator_lettnum {
5438: my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,$whichline,
5439: $alphabet,$record,$scantron_config,$scan_data) = @_;
5440:
5441: # Qon 'letter' implies for each slot in currquest we have:
5442: # ? or * for doubles, a letter in A-Z for a bubble, and
5443: # about anything else (esp. a value of Qoff) for missing
5444: # bubbles.
5445: #
5446: # Qon 'number' implies each slot gives a digit that indexes the
5447: # bubbles filled, or Qoff, or a non-number for unbubbled lines,
5448: # and * or ? for double bubbles on a single line.
5449: #
5450:
5451: my $matchon;
5452: if ($$scantron_config{'Qon'} eq 'letter') {
5453: $matchon = '[A-Z]';
5454: } elsif ($$scantron_config{'Qon'} eq 'number') {
5455: $matchon = '\d';
5456: }
5457: my $occurrences = 0;
5458: if (($responsetype_per_response{$questnum-1} eq 'essayresponse') ||
5459: ($responsetype_per_response{$questnum-1} eq 'formularesponse') ||
5460: ($responsetype_per_response{$questnum-1} eq 'stringresponse')) {
5461: my @singlelines = split('',$currquest);
5462: foreach my $entry (@singlelines) {
5463: $occurrences = &occurence_count($entry,$matchon);
5464: if ($occurrences > 1) {
5465: last;
5466: }
5467: }
5468: } else {
5469: $occurrences = &occurence_count($currquest,$matchon);
5470: }
5471: if (($currquest =~ /\?/ || $currquest =~ /\*/) || ($occurrences > 1)) {
5472: push(@{$record->{'scantron.doubleerror'}},$quest_id);
5473: for (my $ans=0; $ans<$answers_needed; $ans++) {
5474: my $bubble = substr($currquest,$ans,1);
5475: if ($bubble =~ /$matchon/ ) {
5476: if ($$scantron_config{'Qon'} eq 'number') {
5477: if ($bubble == 0) {
5478: $bubble = 10;
5479: }
5480: $record->{"scantron.$ansnum.answer"} =
5481: $alphabet->[$bubble-1];
5482: } else {
5483: $record->{"scantron.$ansnum.answer"} = $bubble;
5484: }
5485: } else {
5486: $record->{"scantron.$ansnum.answer"}='';
5487: }
5488: $ansnum++;
5489: }
5490: } elsif (!defined($currquest)
5491: || (&occurence_count($currquest, $$scantron_config{'Qoff'}) == length($currquest))
5492: || (&occurence_count($currquest,$matchon) == 0)) {
5493: for (my $ans=0; $ans<$answers_needed; $ans++ ) {
5494: $record->{"scantron.$ansnum.answer"}='';
5495: $ansnum++;
5496: }
5497: if (!&scan_data($scan_data,"$whichline.no_bubble.$quest_id")) {
5498: push(@{$record->{'scantron.missingerror'}},$quest_id);
5499: }
5500: } else {
5501: if ($$scantron_config{'Qon'} eq 'number') {
5502: $currquest = &digits_to_letters($currquest);
5503: }
5504: for (my $ans=0; $ans<$answers_needed; $ans++) {
5505: my $bubble = substr($currquest,$ans,1);
5506: $record->{"scantron.$ansnum.answer"} = $bubble;
5507: $ansnum++;
5508: }
5509: }
5510: return $ansnum;
5511: }
5512:
5513: sub scantron_validator_positional {
5514: my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,
5515: $whichline,$alphabet,$record,$scantron_config,$scan_data) = @_;
5516:
5517: # Otherwise there's a positional notation;
5518: # each bubble line requires Qlength items, and there are filled in
5519: # bubbles for each case where there 'Qon' characters.
5520: #
5521:
5522: my @array=split($$scantron_config{'Qon'},$currquest,-1);
5523:
5524: # If the split only gives us one element.. the full length of the
5525: # answer string, no bubbles are filled in:
5526:
5527: if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {
5528: for (my $ans=0; $ans<$answers_needed; $ans++ ) {
5529: $record->{"scantron.$ansnum.answer"}='';
5530: $ansnum++;
5531: }
5532: if (!&scan_data($scan_data,"$whichline.no_bubble.$quest_id")) {
5533: push(@{$record->{"scantron.missingerror"}},$quest_id);
5534: }
5535: } elsif (scalar(@array) == 2) {
5536: my $location = length($array[0]);
5537: my $line_num = int($location / $$scantron_config{'Qlength'});
5538: my $bubble = $alphabet->[$location % $$scantron_config{'Qlength'}];
5539: for (my $ans=0; $ans<$answers_needed; $ans++) {
5540: if ($ans eq $line_num) {
5541: $record->{"scantron.$ansnum.answer"} = $bubble;
5542: } else {
5543: $record->{"scantron.$ansnum.answer"} = ' ';
5544: }
5545: $ansnum++;
5546: }
5547: } else {
5548: # If there's more than one instance of a bubble character
5549: # That's a double bubble; with positional notation we can
5550: # record all the bubbles filled in as well as the
5551: # fact this response consists of multiple bubbles.
5552: #
5553: if (($responsetype_per_response{$questnum-1} eq 'essayresponse') ||
5554: ($responsetype_per_response{$questnum-1} eq 'formularesponse') ||
5555: ($responsetype_per_response{$questnum-1} eq 'stringresponse')) {
5556: my $doubleerror = 0;
5557: while (($currquest >= $$scantron_config{'Qlength'}) &&
5558: (!$doubleerror)) {
5559: my $currline = substr($currquest,0,$$scantron_config{'Qlength'});
5560: $currquest = substr($currquest,$$scantron_config{'Qlength'});
5561: my @currarray = split($$scantron_config{'Qon'},$currline,-1);
5562: if (length(@currarray) > 2) {
5563: $doubleerror = 1;
5564: }
5565: }
5566: if ($doubleerror) {
5567: push(@{$record->{'scantron.doubleerror'}},$quest_id);
5568: }
5569: } else {
5570: push(@{$record->{'scantron.doubleerror'}},$quest_id);
5571: }
5572: my $item = $ansnum;
5573: for (my $ans=0; $ans<$answers_needed; $ans++) {
5574: $record->{"scantron.$item.answer"} = '';
5575: $item ++;
5576: }
5577:
5578: my @ans=@array;
5579: my $i=0;
5580: my $increment = 0;
5581: while ($#ans) {
5582: $i+=length($ans[0]) + $increment;
5583: my $line = int($i/$$scantron_config{'Qlength'} + $ansnum);
5584: my $bubble = $i%$$scantron_config{'Qlength'};
5585: $record->{"scantron.$line.answer"}.=$alphabet->[$bubble];
5586: shift(@ans);
5587: $increment = 1;
5588: }
5589: $ansnum += $answers_needed;
5590: }
5591: return $ansnum;
5592: }
5593:
5594: =pod
5595:
5596: =item scantron_add_delay
5597:
5598: Adds an error message that occurred during the grading phase to a
5599: queue of messages to be shown after grading pass is complete
5600:
5601: Arguments:
5602: $delayqueue - arrary ref of hash ref of error messages
5603: $scanline - the scanline that caused the error
5604: $errormesage - the error message
5605: $errorcode - a numeric code for the error
5606:
5607: Side Effects:
5608: updates the $delayqueue to have a new hash ref of the error
5609:
5610: =cut
5611:
5612: sub scantron_add_delay {
5613: my ($delayqueue,$scanline,$errormessage,$errorcode)=@_;
5614: push(@$delayqueue,
5615: {'line' => $scanline, 'emsg' => $errormessage,
5616: 'ecode' => $errorcode }
5617: );
5618: }
5619:
5620: =pod
5621:
5622: =item scantron_find_student
5623:
5624: Finds the username for the current scanline
5625:
5626: Arguments:
5627: $scantron_record - hash result from scantron_parse_scanline
5628: $scan_data - hash of correction information
5629: (see &scantron_getfile() form more information)
5630: $idmap - hash from &username_to_idmap()
5631: $line - number of current scanline
5632:
5633: Returns:
5634: Either 'username:domain' or undef if unknown
5635:
5636: =cut
5637:
5638: sub scantron_find_student {
5639: my ($scantron_record,$scan_data,$idmap,$line)=@_;
5640: my $scanID=$$scantron_record{'scantron.ID'};
5641: if ($scanID =~ /^\s*$/) {
5642: return &scan_data($scan_data,"$line.user");
5643: }
5644: foreach my $id (keys(%$idmap)) {
5645: if (lc($id) eq lc($scanID)) {
5646: return $$idmap{$id};
5647: }
5648: }
5649: return undef;
5650: }
5651:
5652: =pod
5653:
5654: =item scantron_filter
5655:
5656: Filter sub for lonnavmaps, filters out hidden resources if ignore
5657: hidden resources was selected
5658:
5659: =cut
5660:
5661: sub scantron_filter {
5662: my ($curres)=@_;
5663:
5664: if (ref($curres) && $curres->is_problem()) {
5665: # if the user has asked to not have either hidden
5666: # or 'randomout' controlled resources to be graded
5667: # don't include them
5668: if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
5669: && $curres->randomout) {
5670: return 0;
5671: }
5672: return 1;
5673: }
5674: return 0;
5675: }
5676:
5677: =pod
5678:
5679: =item scantron_process_corrections
5680:
5681: Gets correction information out of submitted form data and corrects
5682: the scanline
5683:
5684: =cut
5685:
5686: sub scantron_process_corrections {
5687: my ($r) = @_;
5688: my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
5689: my ($scanlines,$scan_data)=&scantron_getfile();
5690: my $classlist=&Apache::loncoursedata::get_classlist();
5691: my $which=$env{'form.scantron_line'};
5692: my $line=&scantron_get_line($scanlines,$scan_data,$which);
5693: my ($skip,$err,$errmsg);
5694: if ($env{'form.scantron_skip_record'}) {
5695: $skip=1;
5696: } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) {
5697: my $newstudent=$env{'form.scantron_username'}.':'.
5698: $env{'form.scantron_domain'};
5699: my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID];
5700: ($line,$err,$errmsg)=
5701: &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
5702: 'ID',{'newid'=>$newid,
5703: 'username'=>$env{'form.scantron_username'},
5704: 'domain'=>$env{'form.scantron_domain'}});
5705: } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)CODE$/) {
5706: my $resolution=$env{'form.scantron_CODE_resolution'};
5707: my $newCODE;
5708: my %args;
5709: if ($resolution eq 'use_unfound') {
5710: $newCODE='use_unfound';
5711: } elsif ($resolution eq 'use_found') {
5712: $newCODE=$env{'form.scantron_CODE_selectedvalue'};
5713: } elsif ($resolution eq 'use_typed') {
5714: $newCODE=$env{'form.scantron_CODE_newvalue'};
5715: } elsif ($resolution =~ /^use_closest_(\d+)/) {
5716: $newCODE=$env{"form.scantron_CODE_closest_$1"};
5717: }
5718: if ($env{'form.scantron_corrections'} eq 'duplicateCODE') {
5719: $args{'CODE_ignore_dup'}=1;
5720: }
5721: $args{'CODE'}=$newCODE;
5722: ($line,$err,$errmsg)=
5723: &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
5724: 'CODE',\%args);
5725: } elsif ($env{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) {
5726: foreach my $question (split(',',$env{'form.scantron_questions'})) {
5727: ($line,$err,$errmsg)=
5728: &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,
5729: $which,'answer',
5730: { 'question'=>$question,
5731: 'response'=>$env{"form.scantron_correct_Q_$question"},
5732: 'questionnum'=>$env{"form.scantron_questionnum_Q_$question"}});
5733: if ($err) { last; }
5734: }
5735: }
5736: if ($err) {
5737: $r->print("<span class=\"LC_warning\">Unable to accept last correction, an error occurred :$errmsg:</span>");
5738: } else {
5739: &scantron_put_line($scanlines,$scan_data,$which,$line,$skip);
5740: &scantron_putfile($scanlines,$scan_data);
5741: }
5742: }
5743:
5744: =pod
5745:
5746: =item reset_skipping_status
5747:
5748: Forgets the current set of remember skipped scanlines (and thus
5749: reverts back to considering all lines in the
5750: scantron_skipped_<filename> file)
5751:
5752: =cut
5753:
5754: sub reset_skipping_status {
5755: my ($scanlines,$scan_data)=&scantron_getfile();
5756: &scan_data($scan_data,'remember_skipping',undef,1);
5757: &scantron_putfile(undef,$scan_data);
5758: }
5759:
5760: =pod
5761:
5762: =item start_skipping
5763:
5764: Marks a scanline to be skipped.
5765:
5766: =cut
5767:
5768: sub start_skipping {
5769: my ($scan_data,$i)=@_;
5770: my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
5771: if ($env{'form.scantron_options_redo'} =~ /^redo_/) {
5772: $remembered{$i}=2;
5773: } else {
5774: $remembered{$i}=1;
5775: }
5776: &scan_data($scan_data,'remember_skipping',join(':',%remembered));
5777: }
5778:
5779: =pod
5780:
5781: =item should_be_skipped
5782:
5783: Checks whether a scanline should be skipped.
5784:
5785: =cut
5786:
5787: sub should_be_skipped {
5788: my ($scanlines,$scan_data,$i)=@_;
5789: if ($env{'form.scantron_options_redo'} !~ /^redo_/) {
5790: # not redoing old skips
5791: if ($scanlines->{'skipped'}[$i]) { return 1; }
5792: return 0;
5793: }
5794: my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
5795:
5796: if (exists($remembered{$i}) && $remembered{$i} != 2 ) {
5797: return 0;
5798: }
5799: return 1;
5800: }
5801:
5802: =pod
5803:
5804: =item remember_current_skipped
5805:
5806: Discovers what scanlines are in the scantron_skipped_<filename>
5807: file and remembers them into scan_data for later use.
5808:
5809: =cut
5810:
5811: sub remember_current_skipped {
5812: my ($scanlines,$scan_data)=&scantron_getfile();
5813: my %to_remember;
5814: for (my $i=0;$i<=$scanlines->{'count'};$i++) {
5815: if ($scanlines->{'skipped'}[$i]) {
5816: $to_remember{$i}=1;
5817: }
5818: }
5819:
5820: &scan_data($scan_data,'remember_skipping',join(':',%to_remember));
5821: &scantron_putfile(undef,$scan_data);
5822: }
5823:
5824: =pod
5825:
5826: =item check_for_error
5827:
5828: Checks if there was an error when attempting to remove a specific
5829: scantron_.. bubble sheet data file. Prints out an error if
5830: something went wrong.
5831:
5832: =cut
5833:
5834: sub check_for_error {
5835: my ($r,$result)=@_;
5836: if ($result ne 'ok' && $result ne 'not_found' ) {
5837: $r->print(&mt("An error occurred ([_1]) when trying to remove the existing corrections.",$result));
5838: }
5839: }
5840:
5841: =pod
5842:
5843: =item scantron_warning_screen
5844:
5845: Interstitial screen to make sure the operator has selected the
5846: correct options before we start the validation phase.
5847:
5848: =cut
5849:
5850: sub scantron_warning_screen {
5851: my ($button_text)=@_;
5852: my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
5853: my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
5854: my $CODElist;
5855: if ($scantron_config{'CODElocation'} &&
5856: $scantron_config{'CODEstart'} &&
5857: $scantron_config{'CODElength'}) {
5858: $CODElist=$env{'form.scantron_CODElist'};
5859: if ($env{'form.scantron_CODElist'} eq '') { $CODElist='<span class="LC_warning">None</span>'; }
5860: $CODElist=
5861: '<tr><td><b>'.&mt('List of CODES to validate against:').'</b></td><td><tt>'.
5862: $env{'form.scantron_CODElist'}.'</tt></td></tr>';
5863: }
5864: return ('
5865: <p>
5866: <span class="LC_warning">
5867: '.&mt('Please double check the information below before clicking on \'[_1]\'',&mt($button_text)).'</span>
5868: </p>
5869: <table>
5870: <tr><td><b>'.&mt('Sequence to be Graded:').'</b></td><td>'.$title.'</td></tr>
5871: <tr><td><b>'.&mt('Data File that will be used:').'</b></td><td><tt>'.$env{'form.scantron_selectfile'}.'</tt></td></tr>
5872: '.$CODElist.'
5873: </table>
5874: <br />
5875: <p> '.&mt('If this information is correct, please click on \'[_1]\'.',&mt($button_text)).'</p>
5876: <p> '.&mt('If something is incorrect, please click the \'Grading Menu\' button to start over.').'</p>
5877:
5878: <br />
5879: ');
5880: }
5881:
5882: =pod
5883:
5884: =item scantron_do_warning
5885:
5886: Check if the operator has picked something for all required
5887: fields. Error out if something is missing.
5888:
5889: =cut
5890:
5891: sub scantron_do_warning {
5892: my ($r)=@_;
5893: my ($symb)=&get_symb($r);
5894: if (!$symb) {return '';}
5895: my $default_form_data=&defaultFormData($symb);
5896: $r->print(&scantron_form_start().$default_form_data);
5897: if ( $env{'form.selectpage'} eq '' ||
5898: $env{'form.scantron_selectfile'} eq '' ||
5899: $env{'form.scantron_format'} eq '' ) {
5900: $r->print("<p>".&mt('You have forgetten to specify some information. Please go Back and try again.')."</p>");
5901: if ( $env{'form.selectpage'} eq '') {
5902: $r->print('<p><span class="LC_error">'.&mt('You have not selected a Sequence to grade').'</span></p>');
5903: }
5904: if ( $env{'form.scantron_selectfile'} eq '') {
5905: $r->print('<p><span class="LC_error">'.&mt('You have not selected a file that contains the student\'s response data.').'</span></p>');
5906: }
5907: if ( $env{'form.scantron_format'} eq '') {
5908: $r->print('<p><span class="LC_error">'.&mt('You have not selected a the format of the student\'s response data.').'</span></p>');
5909: }
5910: } else {
5911: my $warning=&scantron_warning_screen('Grading: Validate Records');
5912: $r->print('
5913: '.$warning.'
5914: <input type="submit" name="submit" value="'.&mt('Grading: Validate Records').'" />
5915: <input type="hidden" name="command" value="scantron_validate" />
5916: ');
5917: }
5918: $r->print("</form><br />".&show_grading_menu_form($symb));
5919: return '';
5920: }
5921:
5922: =pod
5923:
5924: =item scantron_form_start
5925:
5926: html hidden input for remembering all selected grading options
5927:
5928: =cut
5929:
5930: sub scantron_form_start {
5931: my ($max_bubble)=@_;
5932: my $result= <<SCANTRONFORM;
5933: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
5934: <input type="hidden" name="selectpage" value="$env{'form.selectpage'}" />
5935: <input type="hidden" name="scantron_format" value="$env{'form.scantron_format'}" />
5936: <input type="hidden" name="scantron_selectfile" value="$env{'form.scantron_selectfile'}" />
5937: <input type="hidden" name="scantron_maxbubble" value="$max_bubble" />
5938: <input type="hidden" name="scantron_CODElist" value="$env{'form.scantron_CODElist'}" />
5939: <input type="hidden" name="scantron_CODEunique" value="$env{'form.scantron_CODEunique'}" />
5940: <input type="hidden" name="scantron_options_redo" value="$env{'form.scantron_options_redo'}" />
5941: <input type="hidden" name="scantron_options_ignore" value="$env{'form.scantron_options_ignore'}" />
5942: <input type="hidden" name="scantron_options_hidden" value="$env{'form.scantron_options_hidden'}" />
5943: SCANTRONFORM
5944:
5945: my $line = 0;
5946: while (defined($env{"form.scantron.bubblelines.$line"})) {
5947: my $chunk =
5948: '<input type="hidden" name="scantron.bubblelines.'.$line.'" value="'.$env{"form.scantron.bubblelines.$line"}.'" />'."\n";
5949: $chunk .=
5950: '<input type="hidden" name="scantron.first_bubble_line.'.$line.'" value="'.$env{"form.scantron.first_bubble_line.$line"}.'" />'."\n";
5951: $chunk .=
5952: '<input type="hidden" name="scantron.sub_bubblelines.'.$line.'" value="'.$env{"form.scantron.sub_bubblelines.$line"}.'" />'."\n";
5953: $result .= $chunk;
5954: $line++;
5955: }
5956: return $result;
5957: }
5958:
5959: =pod
5960:
5961: =item scantron_validate_file
5962:
5963: Dispatch routine for doing validation of a bubble sheet data file.
5964:
5965: Also processes any necessary information resets that need to
5966: occur before validation begins (ignore previous corrections,
5967: restarting the skipped records processing)
5968:
5969: =cut
5970:
5971: sub scantron_validate_file {
5972: my ($r) = @_;
5973: my ($symb)=&get_symb($r);
5974: if (!$symb) {return '';}
5975: my $default_form_data=&defaultFormData($symb);
5976:
5977: # do the detection of only doing skipped records first befroe we delete
5978: # them when doing the corrections reset
5979: if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') {
5980: &reset_skipping_status();
5981: }
5982: if ($env{'form.scantron_options_redo'} eq 'redo_skipped') {
5983: &remember_current_skipped();
5984: $env{'form.scantron_options_redo'}='redo_skipped_ready';
5985: }
5986:
5987: if ($env{'form.scantron_options_ignore'} eq 'ignore_corrections') {
5988: &check_for_error($r,&scantron_remove_file('corrected'));
5989: &check_for_error($r,&scantron_remove_file('skipped'));
5990: &check_for_error($r,&scantron_remove_scan_data());
5991: $env{'form.scantron_options_ignore'}='done';
5992: }
5993:
5994: if ($env{'form.scantron_corrections'}) {
5995: &scantron_process_corrections($r);
5996: }
5997: $r->print('<p>'.&mt('Gathering necessary information.').'</p>');$r->rflush();
5998: #get the student pick code ready
5999: $r->print(&Apache::loncommon::studentbrowser_javascript());
6000: my $max_bubble=&scantron_get_maxbubble();
6001: my $result=&scantron_form_start($max_bubble).$default_form_data;
6002: $r->print($result);
6003:
6004: my @validate_phases=( 'sequence',
6005: 'ID',
6006: 'CODE',
6007: 'doublebubble',
6008: 'missingbubbles');
6009: if (!$env{'form.validatepass'}) {
6010: $env{'form.validatepass'} = 0;
6011: }
6012: my $currentphase=$env{'form.validatepass'};
6013:
6014:
6015: my $stop=0;
6016: while (!$stop && $currentphase < scalar(@validate_phases)) {
6017: $r->print(&mt('Validating '.$validate_phases[$currentphase]).'<br />');
6018: $r->rflush();
6019: my $which="scantron_validate_".$validate_phases[$currentphase];
6020: {
6021: no strict 'refs';
6022: ($stop,$currentphase)=&$which($r,$currentphase);
6023: }
6024: }
6025: if (!$stop) {
6026: my $warning=&scantron_warning_screen('Start Grading');
6027: $r->print('
6028: <b>'.&mt('Validation process complete.').'<b><br />
6029: '.$warning.'
6030: <input type="submit" name="submit" value="'.&mt('Start Grading').'" />
6031: <input type="hidden" name="command" value="scantron_process" />
6032: ');
6033:
6034: } else {
6035: $r->print('<input type="hidden" name="command" value="scantron_validate" />');
6036: $r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />");
6037: }
6038: if ($stop) {
6039: if ($validate_phases[$currentphase] eq 'sequence') {
6040: $r->print('<input type="submit" name="submit" value="'.&mt('Ignore ->').' " />');
6041: $r->print(' '.&mt('this error').' <br />');
6042:
6043: $r->print(" <p>".&mt("Or click the 'Grading Menu' button to start over.")."</p>");
6044: } else {
6045: if ($validate_phases[$currentphase] eq 'doublebubble' || $validate_phases[$currentphase] eq 'missingbubbles') {
6046: $r->print('<input type="button" name="submitbutton" value="'.&mt('Continue ->').'" onclick="javascript:verify_bubble_radio(this.form)" />');
6047: } else {
6048: $r->print('<input type="submit" name="submit" value="'.&mt('Continue ->').'" />');
6049: }
6050: $r->print(' '.&mt('using corrected info').' <br />');
6051: $r->print("<input type='submit' value='".&mt("Skip")."' name='scantron_skip_record' />");
6052: $r->print(" ".&mt("this scanline saving it for later."));
6053: }
6054: }
6055: $r->print(" </form><br />".&show_grading_menu_form($symb));
6056: return '';
6057: }
6058:
6059:
6060: =pod
6061:
6062: =item scantron_remove_file
6063:
6064: Removes the requested bubble sheet data file, makes sure that
6065: scantron_original_<filename> is never removed
6066:
6067:
6068: =cut
6069:
6070: sub scantron_remove_file {
6071: my ($which)=@_;
6072: my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
6073: my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
6074: my $file='scantron_';
6075: if ($which eq 'corrected' || $which eq 'skipped') {
6076: $file.=$which.'_';
6077: } else {
6078: return 'refused';
6079: }
6080: $file.=$env{'form.scantron_selectfile'};
6081: return &Apache::lonnet::removeuserfile($cname,$cdom,$file);
6082: }
6083:
6084:
6085: =pod
6086:
6087: =item scantron_remove_scan_data
6088:
6089: Removes all scan_data correction for the requested bubble sheet
6090: data file. (In the case that both the are doing skipped records we need
6091: to remember the old skipped lines for the time being so that element
6092: persists for a while.)
6093:
6094: =cut
6095:
6096: sub scantron_remove_scan_data {
6097: my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
6098: my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
6099: my @keys=&Apache::lonnet::getkeys('nohist_scantrondata',$cdom,$cname);
6100: my @todelete;
6101: my $filename=$env{'form.scantron_selectfile'};
6102: foreach my $key (@keys) {
6103: if ($key=~/^\Q$filename\E_/) {
6104: if ($env{'form.scantron_options_redo'} eq 'redo_skipped_ready' &&
6105: $key=~/remember_skipping/) {
6106: next;
6107: }
6108: push(@todelete,$key);
6109: }
6110: }
6111: my $result;
6112: if (@todelete) {
6113: $result = &Apache::lonnet::del('nohist_scantrondata',
6114: \@todelete,$cdom,$cname);
6115: } else {
6116: $result = 'ok';
6117: }
6118: return $result;
6119: }
6120:
6121:
6122: =pod
6123:
6124: =item scantron_getfile
6125:
6126: Fetches the requested bubble sheet data file (all 3 versions), and
6127: the scan_data hash
6128:
6129: Arguments:
6130: None
6131:
6132: Returns:
6133: 2 hash references
6134:
6135: - first one has
6136: orig -
6137: corrected -
6138: skipped - each of which points to an array ref of the specified
6139: file broken up into individual lines
6140: count - number of scanlines
6141:
6142: - second is the scan_data hash possible keys are
6143: ($number refers to scanline numbered $number and thus the key affects
6144: only that scanline
6145: $bubline refers to the specific bubble line element and the aspects
6146: refers to that specific bubble line element)
6147:
6148: $number.user - username:domain to use
6149: $number.CODE_ignore_dup
6150: - ignore the duplicate CODE error
6151: $number.useCODE
6152: - use the CODE in the scanline as is
6153: $number.no_bubble.$bubline
6154: - it is valid that there is no bubbled in bubble
6155: at $number $bubline
6156: remember_skipping
6157: - a frozen hash containing keys of $number and values
6158: of either
6159: 1 - we are on a 'do skipped records pass' and plan
6160: on processing this line
6161: 2 - we are on a 'do skipped records pass' and this
6162: scanline has been marked to skip yet again
6163:
6164: =cut
6165:
6166: sub scantron_getfile {
6167: #FIXME really would prefer a scantron directory
6168: my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
6169: my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
6170: my $lines;
6171: $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
6172: 'scantron_orig_'.$env{'form.scantron_selectfile'});
6173: my %scanlines;
6174: $scanlines{'orig'}=[(split("\n",$lines,-1))];
6175: my $temp=$scanlines{'orig'};
6176: $scanlines{'count'}=$#$temp;
6177:
6178: $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
6179: 'scantron_corrected_'.$env{'form.scantron_selectfile'});
6180: if ($lines eq '-1') {
6181: $scanlines{'corrected'}=[];
6182: } else {
6183: $scanlines{'corrected'}=[(split("\n",$lines,-1))];
6184: }
6185: $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
6186: 'scantron_skipped_'.$env{'form.scantron_selectfile'});
6187: if ($lines eq '-1') {
6188: $scanlines{'skipped'}=[];
6189: } else {
6190: $scanlines{'skipped'}=[(split("\n",$lines,-1))];
6191: }
6192: my @tmp=&Apache::lonnet::dump('nohist_scantrondata',$cdom,$cname);
6193: if ($tmp[0] =~ /^(error:|no_such_host)/) { @tmp=(); }
6194: my %scan_data = @tmp;
6195: return (\%scanlines,\%scan_data);
6196: }
6197:
6198: =pod
6199:
6200: =item lonnet_putfile
6201:
6202: Wrapper routine to call &Apache::lonnet::finishuserfileupload
6203:
6204: Arguments:
6205: $contents - data to store
6206: $filename - filename to store $contents into
6207:
6208: Returns:
6209: result value from &Apache::lonnet::finishuserfileupload
6210:
6211: =cut
6212:
6213: sub lonnet_putfile {
6214: my ($contents,$filename)=@_;
6215: my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};
6216: my $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'};
6217: $env{'form.sillywaytopassafilearound'}=$contents;
6218: &Apache::lonnet::finishuserfileupload($docuname,$docudom,'sillywaytopassafilearound',$filename);
6219:
6220: }
6221:
6222: =pod
6223:
6224: =item scantron_putfile
6225:
6226: Stores the current version of the bubble sheet data files, and the
6227: scan_data hash. (Does not modify the original version only the
6228: corrected and skipped versions.
6229:
6230: Arguments:
6231: $scanlines - hash ref that looks like the first return value from
6232: &scantron_getfile()
6233: $scan_data - hash ref that looks like the second return value from
6234: &scantron_getfile()
6235:
6236: =cut
6237:
6238: sub scantron_putfile {
6239: my ($scanlines,$scan_data) = @_;
6240: #FIXME really would prefer a scantron directory
6241: my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
6242: my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
6243: if ($scanlines) {
6244: my $prefix='scantron_';
6245: # no need to update orig, shouldn't change
6246: # &lonnet_putfile(join("\n",@{$scanlines->{'orig'}}),$prefix.'orig_'.
6247: # $env{'form.scantron_selectfile'});
6248: &lonnet_putfile(join("\n",@{$scanlines->{'corrected'}}),
6249: $prefix.'corrected_'.
6250: $env{'form.scantron_selectfile'});
6251: &lonnet_putfile(join("\n",@{$scanlines->{'skipped'}}),
6252: $prefix.'skipped_'.
6253: $env{'form.scantron_selectfile'});
6254: }
6255: &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname);
6256: }
6257:
6258: =pod
6259:
6260: =item scantron_get_line
6261:
6262: Returns the correct version of the scanline
6263:
6264: Arguments:
6265: $scanlines - hash ref that looks like the first return value from
6266: &scantron_getfile()
6267: $scan_data - hash ref that looks like the second return value from
6268: &scantron_getfile()
6269: $i - number of the requested line (starts at 0)
6270:
6271: Returns:
6272: A scanline, (either the original or the corrected one if it
6273: exists), or undef if the requested scanline should be
6274: skipped. (Either because it's an skipped scanline, or it's an
6275: unskipped scanline and we are not doing a 'do skipped scanlines'
6276: pass.
6277:
6278: =cut
6279:
6280: sub scantron_get_line {
6281: my ($scanlines,$scan_data,$i)=@_;
6282: if (&should_be_skipped($scanlines,$scan_data,$i)) { return undef; }
6283: #if ($scanlines->{'skipped'}[$i]) { return undef; }
6284: if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$i];}
6285: return $scanlines->{'orig'}[$i];
6286: }
6287:
6288: =pod
6289:
6290: =item scantron_todo_count
6291:
6292: Counts the number of scanlines that need processing.
6293:
6294: Arguments:
6295: $scanlines - hash ref that looks like the first return value from
6296: &scantron_getfile()
6297: $scan_data - hash ref that looks like the second return value from
6298: &scantron_getfile()
6299:
6300: Returns:
6301: $count - number of scanlines to process
6302:
6303: =cut
6304:
6305: sub get_todo_count {
6306: my ($scanlines,$scan_data)=@_;
6307: my $count=0;
6308: for (my $i=0;$i<=$scanlines->{'count'};$i++) {
6309: my $line=&scantron_get_line($scanlines,$scan_data,$i);
6310: if ($line=~/^[\s\cz]*$/) { next; }
6311: $count++;
6312: }
6313: return $count;
6314: }
6315:
6316: =pod
6317:
6318: =item scantron_put_line
6319:
6320: Updates the 'corrected' or 'skipped' versions of the bubble sheet
6321: data file.
6322:
6323: Arguments:
6324: $scanlines - hash ref that looks like the first return value from
6325: &scantron_getfile()
6326: $scan_data - hash ref that looks like the second return value from
6327: &scantron_getfile()
6328: $i - line number to update
6329: $newline - contents of the updated scanline
6330: $skip - if true make the line for skipping and update the
6331: 'skipped' file
6332:
6333: =cut
6334:
6335: sub scantron_put_line {
6336: my ($scanlines,$scan_data,$i,$newline,$skip)=@_;
6337: if ($skip) {
6338: $scanlines->{'skipped'}[$i]=$newline;
6339: &start_skipping($scan_data,$i);
6340: return;
6341: }
6342: $scanlines->{'corrected'}[$i]=$newline;
6343: }
6344:
6345: =pod
6346:
6347: =item scantron_clear_skip
6348:
6349: Remove a line from the 'skipped' file
6350:
6351: Arguments:
6352: $scanlines - hash ref that looks like the first return value from
6353: &scantron_getfile()
6354: $scan_data - hash ref that looks like the second return value from
6355: &scantron_getfile()
6356: $i - line number to update
6357:
6358: =cut
6359:
6360: sub scantron_clear_skip {
6361: my ($scanlines,$scan_data,$i)=@_;
6362: if (exists($scanlines->{'skipped'}[$i])) {
6363: undef($scanlines->{'skipped'}[$i]);
6364: return 1;
6365: }
6366: return 0;
6367: }
6368:
6369: =pod
6370:
6371: =item scantron_filter_not_exam
6372:
6373: Filter routine used by &Apache::lonnavmaps::retrieveResources(), to
6374: filter out resources that are not marked as 'exam' mode
6375:
6376: =cut
6377:
6378: sub scantron_filter_not_exam {
6379: my ($curres)=@_;
6380:
6381: if (ref($curres) && $curres->is_problem() && !$curres->is_exam()) {
6382: # if the user has asked to not have either hidden
6383: # or 'randomout' controlled resources to be graded
6384: # don't include them
6385: if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
6386: && $curres->randomout) {
6387: return 0;
6388: }
6389: return 1;
6390: }
6391: return 0;
6392: }
6393:
6394: =pod
6395:
6396: =item scantron_validate_sequence
6397:
6398: Validates the selected sequence, checking for resource that are
6399: not set to exam mode.
6400:
6401: =cut
6402:
6403: sub scantron_validate_sequence {
6404: my ($r,$currentphase) = @_;
6405:
6406: my $navmap=Apache::lonnavmaps::navmap->new();
6407: my (undef,undef,$sequence)=
6408: &Apache::lonnet::decode_symb($env{'form.selectpage'});
6409:
6410: my $map=$navmap->getResourceByUrl($sequence);
6411:
6412: $r->print('<input type="hidden" name="validate_sequence_exam"
6413: value="ignore" />');
6414: if ($env{'form.validate_sequence_exam'} ne 'ignore') {
6415: my @resources=
6416: $navmap->retrieveResources($map,\&scantron_filter_not_exam,1,0);
6417: if (@resources) {
6418: $r->print("<p>".&mt('Some resources in the sequence currently are not set to exam mode. Grading these resources currently may not work correctly.')."</p>");
6419: return (1,$currentphase);
6420: }
6421: }
6422:
6423: return (0,$currentphase+1);
6424: }
6425:
6426: =pod
6427:
6428: =item scantron_validate_ID
6429:
6430: Validates all scanlines in the selected file to not have any
6431: invalid or underspecified student IDs
6432:
6433: =cut
6434:
6435: sub scantron_validate_ID {
6436: my ($r,$currentphase) = @_;
6437:
6438: #get student info
6439: my $classlist=&Apache::loncoursedata::get_classlist();
6440: my %idmap=&username_to_idmap($classlist);
6441:
6442: #get scantron line setup
6443: my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
6444: my ($scanlines,$scan_data)=&scantron_getfile();
6445:
6446: &scantron_get_maxbubble(); # parse needs the bubble_lines.. array.
6447:
6448: my %found=('ids'=>{},'usernames'=>{});
6449: for (my $i=0;$i<=$scanlines->{'count'};$i++) {
6450: my $line=&scantron_get_line($scanlines,$scan_data,$i);
6451: if ($line=~/^[\s\cz]*$/) { next; }
6452: my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
6453: $scan_data);
6454: my $id=$$scan_record{'scantron.ID'};
6455: my $found;
6456: foreach my $checkid (keys(%idmap)) {
6457: if (lc($checkid) eq lc($id)) { $found=$checkid;last; }
6458: }
6459: if ($found) {
6460: my $username=$idmap{$found};
6461: if ($found{'ids'}{$found}) {
6462: &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
6463: $line,'duplicateID',$found);
6464: return(1,$currentphase);
6465: } elsif ($found{'usernames'}{$username}) {
6466: &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
6467: $line,'duplicateID',$username);
6468: return(1,$currentphase);
6469: }
6470: #FIXME store away line we previously saw the ID on to use above
6471: $found{'ids'}{$found}++;
6472: $found{'usernames'}{$username}++;
6473: } else {
6474: if ($id =~ /^\s*$/) {
6475: my $username=&scan_data($scan_data,"$i.user");
6476: if (defined($username) && $found{'usernames'}{$username}) {
6477: &scantron_get_correction($r,$i,$scan_record,
6478: \%scantron_config,
6479: $line,'duplicateID',$username);
6480: return(1,$currentphase);
6481: } elsif (!defined($username)) {
6482: &scantron_get_correction($r,$i,$scan_record,
6483: \%scantron_config,
6484: $line,'incorrectID');
6485: return(1,$currentphase);
6486: }
6487: $found{'usernames'}{$username}++;
6488: } else {
6489: &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
6490: $line,'incorrectID');
6491: return(1,$currentphase);
6492: }
6493: }
6494: }
6495:
6496: return (0,$currentphase+1);
6497: }
6498:
6499: =pod
6500:
6501: =item scantron_get_correction
6502:
6503: Builds the interface screen to interact with the operator to fix a
6504: specific error condition in a specific scanline
6505:
6506: Arguments:
6507: $r - Apache request object
6508: $i - number of the current scanline
6509: $scan_record - hash ref as returned from &scantron_parse_scanline()
6510: $scan_config - hash ref as returned from &get_scantron_config()
6511: $line - full contents of the current scanline
6512: $error - error condition, valid values are
6513: 'incorrectCODE', 'duplicateCODE',
6514: 'doublebubble', 'missingbubble',
6515: 'duplicateID', 'incorrectID'
6516: $arg - extra information needed
6517: For errors:
6518: - duplicateID - paper number that this studentID was seen before on
6519: - duplicateCODE - array ref of the paper numbers this CODE was
6520: seen on before
6521: - incorrectCODE - current incorrect CODE
6522: - doublebubble - array ref of the bubble lines that have double
6523: bubble errors
6524: - missingbubble - array ref of the bubble lines that have missing
6525: bubble errors
6526:
6527: =cut
6528:
6529: sub scantron_get_correction {
6530: my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_;
6531: #FIXME in the case of a duplicated ID the previous line, probably need
6532: #to show both the current line and the previous one and allow skipping
6533: #the previous one or the current one
6534:
6535: if ( $$scan_record{'scantron.PaperID'} =~ /\S/) {
6536: $r->print("<p>".&mt("<b>An error was detected ($error)</b>".
6537: " for PaperID <tt>[_1]</tt>",
6538: $$scan_record{'scantron.PaperID'})."</p> \n");
6539: } else {
6540: $r->print("<p>".&mt("<b>An error was detected ($error)</b>".
6541: " in scanline [_1] <pre>[_2]</pre>",
6542: $i,$line)."</p> \n");
6543: }
6544: my $message="<p>".&mt("The ID on the form is <tt>[_1]</tt><br />".
6545: "The name on the paper is [_2],[_3]",
6546: $$scan_record{'scantron.ID'},
6547: $$scan_record{'scantron.LastName'},
6548: $$scan_record{'scantron.FirstName'})."</p>";
6549:
6550: $r->print('<input type="hidden" name="scantron_corrections" value="'.$error.'" />'."\n");
6551: $r->print('<input type="hidden" name="scantron_line" value="'.$i.'" />'."\n");
6552: # Array populated for doublebubble or
6553: my @lines_to_correct; # missingbubble errors to build javascript
6554: # to validate radio button checking
6555:
6556: if ($error =~ /ID$/) {
6557: if ($error eq 'incorrectID') {
6558: $r->print("<p>".&mt("The encoded ID is not in the classlist").
6559: "</p>\n");
6560: } elsif ($error eq 'duplicateID') {
6561: $r->print("<p>".&mt("The encoded ID has also been used by a previous paper [_1]",$arg)."</p>\n");
6562: }
6563: $r->print($message);
6564: $r->print("<p>".&mt("How should I handle this?")." <br /> \n");
6565: $r->print("\n<ul><li> ");
6566: #FIXME it would be nice if this sent back the user ID and
6567: #could do partial userID matches
6568: $r->print(&Apache::loncommon::selectstudent_link('scantronupload',
6569: 'scantron_username','scantron_domain'));
6570: $r->print(": <input type='text' name='scantron_username' value='' />");
6571: $r->print("\n@".
6572: &Apache::loncommon::select_dom_form($env{'request.role.domain'},'scantron_domain'));
6573:
6574: $r->print('</li>');
6575: } elsif ($error =~ /CODE$/) {
6576: if ($error eq 'incorrectCODE') {
6577: $r->print("<p>".&mt("The encoded CODE is not in the list of possible CODEs.")."</p>\n");
6578: } elsif ($error eq 'duplicateCODE') {
6579: $r->print("<p>".&mt("The encoded CODE has also been used by a previous paper [_1], and CODEs are supposed to be unique.",join(', ',@{$arg}))."</p>\n");
6580: }
6581: $r->print("<p>".&mt("The CODE on the form is <tt>'[_1]'</tt>",
6582: $$scan_record{'scantron.CODE'})."<br />\n");
6583: $r->print($message);
6584: $r->print("<p>".&mt("How should I handle this?")." <br /> \n");
6585: $r->print("\n<br /> ");
6586: my $i=0;
6587: if ($error eq 'incorrectCODE'
6588: && $$scan_record{'scantron.CODE'}=~/\S/ ) {
6589: my ($max,$closest)=&scantron_get_closely_matching_CODEs($arg,$$scan_record{'scantron.CODE'});
6590: if ($closest > 0) {
6591: foreach my $testcode (@{$closest}) {
6592: my $checked='';
6593: if (!$i) { $checked=' checked="checked" '; }
6594: $r->print("
6595: <label>
6596: <input type='radio' name='scantron_CODE_resolution' value='use_closest_$i' $checked />
6597: ".&mt("Use the similar CODE [_1] instead.",
6598: "<b><tt>".$testcode."</tt></b>")."
6599: </label>
6600: <input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />");
6601: $r->print("\n<br />");
6602: $i++;
6603: }
6604: }
6605: }
6606: if ($$scan_record{'scantron.CODE'}=~/\S/ ) {
6607: my $checked; if (!$i) { $checked=' checked="checked" '; }
6608: $r->print("
6609: <label>
6610: <input type='radio' name='scantron_CODE_resolution' value='use_unfound' $checked />
6611: ".&mt("Use the CODE [_1] that is was on the paper, ignoring the error.",
6612: "<b><tt>".$$scan_record{'scantron.CODE'}."</tt></b>")."
6613: </label>");
6614: $r->print("\n<br />");
6615: }
6616:
6617: $r->print(<<ENDSCRIPT);
6618: <script type="text/javascript">
6619: function change_radio(field) {
6620: var slct=document.scantronupload.scantron_CODE_resolution;
6621: var i;
6622: for (i=0;i<slct.length;i++) {
6623: if (slct[i].value==field) { slct[i].checked=true; }
6624: }
6625: }
6626: </script>
6627: ENDSCRIPT
6628: my $href="/adm/pickcode?".
6629: "form=".&escape("scantronupload").
6630: "&scantron_format=".&escape($env{'form.scantron_format'}).
6631: "&scantron_CODElist=".&escape($env{'form.scantron_CODElist'}).
6632: "&curCODE=".&escape($$scan_record{'scantron.CODE'}).
6633: "&scantron_selectfile=".&escape($env{'form.scantron_selectfile'});
6634: if ($env{'form.scantron_CODElist'} =~ /\S/) {
6635: $r->print("
6636: <label>
6637: <input type='radio' name='scantron_CODE_resolution' value='use_found' />
6638: ".&mt("[_1]Select[_2] a CODE from the list of all CODEs and use it.",
6639: "<a target='_blank' href='$href'>","</a>")."
6640: </label>
6641: ".&mt("Selected CODE is [_1]","<input readonly='true' type='text' size='8' name='scantron_CODE_selectedvalue' onfocus=\"javascript:change_radio('use_found')\" onchange=\"javascript:change_radio('use_found')\" />"));
6642: $r->print("\n<br />");
6643: }
6644: $r->print("
6645: <label>
6646: <input type='radio' name='scantron_CODE_resolution' value='use_typed' />
6647: ".&mt("Use [_1] as the CODE.",
6648: "</label><input type='text' size='8' name='scantron_CODE_newvalue' onfocus=\"javascript:change_radio('use_typed')\" onkeypress=\"javascript:change_radio('use_typed')\" />"));
6649: $r->print("\n<br /><br />");
6650: } elsif ($error eq 'doublebubble') {
6651: $r->print("<p>".&mt("There have been multiple bubbles scanned for some question(s)")."</p>\n");
6652:
6653: # The form field scantron_questions is acutally a list of line numbers.
6654: # represented by this form so:
6655:
6656: my $line_list = &questions_to_line_list($arg);
6657:
6658: $r->print('<input type="hidden" name="scantron_questions" value="'.
6659: $line_list.'" />');
6660: $r->print($message);
6661: $r->print("<p>".&mt("Please indicate which bubble should be used for grading")."</p>");
6662: foreach my $question (@{$arg}) {
6663: my @linenums = &prompt_for_corrections($r,$question,$scan_config,
6664: $scan_record, $error);
6665: push (@lines_to_correct,@linenums);
6666: }
6667: $r->print(&verify_bubbles_checked(@lines_to_correct));
6668: } elsif ($error eq 'missingbubble') {
6669: $r->print("<p>".&mt("There have been <b>no</b> bubbles scanned for some question(s)")."</p>\n");
6670: $r->print($message);
6671: $r->print("<p>".&mt("Please indicate which bubble should be used for grading.")."</p>");
6672: $r->print(&mt("Some questions have no scanned bubbles.")."\n");
6673:
6674: # The form field scantron_questions is actually a list of line numbers not
6675: # a list of question numbers. Therefore:
6676: #
6677:
6678: my $line_list = &questions_to_line_list($arg);
6679:
6680: $r->print('<input type="hidden" name="scantron_questions" value="'.
6681: $line_list.'" />');
6682: foreach my $question (@{$arg}) {
6683: my @linenums = &prompt_for_corrections($r,$question,$scan_config,
6684: $scan_record, $error);
6685: push (@lines_to_correct,@linenums);
6686: }
6687: $r->print(&verify_bubbles_checked(@lines_to_correct));
6688: } else {
6689: $r->print("\n<ul>");
6690: }
6691: $r->print("\n</li></ul>");
6692: }
6693:
6694: sub verify_bubbles_checked {
6695: my (@ansnums) = @_;
6696: my $ansnumstr = join('","',@ansnums);
6697: my $warning = &mt("A bubble or 'No bubble' selection has not been made for one or more lines.");
6698: my $output = (<<ENDSCRIPT);
6699: <script type="text/javascript">
6700: function verify_bubble_radio(form) {
6701: var ansnumArray = new Array ("$ansnumstr");
6702: var need_bubble_count = 0;
6703: for (var i=0; i<ansnumArray.length; i++) {
6704: if (form.elements["scantron_correct_Q_"+ansnumArray[i]].length > 1) {
6705: var bubble_picked = 0;
6706: for (var j=0; j<form.elements["scantron_correct_Q_"+ansnumArray[i]].length; j++) {
6707: if (form.elements["scantron_correct_Q_"+ansnumArray[i]][j].checked == true) {
6708: bubble_picked = 1;
6709: }
6710: }
6711: if (bubble_picked == 0) {
6712: need_bubble_count ++;
6713: }
6714: }
6715: }
6716: if (need_bubble_count) {
6717: alert("$warning");
6718: return;
6719: }
6720: form.submit();
6721: }
6722: </script>
6723: ENDSCRIPT
6724: return $output;
6725: }
6726:
6727: =pod
6728:
6729: =item questions_to_line_list
6730:
6731: Converts a list of questions into a string of comma separated
6732: line numbers in the answer sheet used by the questions. This is
6733: used to fill in the scantron_questions form field.
6734:
6735: Arguments:
6736: questions - Reference to an array of questions.
6737:
6738: =cut
6739:
6740:
6741: sub questions_to_line_list {
6742: my ($questions) = @_;
6743: my @lines;
6744:
6745: foreach my $item (@{$questions}) {
6746: my $question = $item;
6747: my ($first,$count,$last);
6748: if ($item =~ /^(\d+)\.(\d+)$/) {
6749: $question = $1;
6750: my $subquestion = $2;
6751: $first = $first_bubble_line{$question-1} + 1;
6752: my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
6753: my $subcount = 1;
6754: while ($subcount<$subquestion) {
6755: $first += $subans[$subcount-1];
6756: $subcount ++;
6757: }
6758: $count = $subans[$subquestion-1];
6759: } else {
6760: $first = $first_bubble_line{$question-1} + 1;
6761: $count = $bubble_lines_per_response{$question-1};
6762: }
6763: my $last = $first+$count-1;
6764: push(@lines, ($first..$last));
6765: }
6766: return join(',', @lines);
6767: }
6768:
6769: =pod
6770:
6771: =item prompt_for_corrections
6772:
6773: Prompts for a potentially multiline correction to the
6774: user's bubbling (factors out common code from scantron_get_correction
6775: for multi and missing bubble cases).
6776:
6777: Arguments:
6778: $r - Apache request object.
6779: $question - The question number to prompt for.
6780: $scan_config - The scantron file configuration hash.
6781: $scan_record - Reference to the hash that has the the parsed scanlines.
6782: $error - Type of error
6783:
6784: Implicit inputs:
6785: %bubble_lines_per_response - Starting line numbers for each question.
6786: Numbered from 0 (but question numbers are from
6787: 1.
6788: %first_bubble_line - Starting bubble line for each question.
6789: %subdivided_bubble_lines - optionresponse and matchresponse type
6790: problems render as separate sub-questions,
6791: in exam mode. This hash contains a
6792: comma-separated list of the lines per
6793: sub-question.
6794: %responsetype_per_response - essayresponse, forumalaresponse, and
6795: stringresponse type problem parts can have
6796: multiple lines per response if the weight
6797: assigned exceeds 10. In this case, only
6798: one bubble per line is permitted, but more
6799: than one line might contain bubbles, e.g.
6800: bubbling of: line 1 - J, line 2 - J,
6801: line 3 - B would assign 22 points.
6802:
6803: =cut
6804:
6805: sub prompt_for_corrections {
6806: my ($r, $question, $scan_config, $scan_record, $error) = @_;
6807: my ($current_line,$lines);
6808: my @linenums;
6809: my $questionnum = $question;
6810: if ($question =~ /^(\d+)\.(\d+)$/) {
6811: $question = $1;
6812: $current_line = $first_bubble_line{$question-1} + 1 ;
6813: my $subquestion = $2;
6814: my @subans = split(/,/,$subdivided_bubble_lines{$question-1});
6815: my $subcount = 1;
6816: while ($subcount<$subquestion) {
6817: $current_line += $subans[$subcount-1];
6818: $subcount ++;
6819: }
6820: $lines = $subans[$subquestion-1];
6821: } else {
6822: $current_line = $first_bubble_line{$question-1} + 1 ;
6823: $lines = $bubble_lines_per_response{$question-1};
6824: }
6825: if ($lines > 1) {
6826: $r->print(&mt('The group of bubble lines below responds to a single question.').'<br />');
6827: if (($responsetype_per_response{$question-1} eq 'essayresponse') ||
6828: ($responsetype_per_response{$question-1} eq 'formularesponse') ||
6829: ($responsetype_per_response{$question-1} eq 'stringresponse')) {
6830: $r->print(&mt("Although this particular question type requires handgrading, the instructions for this question in the exam directed students to leave [quant,_1,line] blank on their scantron sheets.",$lines).'<br /><br />'.&mt('A non-zero score can be assigned to the student during scantron grading by selecting a bubble in at least one line.').'<br />'.&mt('The score for this question will be a sum of the numeric values for the selected bubbles from each line, where A=1 point, B=2 points etc.').'<br />'.&mt("To assign a score of zero for this question, mark all lines as 'No bubble'.").'<br /><br />');
6831: } else {
6832: $r->print(&mt("Select at most one bubble in a single line and select 'No Bubble' in all the other lines. ")."<br />");
6833: }
6834: }
6835: for (my $i =0; $i < $lines; $i++) {
6836: my $selected = $$scan_record{"scantron.$current_line.answer"};
6837: &scantron_bubble_selector($r,$scan_config,$current_line,
6838: $questionnum,$error,split('', $selected));
6839: push (@linenums,$current_line);
6840: $current_line++;
6841: }
6842: if ($lines > 1) {
6843: $r->print("<hr /><br />");
6844: }
6845: return @linenums;
6846: }
6847:
6848: =pod
6849:
6850: =item scantron_bubble_selector
6851:
6852: Generates the html radiobuttons to correct a single bubble line
6853: possibly showing the existing the selected bubbles if known
6854:
6855: Arguments:
6856: $r - Apache request object
6857: $scan_config - hash from &get_scantron_config()
6858: $line - Number of the line being displayed.
6859: $questionnum - Question number (may include subquestion)
6860: $error - Type of error.
6861: @selected - Array of bubbles picked on this line.
6862:
6863: =cut
6864:
6865: sub scantron_bubble_selector {
6866: my ($r,$scan_config,$line,$questionnum,$error,@selected)=@_;
6867: my $max=$$scan_config{'Qlength'};
6868:
6869: my $scmode=$$scan_config{'Qon'};
6870: if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }
6871:
6872: my @alphabet=('A'..'Z');
6873: $r->print(&Apache::loncommon::start_data_table().
6874: &Apache::loncommon::start_data_table_row());
6875: $r->print('<td rowspan="2" class="LC_leftcol_header">'.$line.'</td>');
6876: for (my $i=0;$i<$max+1;$i++) {
6877: $r->print("\n".'<td align="center">');
6878: if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) }
6879: else { $r->print(' '); }
6880: $r->print('</td>');
6881: }
6882: $r->print(&Apache::loncommon::end_data_table_row().
6883: &Apache::loncommon::start_data_table_row());
6884: for (my $i=0;$i<$max;$i++) {
6885: $r->print("\n".
6886: '<td><label><input type="radio" name="scantron_correct_Q_'.
6887: $line.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");
6888: }
6889: my $nobub_checked = ' ';
6890: if ($error eq 'missingbubble') {
6891: $nobub_checked = ' checked = "checked" ';
6892: }
6893: $r->print("\n".'<td><label><input type="radio" name="scantron_correct_Q_'.
6894: $line.'" value="none"'.$nobub_checked.'/>'.&mt('No bubble').
6895: '</label>'."\n".'<input type="hidden" name="scantron_questionnum_Q_'.
6896: $line.'" value="'.$questionnum.'" /></td>');
6897: $r->print(&Apache::loncommon::end_data_table_row().
6898: &Apache::loncommon::end_data_table());
6899: }
6900:
6901: =pod
6902:
6903: =item num_matches
6904:
6905: Counts the number of characters that are the same between the two arguments.
6906:
6907: Arguments:
6908: $orig - CODE from the scanline
6909: $code - CODE to match against
6910:
6911: Returns:
6912: $count - integer count of the number of same characters between the
6913: two arguments
6914:
6915: =cut
6916:
6917: sub num_matches {
6918: my ($orig,$code) = @_;
6919: my @code=split(//,$code);
6920: my @orig=split(//,$orig);
6921: my $same=0;
6922: for (my $i=0;$i<scalar(@code);$i++) {
6923: if ($code[$i] eq $orig[$i]) { $same++; }
6924: }
6925: return $same;
6926: }
6927:
6928: =pod
6929:
6930: =item scantron_get_closely_matching_CODEs
6931:
6932: Cycles through all CODEs and finds the set that has the greatest
6933: number of same characters as the provided CODE
6934:
6935: Arguments:
6936: $allcodes - hash ref returned by &get_codes()
6937: $CODE - CODE from the current scanline
6938:
6939: Returns:
6940: 2 element list
6941: - first elements is number of how closely matching the best fit is
6942: (5 means best set has 5 matching characters)
6943: - second element is an arrary ref containing the set of valid CODEs
6944: that best fit the passed in CODE
6945:
6946: =cut
6947:
6948: sub scantron_get_closely_matching_CODEs {
6949: my ($allcodes,$CODE)=@_;
6950: my @CODEs;
6951: foreach my $testcode (sort(keys(%{$allcodes}))) {
6952: push(@{$CODEs[&num_matches($CODE,$testcode)]},$testcode);
6953: }
6954:
6955: return ($#CODEs,$CODEs[-1]);
6956: }
6957:
6958: =pod
6959:
6960: =item get_codes
6961:
6962: Builds a hash which has keys of all of the valid CODEs from the selected
6963: set of remembered CODEs.
6964:
6965: Arguments:
6966: $old_name - name of the set of remembered CODEs
6967: $cdom - domain of the course
6968: $cnum - internal course name
6969:
6970: Returns:
6971: %allcodes - keys are the valid CODEs, values are all 1
6972:
6973: =cut
6974:
6975: sub get_codes {
6976: my ($old_name, $cdom, $cnum) = @_;
6977: if (!$old_name) {
6978: $old_name=$env{'form.scantron_CODElist'};
6979: }
6980: if (!$cdom) {
6981: $cdom =$env{'course.'.$env{'request.course.id'}.'.domain'};
6982: }
6983: if (!$cnum) {
6984: $cnum =$env{'course.'.$env{'request.course.id'}.'.num'};
6985: }
6986: my %result=&Apache::lonnet::get('CODEs',[$old_name,"type\0$old_name"],
6987: $cdom,$cnum);
6988: my %allcodes;
6989: if ($result{"type\0$old_name"} eq 'number') {
6990: %allcodes=map {($_,1)} split(',',$result{$old_name});
6991: } else {
6992: %allcodes=map {(&Apache::lonprintout::num_to_letters($_),1)} split(',',$result{$old_name});
6993: }
6994: return %allcodes;
6995: }
6996:
6997: =pod
6998:
6999: =item scantron_validate_CODE
7000:
7001: Validates all scanlines in the selected file to not have any
7002: invalid or underspecified CODEs and that none of the codes are
7003: duplicated if this was requested.
7004:
7005: =cut
7006:
7007: sub scantron_validate_CODE {
7008: my ($r,$currentphase) = @_;
7009: my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
7010: if ($scantron_config{'CODElocation'} &&
7011: $scantron_config{'CODEstart'} &&
7012: $scantron_config{'CODElength'}) {
7013: if (!defined($env{'form.scantron_CODElist'})) {
7014: &FIXME_blow_up()
7015: }
7016: } else {
7017: return (0,$currentphase+1);
7018: }
7019:
7020: my %usedCODEs;
7021:
7022: my %allcodes=&get_codes();
7023:
7024: &scantron_get_maxbubble(); # parse needs the lines per response array.
7025:
7026: my ($scanlines,$scan_data)=&scantron_getfile();
7027: for (my $i=0;$i<=$scanlines->{'count'};$i++) {
7028: my $line=&scantron_get_line($scanlines,$scan_data,$i);
7029: if ($line=~/^[\s\cz]*$/) { next; }
7030: my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
7031: $scan_data);
7032: my $CODE=$$scan_record{'scantron.CODE'};
7033: my $error=0;
7034: if (!&Apache::lonnet::validCODE($CODE)) {
7035: &scantron_get_correction($r,$i,$scan_record,
7036: \%scantron_config,
7037: $line,'incorrectCODE',\%allcodes);
7038: return(1,$currentphase);
7039: }
7040: if (%allcodes && !exists($allcodes{$CODE})
7041: && !$$scan_record{'scantron.useCODE'}) {
7042: &scantron_get_correction($r,$i,$scan_record,
7043: \%scantron_config,
7044: $line,'incorrectCODE',\%allcodes);
7045: return(1,$currentphase);
7046: }
7047: if (exists($usedCODEs{$CODE})
7048: && $env{'form.scantron_CODEunique'} eq 'yes'
7049: && !$$scan_record{'scantron.CODE_ignore_dup'}) {
7050: &scantron_get_correction($r,$i,$scan_record,
7051: \%scantron_config,
7052: $line,'duplicateCODE',$usedCODEs{$CODE});
7053: return(1,$currentphase);
7054: }
7055: push (@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});
7056: }
7057: return (0,$currentphase+1);
7058: }
7059:
7060: =pod
7061:
7062: =item scantron_validate_doublebubble
7063:
7064: Validates all scanlines in the selected file to not have any
7065: bubble lines with multiple bubbles marked.
7066:
7067: =cut
7068:
7069: sub scantron_validate_doublebubble {
7070: my ($r,$currentphase) = @_;
7071: #get student info
7072: my $classlist=&Apache::loncoursedata::get_classlist();
7073: my %idmap=&username_to_idmap($classlist);
7074:
7075: #get scantron line setup
7076: my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
7077: my ($scanlines,$scan_data)=&scantron_getfile();
7078: &scantron_get_maxbubble(); # parse needs the bubble line array.
7079:
7080: for (my $i=0;$i<=$scanlines->{'count'};$i++) {
7081: my $line=&scantron_get_line($scanlines,$scan_data,$i);
7082: if ($line=~/^[\s\cz]*$/) { next; }
7083: my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
7084: $scan_data);
7085: if (!defined($$scan_record{'scantron.doubleerror'})) { next; }
7086: &scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line,
7087: 'doublebubble',
7088: $$scan_record{'scantron.doubleerror'});
7089: return (1,$currentphase);
7090: }
7091: return (0,$currentphase+1);
7092: }
7093:
7094: =pod
7095:
7096: =item scantron_get_maxbubble
7097:
7098: Returns the maximum number of bubble lines that are expected to
7099: occur. Does this by walking the selected sequence rendering the
7100: resource and then checking &Apache::lonxml::get_problem_counter()
7101: for what the current value of the problem counter is.
7102:
7103: Caches the results to $env{'form.scantron_maxbubble'},
7104: $env{'form.scantron.bubble_lines.n'},
7105: $env{'form.scantron.first_bubble_line.n'} and
7106: $env{"form.scantron.sub_bubblelines.n"}
7107: which are the total number of bubble, lines, the number of bubble
7108: lines for response n and number of the first bubble line for response n,
7109: and a comma separated list of numbers of bubble lines for sub-questions
7110: (for optionresponse items only), for response n.
7111:
7112: =cut
7113:
7114: sub scantron_get_maxbubble {
7115: if (defined($env{'form.scantron_maxbubble'}) &&
7116: $env{'form.scantron_maxbubble'}) {
7117: &restore_bubble_lines();
7118: return $env{'form.scantron_maxbubble'};
7119: }
7120:
7121: my (undef, undef, $sequence) =
7122: &Apache::lonnet::decode_symb($env{'form.selectpage'});
7123:
7124: my $navmap=Apache::lonnavmaps::navmap->new();
7125: my $map=$navmap->getResourceByUrl($sequence);
7126: my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
7127:
7128: &Apache::lonxml::clear_problem_counter();
7129:
7130: my $uname = $env{'form.student'};
7131: my $udom = $env{'form.userdom'};
7132: my $cid = $env{'request.course.id'};
7133: my $total_lines = 0;
7134: %bubble_lines_per_response = ();
7135: %first_bubble_line = ();
7136: %subdivided_bubble_lines = ();
7137: %responsetype_per_response = ();
7138:
7139: my $response_number = 0;
7140: my $bubble_line = 0;
7141: foreach my $resource (@resources) {
7142: # Need to retrieve part IDs and response IDs because essayresponse
7143: # items are not included in $analysis{'parts'} from lonnet::ssi.
7144: my %possible_part_ids;
7145: if (ref($resource->parts()) eq 'ARRAY') {
7146: foreach my $part (@{$resource->parts()}) {
7147: my @resp_ids = $resource->responseIds($part);
7148: foreach my $id (@resp_ids) {
7149: $possible_part_ids{$part.'.'.$id} = 1;
7150: }
7151: }
7152: }
7153: my $result=&Apache::lonnet::ssi($resource->src(),
7154: ('symb' => $resource->symb()),
7155: ('grade_target' => 'analyze'),
7156: ('grade_courseid' => $cid),
7157: ('grade_domain' => $udom),
7158: ('grade_username' => $uname));
7159: my (undef, $an) =
7160: split(/_HASH_REF__/,$result, 2);
7161:
7162: my @parts;
7163:
7164: my %analysis = &Apache::lonnet::str2hash($an);
7165:
7166: if (ref($analysis{'parts'}) eq 'ARRAY') {
7167: @parts = @{$analysis{'parts'}};
7168: }
7169: # Add part_ids for any essayresponse items.
7170: foreach my $part_id (keys(%possible_part_ids)) {
7171: if ($analysis{$part_id.'.type'} eq 'essayresponse') {
7172: if (!grep(/^\Q$part_id\E$/,@parts)) {
7173: push (@parts,$part_id);
7174: }
7175: }
7176: }
7177:
7178: foreach my $part_id (@parts) {
7179: my $lines = $analysis{"$part_id.bubble_lines"};
7180:
7181: # TODO - make this a persistent hash not an array.
7182:
7183: # optionresponse and matchresponse type items render as
7184: # separate sub-questions in exam mode.
7185: if (($analysis{$part_id.'.type'} eq 'optionresponse') ||
7186: ($analysis{$part_id.'.type'} eq 'matchresponse')) {
7187: my ($numbub,$numshown);
7188: if ($analysis{$part_id.'.type'} eq 'optionresponse') {
7189: if (ref($analysis{$part_id.'.options'}) eq 'ARRAY') {
7190: $numbub = scalar(@{$analysis{$part_id.'.options'}});
7191: }
7192: } elsif ($analysis{$part_id.'.type'} eq 'matchresponse') {
7193: if (ref($analysis{$part_id.'.items'}) eq 'ARRAY') {
7194: $numbub = scalar(@{$analysis{$part_id.'.items'}});
7195: }
7196: }
7197: if (ref($analysis{$part_id.'.shown'}) eq 'ARRAY') {
7198: $numshown = scalar(@{$analysis{$part_id.'.shown'}});
7199: }
7200: my $bubbles_per_line = 10;
7201: my $inner_bubble_lines = int($numshown/$bubbles_per_line);
7202: if (($numshown % $bubbles_per_line) != 0) {
7203: $inner_bubble_lines++;
7204: }
7205: for (my $i=0; $i<$numshown; $i++) {
7206: $subdivided_bubble_lines{$response_number} .=
7207: $inner_bubble_lines.',';
7208: }
7209: $subdivided_bubble_lines{$response_number} =~ s/,$//;
7210: }
7211:
7212: $first_bubble_line{$response_number} = $bubble_line;
7213: $bubble_lines_per_response{$response_number} = $lines;
7214: $responsetype_per_response{$response_number} =
7215: $analysis{$part_id.'.type'};
7216: $response_number++;
7217:
7218: $bubble_line += $lines;
7219: $total_lines += $lines;
7220: }
7221:
7222: }
7223: &Apache::lonnet::delenv('scantron\.');
7224:
7225: &save_bubble_lines();
7226: $env{'form.scantron_maxbubble'} =
7227: $total_lines;
7228: return $env{'form.scantron_maxbubble'};
7229: }
7230:
7231: =pod
7232:
7233: =item scantron_validate_missingbubbles
7234:
7235: Validates all scanlines in the selected file to not have any
7236: answers that don't have bubbles that have not been verified
7237: to be bubble free.
7238:
7239: =cut
7240:
7241: sub scantron_validate_missingbubbles {
7242: my ($r,$currentphase) = @_;
7243: #get student info
7244: my $classlist=&Apache::loncoursedata::get_classlist();
7245: my %idmap=&username_to_idmap($classlist);
7246:
7247: #get scantron line setup
7248: my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
7249: my ($scanlines,$scan_data)=&scantron_getfile();
7250: my $max_bubble=&scantron_get_maxbubble();
7251: if (!$max_bubble) { $max_bubble=2**31; }
7252: for (my $i=0;$i<=$scanlines->{'count'};$i++) {
7253: my $line=&scantron_get_line($scanlines,$scan_data,$i);
7254: if ($line=~/^[\s\cz]*$/) { next; }
7255: my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
7256: $scan_data);
7257: if (!defined($$scan_record{'scantron.missingerror'})) { next; }
7258: my @to_correct;
7259:
7260: # Probably here's where the error is...
7261:
7262: foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {
7263: if ($missing > $max_bubble) { next; }
7264: push(@to_correct,$missing);
7265: }
7266: if (@to_correct) {
7267: &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
7268: $line,'missingbubble',\@to_correct);
7269: return (1,$currentphase);
7270: }
7271:
7272: }
7273: return (0,$currentphase+1);
7274: }
7275:
7276: =pod
7277:
7278: =item scantron_process_students
7279:
7280: Routine that does the actual grading of the bubble sheet information.
7281:
7282: The parsed scanline hash is added to %env
7283:
7284: Then foreach unskipped scanline it does an &Apache::lonnet::ssi()
7285: foreach resource , with the form data of
7286:
7287: 'submitted' =>'scantron'
7288: 'grade_target' =>'grade',
7289: 'grade_username'=> username of student
7290: 'grade_domain' => domain of student
7291: 'grade_courseid'=> of course
7292: 'grade_symb' => symb of resource to grade
7293:
7294: This triggers a grading pass. The problem grading code takes care
7295: of converting the bubbled letter information (now in %env) into a
7296: valid submission.
7297:
7298: =cut
7299:
7300: sub scantron_process_students {
7301: my ($r) = @_;
7302: my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
7303: my ($symb)=&get_symb($r);
7304: if (!$symb) {return '';}
7305: my $default_form_data=&defaultFormData($symb);
7306:
7307: my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
7308: my ($scanlines,$scan_data)=&scantron_getfile();
7309: my $classlist=&Apache::loncoursedata::get_classlist();
7310: my %idmap=&username_to_idmap($classlist);
7311: my $navmap=Apache::lonnavmaps::navmap->new();
7312: my $map=$navmap->getResourceByUrl($sequence);
7313: my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
7314: # $r->print("geto ".scalar(@resources)."<br />");
7315: my $result= <<SCANTRONFORM;
7316: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
7317: <input type="hidden" name="command" value="scantron_configphase" />
7318: $default_form_data
7319: SCANTRONFORM
7320: $r->print($result);
7321:
7322: my @delayqueue;
7323: my %completedstudents;
7324:
7325: my $count=&get_todo_count($scanlines,$scan_data);
7326: my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron Status',
7327: 'Scantron Progress',$count,
7328: 'inline',undef,'scantronupload');
7329: &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
7330: 'Processing first student');
7331: my $start=&Time::HiRes::time();
7332: my $i=-1;
7333: my ($uname,$udom,$started);
7334:
7335: &scantron_get_maxbubble(); # Need the bubble lines array to parse.
7336:
7337: while ($i<$scanlines->{'count'}) {
7338: ($uname,$udom)=('','');
7339: $i++;
7340: my $line=&scantron_get_line($scanlines,$scan_data,$i);
7341: if ($line=~/^[\s\cz]*$/) { next; }
7342: if ($started) {
7343: &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
7344: 'last student');
7345: }
7346: $started=1;
7347: my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
7348: $scan_data);
7349: unless ($uname=&scantron_find_student($scan_record,$scan_data,
7350: \%idmap,$i)) {
7351: &scantron_add_delay(\@delayqueue,$line,
7352: 'Unable to find a student that matches',1);
7353: next;
7354: }
7355: if (exists $completedstudents{$uname}) {
7356: &scantron_add_delay(\@delayqueue,$line,
7357: 'Student '.$uname.' has multiple sheets',2);
7358: next;
7359: }
7360: ($uname,$udom)=split(/:/,$uname);
7361:
7362: &Apache::lonxml::clear_problem_counter();
7363: &Apache::lonnet::appenv(%$scan_record);
7364:
7365: if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
7366: &scantron_putfile($scanlines,$scan_data);
7367: }
7368:
7369: my $i=0;
7370: foreach my $resource (@resources) {
7371: $i++;
7372: my %form=('submitted' =>'scantron',
7373: 'grade_target' =>'grade',
7374: 'grade_username'=>$uname,
7375: 'grade_domain' =>$udom,
7376: 'grade_courseid'=>$env{'request.course.id'},
7377: 'grade_symb' =>$resource->symb());
7378: if (exists($scan_record->{'scantron.CODE'})
7379: &&
7380: &Apache::lonnet::validCODE($scan_record->{'scantron.CODE'})) {
7381: $form{'CODE'}=$scan_record->{'scantron.CODE'};
7382: } else {
7383: $form{'CODE'}='';
7384: }
7385: my $result=&Apache::lonnet::ssi($resource->src(),%form);
7386: if ($result ne '') {
7387: }
7388: if (&Apache::loncommon::connection_aborted($r)) { last; }
7389: }
7390: $completedstudents{$uname}={'line'=>$line};
7391: if (&Apache::loncommon::connection_aborted($r)) { last; }
7392: } continue {
7393: &Apache::lonxml::clear_problem_counter();
7394: &Apache::lonnet::delenv('scantron\.');
7395: }
7396: &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
7397: # my $lasttime = &Time::HiRes::time()-$start;
7398: # $r->print("<p>took $lasttime</p>");
7399:
7400: $r->print("</form>");
7401: $r->print(&show_grading_menu_form($symb));
7402: return '';
7403: }
7404:
7405: =pod
7406:
7407: =item scantron_upload_scantron_data
7408:
7409: Creates the screen for adding a new bubble sheet data file to a course.
7410:
7411: =cut
7412:
7413: sub scantron_upload_scantron_data {
7414: my ($r)=@_;
7415: $r->print(&Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}));
7416: my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
7417: 'domainid',
7418: 'coursename');
7419: my $domsel=&Apache::loncommon::select_dom_form($env{'request.role.domain'},
7420: 'domainid');
7421: my $default_form_data=&defaultFormData(&get_symb($r,1));
7422: $r->print('
7423: <script type="text/javascript" language="javascript">
7424: function checkUpload(formname) {
7425: if (formname.upfile.value == "") {
7426: alert("Please use the browse button to select a file from your local directory.");
7427: return false;
7428: }
7429: formname.submit();
7430: }
7431: </script>
7432:
7433: <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
7434: '.$default_form_data.'
7435: <table>
7436: <tr><td>'.$select_link.' </td></tr>
7437: <tr><td>'.&mt('Course ID:').' </td>
7438: <td><input name="courseid" type="text" /> </td></tr>
7439: <tr><td>'.&mt('Course Name:').' </td>
7440: <td><input name="coursename" type="text" /> </td></tr>
7441: <tr><td>'.&mt('Domain:').' </td>
7442: <td>'.$domsel.' </td></tr>
7443: <tr><td>'.&mt('File to upload:').'</td>
7444: <td><input type="file" name="upfile" size="50" /></td></tr>
7445: </table>
7446: <input name="command" value="scantronupload_save" type="hidden" />
7447: <input type="button" onClick="javascript:checkUpload(this.form);" value="'.&mt('Upload Scantron Data').'" />
7448: </form>
7449: ');
7450: return '';
7451: }
7452:
7453: =pod
7454:
7455: =item scantron_upload_scantron_data_save
7456:
7457: Adds a provided bubble information data file to the course if user
7458: has the correct privileges to do so.
7459:
7460: =cut
7461:
7462: sub scantron_upload_scantron_data_save {
7463: my($r)=@_;
7464: my ($symb)=&get_symb($r,1);
7465: my $doanotherupload=
7466: '<br /><form action="/adm/grades" method="post">'."\n".
7467: '<input type="hidden" name="command" value="scantronupload" />'."\n".
7468: '<input type="submit" name="submit" value="'.&mt('Do Another Upload').'" />'."\n".
7469: '</form>'."\n";
7470: if (!&Apache::lonnet::allowed('usc',$env{'form.domainid'}) &&
7471: !&Apache::lonnet::allowed('usc',
7472: $env{'form.domainid'}.'_'.$env{'form.courseid'})) {
7473: $r->print(&mt("You are not allowed to upload Scantron data to the requested course.")."<br />");
7474: if ($symb) {
7475: $r->print(&show_grading_menu_form($symb));
7476: } else {
7477: $r->print($doanotherupload);
7478: }
7479: return '';
7480: }
7481: my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
7482: $r->print(&mt("Doing upload to [_1]",$coursedata{'description'})." <br />");
7483: my $fname=$env{'form.upfile.filename'};
7484: #FIXME
7485: #copied from lonnet::userfileupload()
7486: #make that function able to target a specified course
7487: # Replace Windows backslashes by forward slashes
7488: $fname=~s/\\/\//g;
7489: # Get rid of everything but the actual filename
7490: $fname=~s/^.*\/([^\/]+)$/$1/;
7491: # Replace spaces by underscores
7492: $fname=~s/\s+/\_/g;
7493: # Replace all other weird characters by nothing
7494: $fname=~s/[^\w\.\-]//g;
7495: # See if there is anything left
7496: unless ($fname) { return 'error: no uploaded file'; }
7497: my $uploadedfile=$fname;
7498: $fname='scantron_orig_'.$fname;
7499: if (length($env{'form.upfile'}) < 2) {
7500: $r->print(&mt("<span class=\"LC_error\">Error:</span> The file you attempted to upload, [_1] contained no information. Please check that you entered the correct filename.",'<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</span>"));
7501: } else {
7502: my $result=&Apache::lonnet::finishuserfileupload($env{'form.courseid'},$env{'form.domainid'},'upfile',$fname);
7503: if ($result =~ m|^/uploaded/|) {
7504: $r->print(&mt("<span class=\"LC_success\">Success:</span> Successfully uploaded [_1] bytes of data into location [_2]",
7505: (length($env{'form.upfile'})-1),
7506: '<span class="LC_filename">'.$result."</span>"));
7507: } else {
7508: $r->print(&mt("<span class=\"LC_error\">Error:</span> An error ([_1]) occurred when attempting to upload the file, [_2]",
7509: $result,
7510: '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</span>"));
7511:
7512: }
7513: }
7514: if ($symb) {
7515: $r->print(&scantron_selectphase($r,$uploadedfile));
7516: } else {
7517: $r->print($doanotherupload);
7518: }
7519: return '';
7520: }
7521:
7522: =pod
7523:
7524: =item valid_file
7525:
7526: Validates that the requested bubble data file exists in the course.
7527:
7528: =cut
7529:
7530: sub valid_file {
7531: my ($requested_file)=@_;
7532: foreach my $filename (sort(&scantron_filenames())) {
7533: if ($requested_file eq $filename) { return 1; }
7534: }
7535: return 0;
7536: }
7537:
7538: =pod
7539:
7540: =item scantron_download_scantron_data
7541:
7542: Shows a list of the three internal files (original, corrected,
7543: skipped) for a specific bubble sheet data file that exists in the
7544: course.
7545:
7546: =cut
7547:
7548: sub scantron_download_scantron_data {
7549: my ($r)=@_;
7550: my $default_form_data=&defaultFormData(&get_symb($r,1));
7551: my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
7552: my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
7553: my $file=$env{'form.scantron_selectfile'};
7554: if (! &valid_file($file)) {
7555: $r->print('
7556: <p>
7557: '.&mt('The requested file name was invalid.').'
7558: </p>
7559: ');
7560: $r->print(&show_grading_menu_form(&get_symb($r,1)));
7561: return;
7562: }
7563: my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file;
7564: my $corrected='/uploaded/'.$cdom.'/'.$cname.'/scantron_corrected_'.$file;
7565: my $skipped='/uploaded/'.$cdom.'/'.$cname.'/scantron_skipped_'.$file;
7566: &Apache::lonnet::allowuploaded('/adm/grades',$orig);
7567: &Apache::lonnet::allowuploaded('/adm/grades',$corrected);
7568: &Apache::lonnet::allowuploaded('/adm/grades',$skipped);
7569: $r->print('
7570: <p>
7571: '.&mt('[_1]Original[_2] file as uploaded by the scantron office.',
7572: '<a href="'.$orig.'">','</a>').'
7573: </p>
7574: <p>
7575: '.&mt('[_1]Corrections[_2], a file of corrected records that were used in grading.',
7576: '<a href="'.$corrected.'">','</a>').'
7577: </p>
7578: <p>
7579: '.&mt('[_1]Skipped[_2], a file of records that were skipped.',
7580: '<a href="'.$skipped.'">','</a>').'
7581: </p>
7582: ');
7583: $r->print(&show_grading_menu_form(&get_symb($r,1)));
7584: return '';
7585: }
7586:
7587: =pod
7588:
7589: =back
7590:
7591: =cut
7592:
7593: #-------- end of section for handling grading scantron forms -------
7594: #
7595: #-------------------------------------------------------------------
7596:
7597: #-------------------------- Menu interface -------------------------
7598: #
7599: #--- Show a Grading Menu button - Calls the next routine ---
7600: sub show_grading_menu_form {
7601: my ($symb)=@_;
7602: my $result.='<br /><form action="/adm/grades" method="post">'."\n".
7603: '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
7604: '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
7605: '<input type="hidden" name="command" value="gradingmenu" />'."\n".
7606: '<input type="submit" name="submit" value="'.&mt('Grading Menu').'" />'."\n".
7607: '</form>'."\n";
7608: return $result;
7609: }
7610:
7611: # -- Retrieve choices for grading form
7612: sub savedState {
7613: my %savedState = ();
7614: if ($env{'form.saveState'}) {
7615: foreach (split(/:/,$env{'form.saveState'})) {
7616: my ($key,$value) = split(/=/,$_,2);
7617: $savedState{$key} = $value;
7618: }
7619: }
7620: return \%savedState;
7621: }
7622:
7623: sub grading_menu {
7624: my ($request) = @_;
7625: my ($symb)=&get_symb($request);
7626: if (!$symb) {return '';}
7627: my $probTitle = &Apache::lonnet::gettitle($symb);
7628: my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
7629:
7630: $request->print($table);
7631: my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb),
7632: 'handgrade'=>$hdgrade,
7633: 'probTitle'=>$probTitle,
7634: 'command'=>'submit_options',
7635: 'saveState'=>"",
7636: 'gradingMenu'=>1,
7637: 'showgrading'=>"yes");
7638: my $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
7639: my @menu = ({ url => $url,
7640: name => &mt('Manual Grading/View Submissions'),
7641: short_description =>
7642: &mt('Start the process of hand grading submissions.'),
7643: });
7644: $fields{'command'} = 'csvform';
7645: $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
7646: push (@menu, { url => $url,
7647: name => &mt('Upload Scores'),
7648: short_description =>
7649: &mt('Specify a file containing the class scores for current resource.')});
7650: $fields{'command'} = 'processclicker';
7651: $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
7652: push (@menu, { url => $url,
7653: name => &mt('Process Clicker'),
7654: short_description =>
7655: &mt('Specify a file containing the clicker information for this resource.')});
7656: $fields{'command'} = 'scantron_selectphase';
7657: $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
7658: push (@menu, { url => $url,
7659: name => &mt('Grade/Manage Scantron Forms'),
7660: short_description =>
7661: &mt('')});
7662: $fields{'command'} = 'verify';
7663: $url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
7664: push (@menu, { url => "",
7665: name => &mt('Verify Receipt'),
7666: short_description =>
7667: &mt('')});
7668: #
7669: # Create the menu
7670: my $Str;
7671: # $Str .= '<h2>'.&mt('Please select a grading task').'</h2>';
7672: $Str .= '<form method="post" action="" name="gradingMenu">';
7673: $Str .= '<input type="hidden" name="command" value="" />'.
7674: '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
7675: '<input type="hidden" name="handgrade" value="'.$hdgrade.'" />'."\n".
7676: '<input type="hidden" name="probTitle" value="'.$probTitle.'" />'."\n".
7677: '<input type="hidden" name="saveState" value="" />'."\n".
7678: '<input type="hidden" name="gradingMenu" value="1" />'."\n".
7679: '<input type="hidden" name="showgrading" value="yes" />'."\n";
7680:
7681: foreach my $menudata (@menu) {
7682: if ($menudata->{'name'} ne &mt('Verify Receipt')) {
7683: $Str .=' <h3><a '.
7684: $menudata->{'jscript'}.
7685: ' href="'.
7686: $menudata->{'url'}.'" >'.
7687: $menudata->{'name'}."</a></h3>\n";
7688: } else {
7689: $Str .=' <h3><input type="button" value="'.&mt('Verify Receipt').'" '.
7690: $menudata->{'jscript'}.
7691: ' onClick="javascript:checkChoice(document.forms.gradingMenu,\'5\',\'verify\')" '.
7692: ' /></h3>';
7693: $Str .= (' 'x8).
7694: &mt(' receipt: [_1]',
7695: &Apache::lonnet::recprefix($env{'request.course.id'}).
7696: '-<input type="text" name="receipt" size="4" onChange="javascript:checkReceiptNo(this.form,\'OK\')" />');
7697: }
7698: $Str .= ' '.(' 'x8).$menudata->{'short_description'}.
7699: "\n";
7700: }
7701: $Str .="</form>\n";
7702: $request->print(<<GRADINGMENUJS);
7703: <script type="text/javascript" language="javascript">
7704: function checkChoice(formname,val,cmdx) {
7705: if (val <= 2) {
7706: var cmd = radioSelection(formname.radioChoice);
7707: var cmdsave = cmd;
7708: } else {
7709: cmd = cmdx;
7710: cmdsave = 'submission';
7711: }
7712: formname.command.value = cmd;
7713: if (val < 5) formname.submit();
7714: if (val == 5) {
7715: if (!checkReceiptNo(formname,'notOK')) {
7716: return false;
7717: } else {
7718: formname.submit();
7719: }
7720: }
7721: }
7722:
7723: function checkReceiptNo(formname,nospace) {
7724: var receiptNo = formname.receipt.value;
7725: var checkOpt = false;
7726: if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
7727: if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
7728: if (checkOpt) {
7729: alert("Please enter a receipt number given by a student in the receipt box.");
7730: formname.receipt.value = "";
7731: formname.receipt.focus();
7732: return false;
7733: }
7734: return true;
7735: }
7736: </script>
7737: GRADINGMENUJS
7738: &commonJSfunctions($request);
7739: return $Str;
7740: }
7741:
7742:
7743: #--- Displays the submissions first page -------
7744: sub submit_options {
7745: my ($request) = @_;
7746: my ($symb)=&get_symb($request);
7747: if (!$symb) {return '';}
7748: my $probTitle = &Apache::lonnet::gettitle($symb);
7749:
7750: $request->print(<<GRADINGMENUJS);
7751: <script type="text/javascript" language="javascript">
7752: function checkChoice(formname,val,cmdx) {
7753: if (val <= 2) {
7754: var cmd = radioSelection(formname.radioChoice);
7755: var cmdsave = cmd;
7756: } else {
7757: cmd = cmdx;
7758: cmdsave = 'submission';
7759: }
7760: formname.command.value = cmd;
7761: formname.saveState.value = "saveCmd="+cmdsave+":saveSec="+pullDownSelection(formname.section)+
7762: ":saveSub="+pullDownSelection(formname.submitonly)+":saveStatus="+pullDownSelection(formname.Status);
7763: if (val < 5) formname.submit();
7764: if (val == 5) {
7765: if (!checkReceiptNo(formname,'notOK')) { return false;}
7766: formname.submit();
7767: }
7768: if (val < 7) formname.submit();
7769: }
7770:
7771: function checkReceiptNo(formname,nospace) {
7772: var receiptNo = formname.receipt.value;
7773: var checkOpt = false;
7774: if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
7775: if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
7776: if (checkOpt) {
7777: alert("Please enter a receipt number given by a student in the receipt box.");
7778: formname.receipt.value = "";
7779: formname.receipt.focus();
7780: return false;
7781: }
7782: return true;
7783: }
7784: </script>
7785: GRADINGMENUJS
7786: &commonJSfunctions($request);
7787: my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
7788: my $result;
7789: my (undef,$sections) = &getclasslist('all','0');
7790: my $savedState = &savedState();
7791: my $saveCmd = ($$savedState{'saveCmd'} eq '' ? 'submission' : $$savedState{'saveCmd'});
7792: my $saveSec = ($$savedState{'saveSec'} eq '' ? 'all' : $$savedState{'saveSec'});
7793: my $saveSub = ($$savedState{'saveSub'} eq '' ? 'all' : $$savedState{'saveSub'});
7794: my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'});
7795:
7796: $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
7797: '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
7798: '<input type="hidden" name="handgrade" value="'.$hdgrade.'" />'."\n".
7799: '<input type="hidden" name="probTitle" value="'.$probTitle.'" />'."\n".
7800: '<input type="hidden" name="command" value="" />'."\n".
7801: '<input type="hidden" name="saveState" value="" />'."\n".
7802: '<input type="hidden" name="gradingMenu" value="1" />'."\n".
7803: '<input type="hidden" name="showgrading" value="yes" />'."\n";
7804:
7805: $result.='
7806: <div class="LC_grade_select_mode">
7807: <div class="LC_grade_select_mode_current">
7808: <h2>
7809: '.&mt('Grade Current Resource').'
7810: </h2>
7811: <div class="LC_grade_select_mode_body">
7812: <div class="LC_grades_resource_info">
7813: '.$table.'
7814: </div>
7815: <div class="LC_grade_select_mode_selector">
7816: <div class="LC_grade_select_mode_selector_header">
7817: '.&mt('Sections').'
7818: </div>
7819: <div class="LC_grade_select_mode_selector_body">
7820: <select name="section" multiple="multiple" size="5">'."\n";
7821: if (ref($sections)) {
7822: foreach my $section (sort (@$sections)) {
7823: $result.='<option value="'.$section.'" '.
7824: ($saveSec eq $section ? 'selected="selected"':'').'>'.$section.'</option>'."\n";
7825: }
7826: }
7827: $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> ';
7828: $result.='
7829: </div>
7830: </div>
7831: <div class="LC_grade_select_mode_selector">
7832: <div class="LC_grade_select_mode_selector_header">
7833: '.&mt('Groups').'
7834: </div>
7835: <div class="LC_grade_select_mode_selector_body">
7836: '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'
7837: </div>
7838: </div>
7839: <div class="LC_grade_select_mode_selector">
7840: <div class="LC_grade_select_mode_selector_header">
7841: '.&mt('Access Status').'
7842: </div>
7843: <div class="LC_grade_select_mode_selector_body">
7844: '.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,5,undef,'mult').'
7845: </div>
7846: </div>
7847: <div class="LC_grade_select_mode_selector">
7848: <div class="LC_grade_select_mode_selector_header">
7849: '.&mt('Submission Status').'
7850: </div>
7851: <div class="LC_grade_select_mode_selector_body">
7852: <select name="submitonly" size="5">
7853: <option value="yes" '. ($saveSub eq 'yes' ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>
7854: <option value="queued" '. ($saveSub eq 'queued' ? 'selected="selected"' : '').'>'.&mt('in grading queue').'</option>
7855: <option value="graded" '. ($saveSub eq 'graded' ? 'selected="selected"' : '').'>'.&mt('with ungraded submissions').'</option>
7856: <option value="incorrect" '.($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>
7857: <option value="all" '. ($saveSub eq 'all' ? 'selected="selected"' : '').'>'.&mt('with any status').'</option>
7858: </select>
7859: </div>
7860: </div>
7861: <div class="LC_grade_select_mode_type_body">
7862: <div class="LC_grade_select_mode_type">
7863: <label>
7864: <input type="radio" name="radioChoice" value="submission" '.
7865: ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.
7866: &mt('Select individual students to grade and view submissions.').'
7867: </label>
7868: </div>
7869: <div class="LC_grade_select_mode_type">
7870: <label>
7871: <input type="radio" name="radioChoice" value="viewgrades" '.
7872: ($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '.
7873: &mt('Grade all selected students in a grading table.').'
7874: </label>
7875: </div>
7876: <div class="LC_grade_select_mode_type">
7877: <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next->').'" />
7878: </div>
7879: </div>
7880: </div>
7881: </div>
7882: <div class="LC_grade_select_mode_page">
7883: <h2>
7884: '.&mt('Grade Complete Folder for One Student').'
7885: </h2>
7886: <div class="LC_grades_select_mode_body">
7887: <div class="LC_grade_select_mode_type_body">
7888: <div class="LC_grade_select_mode_type">
7889: <label>
7890: <input type="radio" name="radioChoice" value="pickStudentPage" '.
7891: ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '.
7892: &mt('The <b>complete</b> page/sequence/folder: For one student').'
7893: </label>
7894: </div>
7895: <div class="LC_grade_select_mode_type">
7896: <input type="button" onClick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next->').'" />
7897: </div>
7898: </div>
7899: </div>
7900: </div>
7901: </div>
7902: </form>';
7903: $result .= &show_grading_menu_form($symb);
7904: return $result;
7905: }
7906:
7907: sub reset_perm {
7908: undef(%perm);
7909: }
7910:
7911: sub init_perm {
7912: &reset_perm();
7913: foreach my $test_perm ('vgr','mgr','opa') {
7914:
7915: my $scope = $env{'request.course.id'};
7916: if (!($perm{$test_perm}=&Apache::lonnet::allowed($test_perm,$scope))) {
7917:
7918: $scope .= '/'.$env{'request.course.sec'};
7919: if ( $perm{$test_perm}=
7920: &Apache::lonnet::allowed($test_perm,$scope)) {
7921: $perm{$test_perm.'_section'}=$env{'request.course.sec'};
7922: } else {
7923: delete($perm{$test_perm});
7924: }
7925: }
7926: }
7927: }
7928:
7929: sub gather_clicker_ids {
7930: my %clicker_ids;
7931:
7932: my $classlist = &Apache::loncoursedata::get_classlist();
7933:
7934: # Set up a couple variables.
7935: my $username_idx = &Apache::loncoursedata::CL_SNAME();
7936: my $domain_idx = &Apache::loncoursedata::CL_SDOM();
7937: my $status_idx = &Apache::loncoursedata::CL_STATUS();
7938:
7939: foreach my $student (keys(%$classlist)) {
7940: if ($classlist->{$student}->[$status_idx] ne 'Active') { next; }
7941: my $username = $classlist->{$student}->[$username_idx];
7942: my $domain = $classlist->{$student}->[$domain_idx];
7943: my $clickers =
7944: (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1];
7945: foreach my $id (split(/\,/,$clickers)) {
7946: $id=~s/^[\#0]+//;
7947: $id=~s/[\-\:]//g;
7948: if (exists($clicker_ids{$id})) {
7949: $clicker_ids{$id}.=','.$username.':'.$domain;
7950: } else {
7951: $clicker_ids{$id}=$username.':'.$domain;
7952: }
7953: }
7954: }
7955: return %clicker_ids;
7956: }
7957:
7958: sub gather_adv_clicker_ids {
7959: my %clicker_ids;
7960: my $cnum=$env{'course.'.$env{'request.course.id'}.'.num'};
7961: my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
7962: my %coursepersonnel=&Apache::lonnet::get_course_adv_roles($cdom.'/'.$cnum);
7963: foreach my $element (sort(keys(%coursepersonnel))) {
7964: foreach my $person (split(/\,/,$coursepersonnel{$element})) {
7965: my ($puname,$pudom)=split(/\:/,$person);
7966: my $clickers =
7967: (&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1];
7968: foreach my $id (split(/\,/,$clickers)) {
7969: $id=~s/^[\#0]+//;
7970: $id=~s/[\-\:]//g;
7971: if (exists($clicker_ids{$id})) {
7972: $clicker_ids{$id}.=','.$puname.':'.$pudom;
7973: } else {
7974: $clicker_ids{$id}=$puname.':'.$pudom;
7975: }
7976: }
7977: }
7978: }
7979: return %clicker_ids;
7980: }
7981:
7982: sub clicker_grading_parameters {
7983: return ('gradingmechanism' => 'scalar',
7984: 'upfiletype' => 'scalar',
7985: 'specificid' => 'scalar',
7986: 'pcorrect' => 'scalar',
7987: 'pincorrect' => 'scalar');
7988: }
7989:
7990: sub process_clicker {
7991: my ($r)=@_;
7992: my ($symb)=&get_symb($r);
7993: if (!$symb) {return '';}
7994: my $result=&checkforfile_js();
7995: $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
7996: my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});
7997: $result.=$table;
7998: $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";
7999: $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";
8000: $result.=' <b>'.&mt('Specify a file containing the clicker information for this resource').
8001: '.</b></td></tr>'."\n";
8002: $result.='<tr bgcolor=#ffffe6><td>'."\n";
8003: # Attempt to restore parameters from last session, set defaults if not present
8004: my %Saveable_Parameters=&clicker_grading_parameters();
8005: &Apache::loncommon::restore_course_settings('grades_clicker',
8006: \%Saveable_Parameters);
8007: if (!$env{'form.pcorrect'}) { $env{'form.pcorrect'}=100; }
8008: if (!$env{'form.pincorrect'}) { $env{'form.pincorrect'}=100; }
8009: if (!$env{'form.gradingmechanism'}) { $env{'form.gradingmechanism'}='attendance'; }
8010: if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; }
8011:
8012: my %checked;
8013: foreach my $gradingmechanism ('attendance','personnel','specific') {
8014: if ($env{'form.gradingmechanism'} eq $gradingmechanism) {
8015: $checked{$gradingmechanism}="checked='checked'";
8016: }
8017: }
8018:
8019: my $upload=&mt("Upload File");
8020: my $type=&mt("Type");
8021: my $attendance=&mt("Award points just for participation");
8022: my $personnel=&mt("Correctness determined from response by course personnel");
8023: my $specific=&mt("Correctness determined from response with clicker ID(s)");
8024: my $pcorrect=&mt("Percentage points for correct solution");
8025: my $pincorrect=&mt("Percentage points for incorrect solution");
8026: my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',
8027: ('iclicker' => 'i>clicker',
8028: 'interwrite' => 'interwrite PRS'));
8029: $symb = &Apache::lonenc::check_encrypt($symb);
8030: $result.=<<ENDUPFORM;
8031: <script type="text/javascript">
8032: function sanitycheck() {
8033: // Accept only integer percentages
8034: document.forms.gradesupload.pcorrect.value=Math.round(document.forms.gradesupload.pcorrect.value);
8035: document.forms.gradesupload.pincorrect.value=Math.round(document.forms.gradesupload.pincorrect.value);
8036: // Find out grading choice
8037: for (i=0; i<document.forms.gradesupload.gradingmechanism.length; i++) {
8038: if (document.forms.gradesupload.gradingmechanism[i].checked) {
8039: gradingchoice=document.forms.gradesupload.gradingmechanism[i].value;
8040: }
8041: }
8042: // By default, new choice equals user selection
8043: newgradingchoice=gradingchoice;
8044: // Not good to give more points for false answers than correct ones
8045: if (Math.round(document.forms.gradesupload.pcorrect.value)<Math.round(document.forms.gradesupload.pincorrect.value)) {
8046: document.forms.gradesupload.pcorrect.value=document.forms.gradesupload.pincorrect.value;
8047: }
8048: // If new choice is attendance only, and old choice was correctness-based, restore defaults
8049: if ((gradingchoice=='attendance') && (document.forms.gradesupload.waschecked.value!='attendance')) {
8050: document.forms.gradesupload.pcorrect.value=100;
8051: document.forms.gradesupload.pincorrect.value=100;
8052: }
8053: // If the values are different, cannot be attendance only
8054: if ((Math.round(document.forms.gradesupload.pcorrect.value)!=Math.round(document.forms.gradesupload.pincorrect.value)) &&
8055: (gradingchoice=='attendance')) {
8056: newgradingchoice='personnel';
8057: }
8058: // Change grading choice to new one
8059: for (i=0; i<document.forms.gradesupload.gradingmechanism.length; i++) {
8060: if (document.forms.gradesupload.gradingmechanism[i].value==newgradingchoice) {
8061: document.forms.gradesupload.gradingmechanism[i].checked=true;
8062: } else {
8063: document.forms.gradesupload.gradingmechanism[i].checked=false;
8064: }
8065: }
8066: // Remember the old state
8067: document.forms.gradesupload.waschecked.value=newgradingchoice;
8068: }
8069: </script>
8070: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
8071: <input type="hidden" name="symb" value="$symb" />
8072: <input type="hidden" name="command" value="processclickerfile" />
8073: <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
8074: <input type="hidden" name="saveState" value="$env{'form.saveState'}" />
8075: <input type="file" name="upfile" size="50" />
8076: <br /><label>$type: $selectform</label>
8077: <br /><label><input type="radio" name="gradingmechanism" value="attendance" $checked{'attendance'} onClick="sanitycheck()" />$attendance </label>
8078: <br /><label><input type="radio" name="gradingmechanism" value="personnel" $checked{'personnel'} onClick="sanitycheck()" />$personnel</label>
8079: <br /><label><input type="radio" name="gradingmechanism" value="specific" $checked{'specific'} onClick="sanitycheck()" />$specific </label>
8080: <input type="text" name="specificid" value="$env{'form.specificid'}" size="20" />
8081: <input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" />
8082: <br /><label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onChange="sanitycheck()" /></label>
8083: <br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onChange="sanitycheck()" /></label>
8084: <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="$upload" />
8085: </form>
8086: ENDUPFORM
8087: $result.='</td></tr></table>'."\n".
8088: '</td></tr></table><br /><br />'."\n";
8089: $result.=&show_grading_menu_form($symb);
8090: return $result;
8091: }
8092:
8093: sub process_clicker_file {
8094: my ($r)=@_;
8095: my ($symb)=&get_symb($r);
8096: if (!$symb) {return '';}
8097:
8098: my %Saveable_Parameters=&clicker_grading_parameters();
8099: &Apache::loncommon::store_course_settings('grades_clicker',
8100: \%Saveable_Parameters);
8101:
8102: my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
8103: if (($env{'form.gradingmechanism'} eq 'specific') && ($env{'form.specificid'}!~/\w/)) {
8104: $result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>';
8105: return $result.&show_grading_menu_form($symb);
8106: }
8107: my %clicker_ids=&gather_clicker_ids();
8108: my %correct_ids;
8109: if ($env{'form.gradingmechanism'} eq 'personnel') {
8110: %correct_ids=&gather_adv_clicker_ids();
8111: }
8112: if ($env{'form.gradingmechanism'} eq 'specific') {
8113: foreach my $correct_id (split(/[\s\,]/,$env{'form.specificid'})) {;
8114: $correct_id=~tr/a-z/A-Z/;
8115: $correct_id=~s/\s//gs;
8116: $correct_id=~s/^[\#0]+//;
8117: $correct_id=~s/[\-\:]//g;
8118: if ($correct_id) {
8119: $correct_ids{$correct_id}='specified';
8120: }
8121: }
8122: }
8123: if ($env{'form.gradingmechanism'} eq 'attendance') {
8124: $result.=&mt('Score based on attendance only');
8125: } else {
8126: my $number=0;
8127: $result.='<p><b>'.&mt('Correctness determined by the following IDs').'</b>';
8128: foreach my $id (sort(keys(%correct_ids))) {
8129: $result.='<br /><tt>'.$id.'</tt> - ';
8130: if ($correct_ids{$id} eq 'specified') {
8131: $result.=&mt('specified');
8132: } else {
8133: my ($uname,$udom)=split(/\:/,$correct_ids{$id});
8134: $result.=&Apache::loncommon::plainname($uname,$udom);
8135: }
8136: $number++;
8137: }
8138: $result.="</p>\n";
8139: if ($number==0) {
8140: $result.='<span class="LC_error">'.&mt('No IDs found to determine correct answer').'</span>';
8141: return $result.&show_grading_menu_form($symb);
8142: }
8143: }
8144: if (length($env{'form.upfile'}) < 2) {
8145: $result.=&mt('[_1] Error: [_2] The file you attempted to upload, [_3] contained no information. Please check that you entered the correct filename.',
8146: '<span class="LC_error">',
8147: '</span>',
8148: '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>');
8149: return $result.&show_grading_menu_form($symb);
8150: }
8151:
8152: # Were able to get all the info needed, now analyze the file
8153:
8154: $result.=&Apache::loncommon::studentbrowser_javascript();
8155: $symb = &Apache::lonenc::check_encrypt($symb);
8156: my $heading=&mt('Scanning clicker file');
8157: $result.=(<<ENDHEADER);
8158: <br /><table width="100%" border="0"><tr><td bgcolor="#777777">
8159: <table width="100%" border="0"><tr bgcolor="#e6ffff"><td>
8160: <b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>
8161: <form method="post" action="/adm/grades" name="clickeranalysis">
8162: <input type="hidden" name="symb" value="$symb" />
8163: <input type="hidden" name="command" value="assignclickergrades" />
8164: <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
8165: <input type="hidden" name="saveState" value="$env{'form.saveState'}" />
8166: <input type="hidden" name="gradingmechanism" value="$env{'form.gradingmechanism'}" />
8167: <input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" />
8168: <input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" />
8169: ENDHEADER
8170: my %responses;
8171: my @questiontitles;
8172: my $errormsg='';
8173: my $number=0;
8174: if ($env{'form.upfiletype'} eq 'iclicker') {
8175: ($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses);
8176: }
8177: if ($env{'form.upfiletype'} eq 'interwrite') {
8178: ($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses);
8179: }
8180: $result.='<br />'.&mt('Found [_1] question(s)',$number).'<br />'.
8181: '<input type="hidden" name="number" value="'.$number.'" />'.
8182: &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',
8183: $env{'form.pcorrect'},$env{'form.pincorrect'}).
8184: '<br />';
8185: # Remember Question Titles
8186: # FIXME: Possibly need delimiter other than ":"
8187: for (my $i=0;$i<$number;$i++) {
8188: $result.='<input type="hidden" name="question:'.$i.'" value="'.
8189: &HTML::Entities::encode($questiontitles[$i],'"&<>').'" />';
8190: }
8191: my $correct_count=0;
8192: my $student_count=0;
8193: my $unknown_count=0;
8194: # Match answers with usernames
8195: # FIXME: Possibly need delimiter other than ":"
8196: foreach my $id (keys(%responses)) {
8197: if ($correct_ids{$id}) {
8198: $result.="\n".'<input type="hidden" name="correct:'.$correct_count.':'.$correct_ids{$id}.'" value="'.$responses{$id}.'" />';
8199: $correct_count++;
8200: } elsif ($clicker_ids{$id}) {
8201: if ($clicker_ids{$id}=~/\,/) {
8202: # More than one user with the same clicker!
8203: $result.="\n<hr />".&mt('Clicker registered more than once').": <tt>".$id."</tt><br />";
8204: $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
8205: "<select name='multi".$id."'>";
8206: foreach my $reguser (sort(split(/\,/,$clicker_ids{$id}))) {
8207: $result.="<option value='".$reguser."'>".&Apache::loncommon::plainname(split(/\:/,$reguser)).' ('.$reguser.')</option>';
8208: }
8209: $result.='</select>';
8210: $unknown_count++;
8211: } else {
8212: # Good: found one and only one user with the right clicker
8213: $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />';
8214: $student_count++;
8215: }
8216: } else {
8217: $result.="\n<hr />".&mt('Unregistered Clicker')." <tt>".$id."</tt><br />";
8218: $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
8219: "\n".&mt("Username").": <input type='text' name='uname".$id."' /> ".
8220: "\n".&mt("Domain").": ".
8221: &Apache::loncommon::select_dom_form($env{'course.'.$env{'request.course.id'}.'.domain'},'udom'.$id).' '.
8222: &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id);
8223: $unknown_count++;
8224: }
8225: }
8226: $result.='<hr />'.
8227: &mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);
8228: if ($env{'form.gradingmechanism'} ne 'attendance') {
8229: if ($correct_count==0) {
8230: $errormsg.="Found no correct answers answers for grading!";
8231: } elsif ($correct_count>1) {
8232: $result.='<br /><span class="LC_warning">'.&mt("Found [_1] entries for grading!",$correct_count).'</span>';
8233: }
8234: }
8235: if ($number<1) {
8236: $errormsg.="Found no questions.";
8237: }
8238: if ($errormsg) {
8239: $result.='<br /><span class="LC_error">'.&mt($errormsg).'</span>';
8240: } else {
8241: $result.='<br /><input type="submit" name="finalize" value="'.&mt('Finalize Grading').'" />';
8242: }
8243: $result.='</form></td></tr></table>'."\n".
8244: '</td></tr></table><br /><br />'."\n";
8245: return $result.&show_grading_menu_form($symb);
8246: }
8247:
8248: sub iclicker_eval {
8249: my ($questiontitles,$responses)=@_;
8250: my $number=0;
8251: my $errormsg='';
8252: foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
8253: my %components=&Apache::loncommon::record_sep($line);
8254: my @entries=map {$components{$_}} (sort(keys(%components)));
8255: if ($entries[0] eq 'Question') {
8256: for (my $i=3;$i<$#entries;$i+=6) {
8257: $$questiontitles[$number]=$entries[$i];
8258: $number++;
8259: }
8260: }
8261: if ($entries[0]=~/^\#/) {
8262: my $id=$entries[0];
8263: my @idresponses;
8264: $id=~s/^[\#0]+//;
8265: for (my $i=0;$i<$number;$i++) {
8266: my $idx=3+$i*6;
8267: push(@idresponses,$entries[$idx]);
8268: }
8269: $$responses{$id}=join(',',@idresponses);
8270: }
8271: }
8272: return ($errormsg,$number);
8273: }
8274:
8275: sub interwrite_eval {
8276: my ($questiontitles,$responses)=@_;
8277: my $number=0;
8278: my $errormsg='';
8279: my $skipline=1;
8280: my $questionnumber=0;
8281: my %idresponses=();
8282: foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
8283: my %components=&Apache::loncommon::record_sep($line);
8284: my @entries=map {$components{$_}} (sort(keys(%components)));
8285: if ($entries[1] eq 'Time') { $skipline=0; next; }
8286: if ($entries[1] eq 'Response') { $skipline=1; }
8287: next if $skipline;
8288: if ($entries[0]!=$questionnumber) {
8289: $questionnumber=$entries[0];
8290: $$questiontitles[$number]=&mt('Question [_1]',$questionnumber);
8291: $number++;
8292: }
8293: my $id=$entries[4];
8294: $id=~s/^[\#0]+//;
8295: $id=~s/^v\d*\://i;
8296: $id=~s/[\-\:]//g;
8297: $idresponses{$id}[$number]=$entries[6];
8298: }
8299: foreach my $id (keys %idresponses) {
8300: $$responses{$id}=join(',',@{$idresponses{$id}});
8301: $$responses{$id}=~s/^\s*\,//;
8302: }
8303: return ($errormsg,$number);
8304: }
8305:
8306: sub assign_clicker_grades {
8307: my ($r)=@_;
8308: my ($symb)=&get_symb($r);
8309: if (!$symb) {return '';}
8310: # See which part we are saving to
8311: my ($partlist,$handgrade,$responseType) = &response_type($symb);
8312: # FIXME: This should probably look for the first handgradeable part
8313: my $part=$$partlist[0];
8314: # Start screen output
8315: my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
8316:
8317: my $heading=&mt('Assigning grades based on clicker file');
8318: $result.=(<<ENDHEADER);
8319: <br /><table width="100%" border="0"><tr><td bgcolor="#777777">
8320: <table width="100%" border="0"><tr bgcolor="#e6ffff"><td>
8321: <b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>
8322: ENDHEADER
8323: # Get correct result
8324: # FIXME: Possibly need delimiter other than ":"
8325: my @correct=();
8326: my $gradingmechanism=$env{'form.gradingmechanism'};
8327: my $number=$env{'form.number'};
8328: if ($gradingmechanism ne 'attendance') {
8329: foreach my $key (keys(%env)) {
8330: if ($key=~/^form\.correct\:/) {
8331: my @input=split(/\,/,$env{$key});
8332: for (my $i=0;$i<=$#input;$i++) {
8333: if (($correct[$i]) && ($input[$i]) &&
8334: ($correct[$i] ne $input[$i])) {
8335: $result.='<br /><span class="LC_warning">'.
8336: &mt('More than one correct result given for question "[_1]": [_2] versus [_3].',
8337: $env{'form.question:'.$i},$correct[$i],$input[$i]).'</span>';
8338: } elsif ($input[$i]) {
8339: $correct[$i]=$input[$i];
8340: }
8341: }
8342: }
8343: }
8344: for (my $i=0;$i<$number;$i++) {
8345: if (!$correct[$i]) {
8346: $result.='<br /><span class="LC_error">'.
8347: &mt('No correct result given for question "[_1]"!',
8348: $env{'form.question:'.$i}).'</span>';
8349: }
8350: }
8351: $result.='<br />'.&mt("Correct answer: [_1]",join(', ',map { ($_?$_:'-') } @correct));
8352: }
8353: # Start grading
8354: my $pcorrect=$env{'form.pcorrect'};
8355: my $pincorrect=$env{'form.pincorrect'};
8356: my $storecount=0;
8357: foreach my $key (keys(%env)) {
8358: my $user='';
8359: if ($key=~/^form\.student\:(.*)$/) {
8360: $user=$1;
8361: }
8362: if ($key=~/^form\.unknown\:(.*)$/) {
8363: my $id=$1;
8364: if (($env{'form.uname'.$id}) && ($env{'form.udom'.$id})) {
8365: $user=$env{'form.uname'.$id}.':'.$env{'form.udom'.$id};
8366: } elsif ($env{'form.multi'.$id}) {
8367: $user=$env{'form.multi'.$id};
8368: }
8369: }
8370: if ($user) {
8371: my @answer=split(/\,/,$env{$key});
8372: my $sum=0;
8373: for (my $i=0;$i<$number;$i++) {
8374: if ($answer[$i]) {
8375: if ($gradingmechanism eq 'attendance') {
8376: $sum+=$pcorrect;
8377: } else {
8378: if ($answer[$i] eq $correct[$i]) {
8379: $sum+=$pcorrect;
8380: } else {
8381: $sum+=$pincorrect;
8382: }
8383: }
8384: }
8385: }
8386: my $ave=$sum/(100*$number);
8387: # Store
8388: my ($username,$domain)=split(/\:/,$user);
8389: my %grades=();
8390: $grades{"resource.$part.solved"}='correct_by_override';
8391: $grades{"resource.$part.awarded"}=$ave;
8392: $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
8393: my $returncode=&Apache::lonnet::cstore(\%grades,$symb,
8394: $env{'request.course.id'},
8395: $domain,$username);
8396: if ($returncode ne 'ok') {
8397: $result.="<br /><span class=\"LC_error\">Failed to save student $username:$domain. Message when trying to save was ($returncode)</span>";
8398: } else {
8399: $storecount++;
8400: }
8401: }
8402: }
8403: # We are done
8404: $result.='<br />'.&mt('Successfully stored grades for [_1] student(s).',$storecount).
8405: '</td></tr></table>'."\n".
8406: '</td></tr></table><br /><br />'."\n";
8407: return $result.&show_grading_menu_form($symb);
8408: }
8409:
8410: sub handler {
8411: my $request=$_[0];
8412: &reset_caches();
8413: if ($env{'browser.mathml'}) {
8414: &Apache::loncommon::content_type($request,'text/xml');
8415: } else {
8416: &Apache::loncommon::content_type($request,'text/html');
8417: }
8418: $request->send_http_header;
8419: return '' if $request->header_only;
8420: &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});
8421: my $symb=&get_symb($request,1);
8422: my @commands=&Apache::loncommon::get_env_multiple('form.command');
8423: my $command=$commands[0];
8424:
8425: if ($#commands > 0) {
8426: &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
8427: }
8428:
8429:
8430: $request->print(&Apache::loncommon::start_page('Grading'));
8431: if ($symb eq '' && $command eq '') {
8432: if ($env{'user.adv'}) {
8433: if (($env{'form.codeone'}) && ($env{'form.codetwo'}) &&
8434: ($env{'form.codethree'})) {
8435: my $token=$env{'form.codeone'}.'*'.$env{'form.codetwo'}.'*'.
8436: $env{'form.codethree'};
8437: my ($tsymb,$tuname,$tudom,$tcrsid)=
8438: &Apache::lonnet::checkin($token);
8439: if ($tsymb) {
8440: my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb);
8441: if (&Apache::lonnet::allowed('mgr',$tcrsid)) {
8442: $request->print(&Apache::lonnet::ssi_body('/res/'.$url,
8443: ('grade_username' => $tuname,
8444: 'grade_domain' => $tudom,
8445: 'grade_courseid' => $tcrsid,
8446: 'grade_symb' => $tsymb)));
8447: } else {
8448: $request->print('<h3>Not authorized: '.$token.'</h3>');
8449: }
8450: } else {
8451: $request->print('<h3>Not a valid DocID: '.$token.'</h3>');
8452: }
8453: } else {
8454: $request->print(&Apache::lonxml::tokeninputfield());
8455: }
8456: }
8457: } else {
8458: &init_perm();
8459: if ($command eq 'submission' && $perm{'vgr'}) {
8460: ($env{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0));
8461: } elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) {
8462: &pickStudentPage($request);
8463: } elsif ($command eq 'displayPage' && $perm{'vgr'}) {
8464: &displayPage($request);
8465: } elsif ($command eq 'gradeByPage' && $perm{'mgr'}) {
8466: &updateGradeByPage($request);
8467: } elsif ($command eq 'processGroup' && $perm{'vgr'}) {
8468: &processGroup($request);
8469: } elsif ($command eq 'gradingmenu' && $perm{'vgr'}) {
8470: $request->print(&grading_menu($request));
8471: } elsif ($command eq 'submit_options' && $perm{'vgr'}) {
8472: $request->print(&submit_options($request));
8473: } elsif ($command eq 'viewgrades' && $perm{'vgr'}) {
8474: $request->print(&viewgrades($request));
8475: } elsif ($command eq 'handgrade' && $perm{'mgr'}) {
8476: $request->print(&processHandGrade($request));
8477: } elsif ($command eq 'editgrades' && $perm{'mgr'}) {
8478: $request->print(&editgrades($request));
8479: } elsif ($command eq 'verify' && $perm{'vgr'}) {
8480: $request->print(&verifyreceipt($request));
8481: } elsif ($command eq 'processclicker' && $perm{'mgr'}) {
8482: $request->print(&process_clicker($request));
8483: } elsif ($command eq 'processclickerfile' && $perm{'mgr'}) {
8484: $request->print(&process_clicker_file($request));
8485: } elsif ($command eq 'assignclickergrades' && $perm{'mgr'}) {
8486: $request->print(&assign_clicker_grades($request));
8487: } elsif ($command eq 'csvform' && $perm{'mgr'}) {
8488: $request->print(&upcsvScores_form($request));
8489: } elsif ($command eq 'csvupload' && $perm{'mgr'}) {
8490: $request->print(&csvupload($request));
8491: } elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) {
8492: $request->print(&csvuploadmap($request));
8493: } elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) {
8494: if ($env{'form.associate'} ne 'Reverse Association') {
8495: $request->print(&csvuploadoptions($request));
8496: } else {
8497: if ( $env{'form.upfile_associate'} ne 'reverse' ) {
8498: $env{'form.upfile_associate'} = 'reverse';
8499: } else {
8500: $env{'form.upfile_associate'} = 'forward';
8501: }
8502: $request->print(&csvuploadmap($request));
8503: }
8504: } elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {
8505: $request->print(&csvuploadassign($request));
8506: } elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
8507: $request->print(&scantron_selectphase($request));
8508: } elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {
8509: $request->print(&scantron_do_warning($request));
8510: } elsif ($command eq 'scantron_validate' && $perm{'mgr'}) {
8511: $request->print(&scantron_validate_file($request));
8512: } elsif ($command eq 'scantron_process' && $perm{'mgr'}) {
8513: $request->print(&scantron_process_students($request));
8514: } elsif ($command eq 'scantronupload' &&
8515: (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
8516: &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
8517: $request->print(&scantron_upload_scantron_data($request));
8518: } elsif ($command eq 'scantronupload_save' &&
8519: (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
8520: &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
8521: $request->print(&scantron_upload_scantron_data_save($request));
8522: } elsif ($command eq 'scantron_download' &&
8523: &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
8524: $request->print(&scantron_download_scantron_data($request));
8525: } elsif ($command) {
8526: $request->print("Access Denied ($command)");
8527: }
8528: }
8529: $request->print(&Apache::loncommon::end_page());
8530: &reset_caches();
8531: return '';
8532: }
8533:
8534: 1;
8535:
8536: __END__;
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>