1: # The LearningOnline Network with CAPA
2: #
3: # $Id: lonstudentsubmissions.pm,v 1.16 2004/09/02 21:02:54 matthew Exp $
4: #
5: # Copyright Michigan State University Board of Trustees
6: #
7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
8: #
9: # LON-CAPA is free software; you can redistribute it and/or modify
10: # it under the terms of the GNU General Public License as published by
11: # the Free Software Foundation; either version 2 of the License, or
12: # (at your option) any later version.
13: #
14: # LON-CAPA is distributed in the hope that it will be useful,
15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17: # GNU General Public License for more details.
18: #
19: # You should have received a copy of the GNU General Public License
20: # along with LON-CAPA; if not, write to the Free Software
21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22: #
23: # /home/httpd/html/adm/gpl.txt
24: #
25: # http://www.lon-capa.org/
26: #
27: package Apache::lonstudentsubmissions;
28:
29: use strict;
30: use Apache::lonnet();
31: use Apache::loncommon();
32: use Apache::lonhtmlcommon();
33: use Apache::loncoursedata();
34: use Apache::lonstatistics;
35: use Apache::lonlocal;
36: use Apache::lonstathelpers;
37: use HTML::Entities();
38: use Time::Local();
39: use Spreadsheet::WriteExcel();
40:
41: my @SubmitButtons = ({ name => 'PrevProblem',
42: text => 'Previous Problem' },
43: { name => 'NextProblem',
44: text => 'Next Problem' },
45: { name => 'break'},
46: { name => 'SelectAnother',
47: text => 'Choose a different Problem' },
48: { name => 'Generate',
49: text => 'Generate Report'},
50: );
51:
52: sub BuildStudentSubmissionsPage {
53: my ($r,$c)=@_;
54: #
55: my %Saveable_Parameters = ('Status' => 'scalar',
56: 'Section' => 'array',
57: 'NumPlots' => 'scalar',
58: );
59: &Apache::loncommon::store_course_settings('student_submissions',
60: \%Saveable_Parameters);
61: &Apache::loncommon::restore_course_settings('student_submissions',
62: \%Saveable_Parameters);
63: #
64: &Apache::lonstatistics::PrepareClasslist();
65: #
66: $r->print(&CreateInterface());
67: #
68: my @Students = @Apache::lonstatistics::Students;
69: #
70: if (@Students < 1) {
71: $r->print('<h2>There are no students in the sections selected</h2>');
72: }
73: #
74: my @CacheButtonHTML =
75: &Apache::lonstathelpers::manage_caches($r,'Statistics','stats_status');
76: $r->rflush();
77: #
78: if (exists($ENV{'form.problemchoice'}) &&
79: ! exists($ENV{'form.SelectAnother'})) {
80: foreach my $button (@SubmitButtons) {
81: if ($button->{'name'} eq 'break') {
82: $r->print("<br />\n");
83: } else {
84: $r->print('<input type="submit" name="'.$button->{'name'}.'" '.
85: 'value="'.&mt($button->{'text'}).'" />');
86: $r->print(' 'x5);
87: }
88: }
89: foreach my $html (@CacheButtonHTML) {
90: $r->print($html.(' 'x5));
91: }
92: #
93: $r->print('<hr />');
94: $r->rflush();
95: #
96: # Determine which problem we are to analyze
97: my $current_problem = &Apache::lonstathelpers::get_target_from_id
98: ($ENV{'form.problemchoice'});
99: #
100: my ($prev,$curr,$next) =
101: &Apache::lonstathelpers::get_prev_curr_next($current_problem,
102: '.',
103: 'response',
104: );
105: if (exists($ENV{'form.PrevProblem'}) && defined($prev)) {
106: $current_problem = $prev;
107: } elsif (exists($ENV{'form.NextProblem'}) && defined($next)) {
108: $current_problem = $next;
109: } else {
110: $current_problem = $curr;
111: }
112: #
113: # Store the current problem choice and send it out in the form
114: $ENV{'form.problemchoice'} =
115: &Apache::lonstathelpers::make_target_id($current_problem);
116: $r->print('<input type="hidden" name="problemchoice" value="'.
117: $ENV{'form.problemchoice'}.'" />');
118: #
119: if (! defined($current_problem->{'resource'})) {
120: $r->print('resource is undefined');
121: } else {
122: my $resource = $current_problem->{'resource'};
123: $r->print('<h1>'.$resource->{'title'}.'</h1>');
124: $r->print('<h3>'.$resource->{'src'}.'</h3>');
125: if ($ENV{'form.renderprob'} eq 'true') {
126: $r->print(&Apache::lonstathelpers::render_resource($resource));
127: }
128: $r->rflush();
129: my %Data = &Apache::lonstathelpers::get_problem_data
130: ($resource->{'src'});
131: my $ProblemData = $Data{$current_problem->{'part'}.
132: '.'.
133: $current_problem->{'respid'}};
134: if ($ENV{'form.output'} eq 'excel') {
135: &prepare_excel_output($r,$current_problem,
136: $ProblemData,\@Students);
137: } else {
138: &prepare_html_output($r,$current_problem,
139: $ProblemData,\@Students);
140: }
141: }
142: $r->print('<hr />');
143: } else {
144: $r->print('<input type="submit" name="Generate" value="'.
145: &mt('Generate Spreadsheet').'" />');
146: $r->print(' 'x5);
147: $r->print('<h3>'.&mt('Please select a problem to analyze').'</h3>');
148: $r->print(&Apache::lonstathelpers::ProblemSelector('.'));
149: }
150: }
151:
152: #########################################################
153: #########################################################
154:
155: my @DefaultColumns =
156: (
157: {name=>'username',
158: display=>'Student'},
159: {name=>'domain',
160: display=>'Domain'},
161: {name => 'id',
162: display => 'Id'},
163: {name => 'time',
164: display =>'Time'},
165: {name => 'attempt',
166: display =>'Attempt'},
167: {name => 'awarddetail',
168: display =>'Awarddetail'},
169: {name => 'grading',
170: display =>'Score'},
171: );
172:
173: #########################################################
174: #########################################################
175: ##
176: ## prepare_html_output
177: ##
178: #########################################################
179: #########################################################
180: sub prepare_html_output {
181: my ($r,$problem,$ProblemData,$Students) = @_;
182: my $c = $r->connection();
183: my ($resource,$respid,$partid) = ($problem->{'resource'},
184: $problem->{'respid'},
185: $problem->{'part'});
186: #
187: if ($ENV{'form.correctans'} eq 'true') {
188: $r->print('<h2>'.&mt('Generating Correct Answers').'</h2>');
189: &Apache::lonstathelpers::GetStudentAnswers($r,$problem,$Students,
190: 'Statistics',
191: 'stats_status');
192: }
193: #
194: $r->print('<h2>'.&mt('Student Responses').'</h2>');
195: #
196: $r->rflush();
197: my $response_type;
198: for (my $i=0;
199: $i<scalar(@{$resource->{'partdata'}->{$partid}->{'ResponseIds'}});
200: $i++) {
201: if($resource->{'partdata'}->{$partid}->{'ResponseIds'}->[$i] eq $respid){
202: $response_type =
203: $resource->{'partdata'}->{$partid}->{'ResponseTypes'}->[$i];
204: last;
205: }
206: }
207: if (! defined($response_type)) {
208: $r->print('<h2>'.&mt('Unable to determine response type').'</h2>');
209: } else {
210: my $count = 0;
211: my @Columns;
212: if (exists($ENV{'form.concise'}) && $ENV{'form.concise'} eq 'true') {
213: foreach (@DefaultColumns) {
214: if ($_->{'name'} =~ /^(username|domain|id)$/){
215: push(@Columns,$_);
216: }
217: }
218: } else {
219: @Columns = @DefaultColumns;
220: }
221: my $header = '<table>'.$/.&html_headers(\@Columns);
222: if ($response_type eq 'essay') {
223: $header .= &html_essay_headers();
224: } elsif ($response_type eq 'option') {
225: $header .= &html_option_headers();
226: } else {
227: $header .= &html_generic_headers();
228: }
229: $header = '<tr>'.$header.'</tr>';
230: #
231: $r->print($/.$header.$/);
232: foreach my $student (@$Students) {
233: if ($count >= 50) {
234: $r->print('</table>'.$/.$header.$/);
235: $count = 0;
236: }
237: last if ($c->aborted());
238: my $results = &Apache::loncoursedata::get_response_data_by_student
239: ($student,$resource->{'symb'},$respid);
240: next if (! defined($results) || ref($results) ne 'ARRAY');
241: for (my $i=0;$i<scalar(@$results);$i++) {
242: my $response = $results->[$i];
243: if ($ENV{'form.last_sub_only'} eq 'true' &&
244: $i < (scalar(@$results)-1)) {
245: next;
246: }
247: my $data;
248: $data->{'username'} = $student->{'username'};
249: $data->{'domain'} = $student->{'domain'};
250: $data->{'id'} = $student->{'id'};
251: $data->{'fullname'} = $student->{'fullanem'};
252: $data->{'status'} = $student->{'status'};
253: $data->{'time'} = &Apache::lonlocal::locallocaltime
254: ($response->[&Apache::loncoursedata::RDs_timestamp()]);
255: $data->{'attempt'} =
256: $response->[&Apache::loncoursedata::RDs_tries()];
257: $data->{'awarded'} =
258: $response->[&Apache::loncoursedata::RDs_awarded()];
259: $data->{'awarddetail'} =
260: $response->[&Apache::loncoursedata::RDs_awarddetail()];
261: my $rowextra = 'bgcolor="#CCCCCC"';
262: if ($count % 2 == 1) {
263: $rowextra = 'bgcolor="#EEEEEE"';
264: }
265: my $row = '<tr '.$rowextra.'>';
266: foreach my $col (@Columns) {
267: $row .= '<td valign="top">'.
268: $data->{$col->{'name'}}.'</td>';
269: }
270: if ($response_type eq 'essay') {
271: $row .= &html_essay
272: ($response->[&Apache::loncoursedata::RDs_submission()],
273: $student->{'answer'},
274: scalar(@Columns),$rowextra);
275: } elsif ($response_type eq 'option') {
276: $row .= &html_option
277: ($response->[&Apache::loncoursedata::RDs_submission()],
278: $student->{'answer'},
279: scalar(@Columns),$rowextra);
280: } else {
281: $row .= &html_generic
282: ($response->[&Apache::loncoursedata::RDs_submission()],
283: $student->{'answer'},
284: scalar(@Columns),$rowextra);
285: }
286: $row .= '</tr>';
287: $r->print($row.$/);
288: $count++;
289: }
290: }
291: $r->print('</table>'.$/);
292: }
293: return;
294: }
295:
296: #####################################################
297: ##
298: ## HTML helper routines
299: ##
300: #####################################################
301: sub html_headers {
302: my ($Columns) = @_;
303: my $Str;
304: foreach my $column (@$Columns) {
305: $Str .= '<th align="left">'.$column->{'display'}.'</th>';
306: }
307: return $Str;
308: }
309:
310: sub html_essay {
311: my ($submission,$correct,$tablewidth,$rowextra)=@_;
312: #
313: $submission =~ s|\\r\\n|$/|g;
314: $submission = &HTML::Entities::encode($submission,'<>&"');
315: $submission =~ s|$/\s*$/|$/</p><p>$/|g;
316: $submission =~ s|\\||g;
317: $submission = '<p>'.$submission.'</p>';
318: #
319: my $Str = '</tr><tr '.$rowextra.'>'.
320: '<td colspan="'.$tablewidth.'">'.$submission.'</td>';
321: if ($ENV{'form.correctans'} eq 'true') {
322: $Str .= '</tr>';
323: if (defined($correct) && $correct !~ /^\s*$/) {
324: $Str .= '<tr '.$rowextra.'><td colspan="'.$tablewidth.'">'.
325: '<b>'.&mt('Correct Answer:').'</b>'.$correct.'</td>';
326: }
327: }
328: $Str .= '</tr>';
329: #
330: return $Str;
331: }
332:
333: sub html_essay_headers {
334: return '';
335: }
336:
337: sub html_generic_headers {
338: my $header ='<th>'.&mt('Submission').'</th>';
339: if ($ENV{'form.correctans'} eq 'true') {
340: $header .= '<th>'.&mt('Correct').'</th>';
341: }
342: return $header;
343: }
344:
345: sub html_radiobutton {
346: my ($submission,$correct,$tablewidth,$rowclass)=@_;
347: $submission =~ s/=([^=])$//;
348: return &html_generic_results($submission,$correct,$tablewidth,$rowclass);
349: }
350:
351: sub html_generic {
352: my ($submission,$correct,$tablewidth,$rowclass)=@_;
353: my $Str .= '<td>'.$submission.'</td>';
354: if ($ENV{'form.correctans'} eq 'true') {
355: $Str .= '<td>'.$correct.'</td>';
356: }
357: $Str .= '</tr>';
358: return $Str;
359: }
360:
361: sub html_option_headers {
362: return &html_generic_headers();
363: }
364:
365: sub html_option {
366: my ($submission,$correct,$tablewidth,$rowclass)=@_;
367: $submission =
368: '<ul class="sub_studentans">'.
369: '<li>'.join('</li><li>',
370: map {
371: &Apache::lonnet::unescape($_) ;
372: } sort split('&',$submission)
373: ).
374: '</li><ul>';
375: if (defined($correct) && $correct !~ /^\s*$/) {
376: $correct = '<ul class="sub_correctans">'.
377: '<li>'.join('</li><li>',
378: map {
379: &Apache::lonnet::unescape($_) ;
380: } sort split('&',$correct)
381: ).
382: '</li></ul>';
383: }
384: #
385: return &html_generic($submission,$correct,$tablewidth,$rowclass);
386: }
387:
388: #########################################################
389: #########################################################
390: ##
391: ## Excel output of student answers and correct answers
392: ##
393: #########################################################
394: #########################################################
395: sub prepare_excel_output {
396: my ($r,$problem,$ProblemData,$Students) = @_;
397: my $c = $r->connection();
398: my ($resource,$respid,$partid) = ($problem->{'resource'},
399: $problem->{'respid'},
400: $problem->{'part'});
401: $r->print('<h2>'.
402: &mt('Preparing Excel spreadsheet of student responses').
403: '</h2>'.
404: '<p>'.
405: &mt('See the status bar above for student answer computation progress').
406: '</p>');
407: #
408: if ($ENV{'form.correctans'} eq 'true') {
409: &Apache::lonstathelpers::GetStudentAnswers($r,$problem,$Students,
410: 'Statistics',
411: 'stats_status');
412: $r->print('<script>'.
413: 'window.document.Statistics.stats_status.value="'.
414: 'Done computing student answers. Compiling spreadsheet.'.
415: '";</script>');
416: }
417: #
418: $r->rflush();
419: my @Columns;
420: push(@Columns,'username');
421: push(@Columns,'domain');
422: push(@Columns,'attempt');
423: push(@Columns,'time');
424: push(@Columns,'submission');
425: if ($ENV{'form.correctans'} eq 'true') { push(@Columns,'correct'); }
426: push(@Columns,'grading');
427: push(@Columns,'awarded');
428: my $awarded_col = $#Columns;
429: push(@Columns,'weight');
430: my $weight_col = $#Columns;
431: push(@Columns,'score');
432: #
433: # Create excel worksheet
434: my $filename = '/prtspool/'.
435: $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
436: time.'_'.rand(1000000000).'.xls';
437: my $workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
438: if (! defined($workbook)) {
439: $r->log_error("Error creating excel spreadsheet $filename: $!");
440: $r->print('<p>'.&mt("Unable to create new Excel file. ".
441: "This error has been logged. ".
442: "Please alert your LON-CAPA administrator").
443: '</p>');
444: return undef;
445: }
446: #
447: $workbook->set_tempdir('/home/httpd/perl/tmp');
448: #
449: my $format = &Apache::loncommon::define_excel_formats($workbook);
450: my $worksheet = $workbook->addworksheet('Student Submission Data');
451: #
452: # Make sure we get new weight data instead of data on a 10 minute delay
453: &Apache::lonnet::clear_EXT_cache_status();
454: #
455: # Put on the standard headers and whatnot
456: my $rows_output=0;
457: $worksheet->write($rows_output++,0,$resource->{'title'},$format->{'h1'});
458: $worksheet->write($rows_output++,0,$resource->{'src'},$format->{'h3'});
459: $rows_output++;
460: $worksheet->write_row($rows_output++,0,\@Columns,$format->{'bold'});
461: #
462: # Populate the worksheet with the student data
463: my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
464: ($r,'Excel File Compilation Status',
465: 'Excel File Compilation Progress',
466: scalar(@$Students),'inline',undef,'Statistics','stats_status');
467: foreach my $student (@$Students) {
468: last if ($c->aborted());
469:
470: my $results = &Apache::loncoursedata::get_response_data_by_student
471: ($student,$resource->{'symb'},$respid);
472: my %row;
473: $row{'username'} = $student->{'username'};
474: $row{'domain'} = $student->{'domain'};
475: $row{'correct'} = $student->{'answer'};
476: $row{'weight'} = &Apache::lonnet::EXT
477: ('resource.'.$partid.'.weight',$resource->{'symb'},
478: undef,undef,undef);
479: if (! defined($results) || ref($results) ne 'ARRAY') {
480: $row{'score'} = '='.
481: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
482: ($rows_output,$awarded_col)
483: .'*'.
484: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
485: ($rows_output,$weight_col);
486: my $cols_output = 0;
487: foreach my $col (@Columns) {
488: if (! exists($row{$col})) {
489: $cols_output++;
490: next;
491: }
492: $worksheet->write($rows_output,$cols_output++,$row{$col});
493: }
494: $rows_output++;
495: } else {
496: for (my $i=0;$i<scalar(@$results);$i++) {
497: my $response = $results->[$i];
498: if ($ENV{'form.last_sub_only'} eq 'true' &&
499: $i < (scalar(@$results)-1)) {
500: next;
501: }
502: delete($row{'time'});
503: delete($row{'attempt'});
504: delete($row{'submission'});
505: delete($row{'awarded'});
506: delete($row{'grading'});
507: delete($row{'score'});
508: my %row_format;
509: #
510: # Time is handled differently
511: $row{'time'} = &Apache::lonstathelpers::calc_serial
512: ($response->[&Apache::loncoursedata::RDs_timestamp()]);
513: $row_format{'time'}=$format->{'date'};
514: #
515: $row{'attempt'} = $response->[
516: &Apache::loncoursedata::RDs_tries()];
517: $row{'submission'} = $response->[
518: &Apache::loncoursedata::RDs_submission()];
519: if ($row{'submission'} =~ m/^=/) {
520: # This will be interpreted as a formula. That is bad!
521: $row{'submission'} = " ".$row{'submission'};
522: }
523: $row{'grading'} = $response->[
524: &Apache::loncoursedata::RDs_awarddetail()];
525: $row{'awarded'} = $response->[
526: &Apache::loncoursedata::RDs_awarded()];
527: $row{'score'} = '='.
528: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
529: ($rows_output,$awarded_col)
530: .'*'.
531: &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
532: ($rows_output,$weight_col);
533: my $cols_output = 0;
534: foreach my $col (@Columns) {
535: $worksheet->write($rows_output,$cols_output++,$row{$col},
536: $row_format{$col});
537: }
538: $rows_output++;
539: }
540: } # End of else clause on if (! defined($results) ....
541: &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
542: 'last student');
543: }
544: &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
545: #
546: # Close the excel file
547: $workbook->close();
548: #
549: # Write a link to allow them to download it
550: $r->print('<p><a href="'.$filename.'">'.
551: &mt('Your Excel spreadsheet.').
552: '</a></p>'."\n");
553: $r->print('<script>'.
554: 'window.document.Statistics.stats_status.value="'.
555: 'Done compiling spreadsheet. See link below to download.'.
556: '";</script>');
557: $r->rflush();
558: return;
559: }
560:
561: #########################################################
562: #########################################################
563: ##
564: ## Generic Interface Routines
565: ##
566: #########################################################
567: #########################################################
568: sub CreateInterface {
569: ##
570: ## Output Selection
571: my $OutputSelector = $/.'<select name="output">'.$/;
572: foreach ('HTML','Excel') {
573: $OutputSelector .= ' <option value="'.lc($_).'"';
574: if ($ENV{'form.output'} eq lc($_)) {
575: $OutputSelector .= ' selected ';
576: }
577: $OutputSelector .='>'.&mt($_).'</option>'.$/;
578: }
579: $OutputSelector .= '</select>'.$/;
580: ##
581: ## Environment variable initialization
582: my $Str = '';
583: $Str .= &Apache::lonhtmlcommon::breadcrumbs
584: (undef,'Student Submission Reports');
585: $Str .= '<p>';
586: $Str .= '<table cellspacing="5">'."\n";
587: $Str .= '<tr>';
588: $Str .= '<th>'.&mt('Sections').'</th>';
589: $Str .= '<th>'.&mt('Enrollment Status').'</th>';
590: $Str .= '<th>'.&mt('Output Options').'</th>';
591: $Str .= '</tr>'."\n";
592: #
593: $Str .= '<tr><td align="center">'."\n";
594: $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
595: $Str .= '</td>';
596: #
597: $Str .= '<td align="center">';
598: $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
599: $Str .= '</td>';
600: #
601: # Render problem checkbox
602: my $prob_checkbox = '<input type="checkbox" name="renderprob" ';
603: if (exists($ENV{'form.renderprob'}) && $ENV{'form.renderprob'} eq 'true') {
604: $prob_checkbox .= 'checked ';
605: }
606: $prob_checkbox .= 'value="true" />';
607: #
608: # Compute correct answers checkbox
609: my $ans_checkbox = '<input type="checkbox" name="correctans" ';
610: if (exists($ENV{'form.correctans'}) && $ENV{'form.correctans'} eq 'true') {
611: $ans_checkbox .= 'checked ';
612: }
613: $ans_checkbox .= 'value="true" />';
614: #
615: # Only show last submission checkbox
616: my $last_sub_checkbox = '<input type="checkbox" name="last_sub_only" ';
617: if (exists($ENV{'form.last_sub_only'}) &&
618: $ENV{'form.last_sub_only'} eq 'true') {
619: $last_sub_checkbox .= 'checked ';
620: }
621: $last_sub_checkbox.= 'value="true" />';
622: #
623: # Concise view checkbox
624: my $concise_view_checkbox = '<input type="checkbox" name="concise" ';
625: if (exists($ENV{'form.concise'}) && $ENV{'form.concise'} eq 'true') {
626: $concise_view_checkbox .= 'checked ';
627: }
628: $concise_view_checkbox .= 'value="true" />';
629: #
630: $Str .= '<td align="right" halign="top">'.
631: '<b>'.&mt('Output Format: [_1]',$OutputSelector).'</b><br />'.$/.
632: '<label><b>'.
633: &mt('show problem [_1]',$prob_checkbox).'</b></label><br />'.
634: '<label><b>'.
635: &mt('compute correct answers [_1]',$ans_checkbox).'</b></label><br />'.
636: '<label><b>'.
637: &mt('final answer only [_1]',$last_sub_checkbox).'</b></label><br />'.
638: '<label><b>'.
639: &mt('concise view [_1]',$concise_view_checkbox).'</b></label><br />'.
640: '</td>';
641: #
642: $Str .= '</tr>'."\n";
643: $Str .= '</table>'."\n";
644: #
645: $Str .= '<nobr>'.&mt('Status: [_1]',
646: '<input type="text" '.
647: 'name="stats_status" size="60" value="" />').
648: '</nobr>'.'</p>';
649: ##
650: return $Str;
651: }
652:
653: 1;
654:
655: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>