File:  [LON-CAPA] / loncom / interface / statistics / lonstudentsubmissions.pm
Revision 1.21: download - view: text, annotated - select for diffs
Wed Sep 22 15:38:49 2004 UTC (19 years, 9 months ago) by matthew
Branches: MAIN
CVS tags: HEAD
Complete re-write of html output to support multiple problem & part output.
Seems to work pretty well and produce relatively no-ugly output.

    1: # The LearningOnline Network with CAPA
    2: #
    3: # $Id: lonstudentsubmissions.pm,v 1.21 2004/09/22 15:38:49 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 => 'SelectAnother',
   42:                        text => 'Choose a different Problem' },
   43:                      { name => 'Generate',
   44:                        text => 'Generate Report'},
   45:                      );
   46: 
   47: sub BuildStudentSubmissionsPage {
   48:     my ($r,$c)=@_;
   49:     #
   50:     my %Saveable_Parameters = ('Status' => 'scalar',
   51:                                'Section' => 'array',
   52:                                'NumPlots' => 'scalar',
   53:                                );
   54:     &Apache::loncommon::store_course_settings('student_submissions',
   55:                                               \%Saveable_Parameters);
   56:     &Apache::loncommon::restore_course_settings('student_submissions',
   57:                                                 \%Saveable_Parameters);
   58:     #
   59:     &Apache::lonstatistics::PrepareClasslist();
   60:     #
   61:     $r->print(&CreateInterface());
   62:     #
   63:     my @Students = @Apache::lonstatistics::Students;
   64:     #
   65:     if (@Students < 1) {
   66:         $r->print('<h2>There are no students in the sections selected</h2>');
   67:     }
   68:     #
   69:     my @CacheButtonHTML = 
   70:         &Apache::lonstathelpers::manage_caches($r,'Statistics','stats_status',
   71:                                    '<h3>'.&mt('Loading student data').'</h3>');
   72:     $r->rflush();
   73:     #
   74:     if (exists($ENV{'form.problemchoice'}) && 
   75:         ! exists($ENV{'form.SelectAnother'})) {
   76:         foreach my $button (@SubmitButtons) {
   77:             if ($button->{'name'} eq 'break') {
   78:                 $r->print("<br />\n");
   79:             } else {
   80:                 $r->print('<input type="submit" name="'.$button->{'name'}.'" '.
   81:                           'value="'.&mt($button->{'text'}).'" />');
   82:                 $r->print('&nbsp;'x5);
   83:             }
   84:         }
   85:         foreach my $html (@CacheButtonHTML) {
   86:             $r->print($html.('&nbsp;'x5));
   87:         }
   88:         #
   89:         $r->print('<hr />'.$/);
   90:         $r->rflush();
   91:         #
   92:         # Determine which problems we are to analyze
   93:         my @Symbs = 
   94:             &Apache::lonstathelpers::get_selected_symbs('problemchoice');
   95:         foreach my $selected (@Symbs) {
   96:             $r->print('<input type="hidden" name="problemchoice" value="'.
   97:                       $selected.'" />'.$/);
   98:         }
   99:         #
  100:         # Get resource objects
  101:         my $navmap = Apache::lonnavmaps::navmap->new();
  102:         if (!defined($navmap)) {
  103:             $r->print('<h1>'.&mt("Internal error").'</h1>');
  104:             return;
  105:         }
  106:         my %already_seen;
  107:         my @Problems;
  108:         foreach my $symb (@Symbs) {
  109:             my $resource = $navmap->getBySymb($symb);
  110:             push(@Problems,$resource);
  111:         }
  112:         #
  113:         if (! scalar(@Problems) || ! defined($Problems[0])) {
  114:             $r->print('resource is undefined');
  115:         } else {
  116:             if (scalar(@Problems) == 1) {
  117:                 my $resource = $Problems[0];
  118:                 $r->print('<h1>'.$resource->title.'</h1>');
  119:                 $r->print('<h3>'.$resource->src.'</h3>');
  120:                 if ($ENV{'form.renderprob'} eq 'true') {
  121:                     $r->print(
  122:                               &Apache::lonstathelpers::render_resource({src => $resource->src})
  123:                               );
  124:                     $r->rflush();
  125:                 }
  126:             }
  127:             if ($ENV{'form.output'} eq 'excel') {
  128:                 &prepare_excel_output($r,\@Problems,\@Students);
  129:             } else {
  130:                 &prepare_html_output($r,\@Problems,\@Students);
  131:             }
  132:         }
  133:         $r->print('<hr />');
  134:     } else {
  135:         $r->print('<input type="submit" name="Generate" value="'.
  136:                   &mt('Prepare Report').'" />');
  137:         $r->print('&nbsp;'x5);
  138:         $r->print('<p>'.
  139:                   &mt('Computing correct answers greatly increasese the amount of time required to prepare a report.').
  140:                   '</p>');
  141:         $r->print('<p>'.
  142:                   &mt('please select problems and use the <b>Prepare Report</b> button to continue.').
  143:                   '</p>');
  144:         $r->print(&Apache::lonstathelpers::MultipleProblemSelector
  145:                   (undef,'problemchoice','Statistics'));
  146:     }
  147: }
  148: 
  149: #########################################################
  150: #########################################################
  151: ##
  152: ##    HTML Output Routines
  153: ##
  154: #########################################################
  155: #########################################################
  156: sub prepare_html_output {
  157:     my ($r,$problems,$students) = @_;
  158:     my $c = $r->connection();
  159:     #
  160:     # Set a flag for the case when there is just one problem
  161:     my $single_response = 0;
  162:     if (scalar(@$problems) == 1 &&
  163:         $problems->[0]->countResponses == 1) {
  164:         $single_response = 1;
  165:     }
  166:     #
  167:     # Compute the number of columns per response
  168:     my @response_headers = ('Submission');
  169:     if ($ENV{'form.correctans'} eq 'true') {
  170:         push(@response_headers,'Correct');
  171:     } 
  172:     if ($ENV{'form.prob_status'} eq 'true') {
  173:         push(@response_headers,'Award Detail'); 
  174:         push(@response_headers,'Time');
  175:         push(@response_headers,'Attempt');
  176:         push(@response_headers,'Awarded');
  177:     }
  178:     my $response_multiplier = scalar(@response_headers);
  179:     #
  180:     # Create the table header
  181:     my @student_columns = ('username','domain','id');
  182:     #
  183:     my %headers;
  184:     my $student_column_count = scalar(@student_columns);
  185:     $headers{'problem'} = qq{<th colspan="$student_column_count">\&nbsp;</th>};
  186:     foreach (@student_columns) {
  187:         $headers{'student'}.= '<th>'.ucfirst($_).'</th>';
  188:     }
  189:     #
  190:     # we put the headers into the %headers hash
  191:     my $total_col = scalar(@student_columns);
  192:     my $nonempty_part_headers = 0;
  193:     foreach my $prob (@$problems) {
  194:         my $prob_span = 0;
  195:         my $single_part = 0;
  196:         if (scalar(@{$prob->parts}) == 1) {
  197:             $single_part = 1;
  198:         }
  199:         foreach my $partid (@{$prob->parts}) {
  200:             my $part_span = 0;
  201:             my $responses = [$prob->responseIds($partid)];
  202:             my $resptypes = [$prob->responseType($partid)];
  203:             for (my $i=0;$i<scalar(@$responses);$i++) {
  204:                 if ($resptypes->[$i] eq 'essay') {
  205:                     # do something clever, like shut up.
  206:                 } else {
  207:                     $total_col += scalar(@response_headers);
  208:                     $headers{'response'} .=
  209:                         '<th colspan="'.scalar(@response_headers).'">'.
  210:                         &mt('Response [_1]',$responses->[$i]).'</th>';
  211:                     $headers{'student'}.= '<th>'.join('</th><th>',
  212:                                                       @response_headers).
  213:                                                           '</th>';
  214:                     $part_span += scalar(@response_headers);
  215:                 }
  216:             }
  217:             if (! $single_part) {
  218:                 my $tmpname = $partid;
  219:                 if ($partid =~/^\d+$/) {
  220:                     $tmpname = &mt('Part [_1]',$partid);
  221:                 }
  222:                 $headers{'part'} .= qq{<th colspan="$part_span">$tmpname</th>};
  223:                 $nonempty_part_headers = 1;
  224:             } else {
  225:                 $headers{'part'} .= qq{<th colspan="$part_span">&nbsp</th>};
  226:             }
  227:             $prob_span += $part_span;
  228:         }
  229:         my $title = &get_title($prob->title,$prob->src);
  230:         if ($prob_span > 0) {
  231:             $headers{'problem'}.= qq{<th colspan="$prob_span">$title</th>};
  232:         } elsif ($single_response) {
  233:             $prob_span = scalar(@student_columns);
  234:             $headers{'problem'} = qq{<th colspan="$prob_span">$title</th>};
  235:         }
  236:     }
  237:     if (exists($headers{'part'})) {
  238:         $headers{'part'} = qq{<th colspan="$student_column_count">\&nbsp;</th>}.
  239:             $headers{'part'};
  240:     }
  241:     if (exists($headers{'response'})) {
  242:         $headers{'response'}=
  243:             qq{<th colspan="$student_column_count">\&nbsp;</th>}.
  244:             $headers{'response'};
  245:     }
  246:     my $full_header = $/.'<table>'.$/;
  247:     $full_header .= '<tr align="left">'.$headers{'problem'}.'</tr>'.$/;
  248:     if ($nonempty_part_headers) {
  249:         $full_header .= '<tr align="left">'.$headers{'part'}.'</tr>'.$/;
  250:     }
  251:     $full_header .= '<tr align="left">'.$headers{'response'}.'</tr>'.$/;
  252:     $full_header .= '<tr align="left">'.$headers{'student'}.'</tr>'.$/;
  253:     #
  254:     # Main loop
  255:     my $count;
  256:     $r->print($/.$full_header.$/);
  257:     my $row_class = 'odd';   # css 
  258:     foreach my $student (@$students) {
  259:         my $student_row_data;
  260:         if ($count++ >= 30) {
  261:             $r->print('</table>'.$/.$full_header.$/);
  262:             $count = 0;
  263:         }
  264:         last if ($c->aborted());
  265:         foreach my $field (@student_columns) {
  266:             $student_row_data .= 
  267:                 '<td valign="top">'.$student->{$field}.'</td>';
  268:         }
  269:         #
  270:         # Figure out what it is we need to output for this student
  271:         my @essays;
  272:         my %problem_data;
  273:         my $maxrow;
  274:         foreach my $prob (@$problems) {
  275:             my $symb = $prob->symb;
  276:             $problem_data{$symb}={};
  277:             foreach my $partid (@{$prob->parts}) {
  278:                 my @responses = $prob->responseIds($partid);
  279:                 my @response_type = $prob->responseType($partid);
  280:                 for (my $i=0;$i<=$#responses;$i++) {
  281:                     my $respid   = $responses[$i];
  282:                     my $resptype = $response_type[$i];
  283:                     my $width = scalar(@response_headers);
  284:                     $problem_data{$symb}->{$partid}->{$respid}={};
  285:                     my $resp_data = $problem_data{$symb}->{$partid}->{$respid};
  286:                     $resp_data->{'fake'} = qq{<td colspan="$width">&nbsp</td>};
  287:                     my $results = 
  288:                         &Apache::loncoursedata::get_response_data_by_student
  289:                         ($student,$prob->symb(),$respid);
  290:                     if (! defined($results)) {
  291:                         $results = [];
  292:                     }
  293:                     #
  294:                     if (scalar(@$results) > $maxrow && $resptype ne 'essay') {
  295:                         $maxrow = scalar(@$results);
  296:                     }
  297:                     for (my $j=scalar(@$results)-1;$j>=0;$j--) {
  298:                         my $response = $results->[$j];
  299:                         if ($ENV{'form.all_sub'} ne 'true') {
  300:                             next if ($j ne scalar(@$results)-1);
  301:                         }
  302:                         if ($resptype eq 'essay') {
  303:                             push(@essays,
  304:                                  &html_essay_results($student,
  305:                                                      $prob,$partid,$respid,
  306:                                                      $response,
  307:                                                      $single_response).
  308:                                  '</td>');
  309:                         } else {
  310:                             push(@{$resp_data->{'real'}},
  311:                                  &html_results($student,
  312:                                                $prob,$partid,$respid,
  313:                                                $response,$resptype));
  314:                         }
  315:                     } 
  316:                 } # end of $i loop
  317:             } # end of partid loop
  318:         } # end of prob loop
  319:         #
  320:         # if there is no data, skip this student.
  321:         next if (! $maxrow && ! scalar(@essays));
  322:         #
  323:         # Go through the problem data and output a row.
  324:         if ($row_class eq 'even') {
  325:             $row_class = 'odd'; 
  326:         } else {
  327:             $row_class = 'even'; 
  328:         }
  329:         my $printed_something;
  330:         for (my $rows_output = 0;$rows_output<$maxrow;$rows_output++) {
  331:             my $html;
  332:             my $no_data = 1;
  333:             foreach my $prob (@$problems) {
  334:                 my $symb = $prob->symb;
  335:                 foreach my $partid (@{$prob->parts}) {
  336:                     my @responses     = $prob->responseIds($partid);
  337:                     my @response_type = $prob->responseType($partid);
  338:                     for (my $i=0;$i<=$#responses;$i++) {
  339:                         my $respid   = $responses[$i];
  340:                         my $resp_data = 
  341:                             $problem_data{$symb}->{$partid}->{$respid};
  342:                         next if ($response_type[$i] eq 'essay');
  343:                         if (defined($resp_data->{'real'}->[$rows_output])) {
  344:                             $html .= $resp_data->{'real'}->[$rows_output];
  345:                             $no_data = 0;
  346:                         } else {
  347:                             $html .= $resp_data->{'fake'};
  348:                         }
  349:                     }
  350:                 }
  351:             }
  352:             if (! $no_data) {
  353:                 $r->print(qq{<tr class="$row_class">$student_row_data$html</tr>}.$/);
  354:                 $printed_something=1;
  355:             }
  356:         }
  357:         if (@essays) {
  358:             my $tr = qq{<tr class="$row_class">};
  359:             my $td = qq{<td  valign="top" class="essay" colspan="$total_col">};
  360:             if (! $printed_something) {
  361:                 $r->print($tr.$student_row_data.'</tr>'.$/);
  362:             }
  363:             $r->print($tr.$td.
  364:                       join('</td></tr>'.$/.$tr.$td,@essays).'</td></tr>'.$/);
  365:             undef(@essays);
  366:         }
  367:     } # end of student loop
  368:     return;
  369: }
  370: 
  371: #####################################################
  372: ##
  373: ##     HTML helper routines
  374: ##
  375: #####################################################
  376: sub html_essay_results {
  377:     my ($student,$prob,$partid,$respid,$response,$single_response)=@_;
  378:     #
  379:     my $submission =$response->[&Apache::loncoursedata::RDs_submission()];
  380:     $submission = &html_format_sub($submission,'essay');
  381:     #
  382:     my $correct = '';
  383:     if ($ENV{'form.correctans'} eq 'true') {
  384:         $correct = &Apache::lonstathelpers::analyze_problem_as_student
  385:             ($prob,$student->{'username'},$student->{'domain'},
  386:              $partid,$respid);
  387:         $correct = &html_format_sub($correct,'essay');
  388:     }
  389:     my $Str;
  390:     if (! $single_response) {
  391:         my $id = &get_title($prob->title,$prob->src);
  392:         if (defined($partid) && $partid ne '0') {
  393:             $id .= ' '.$partid;
  394:         }
  395:         if (defined($respid)) {
  396:             $id .= ' '.$respid;
  397:         }
  398:         $Str .= '<nobr>'.$id.'</nobr>'.('&nbsp;'x4);
  399:     }
  400:     if ($ENV{'form.prob_status'} eq 'true') {
  401:         $Str .= '<nobr>';
  402:         $Str .= $response->[&Apache::loncoursedata::RDs_awarddetail()].
  403:             ('&nbsp;'x4);
  404:         $Str .= &mt('Attempt [_1]',
  405:                     $response->[&Apache::loncoursedata::RDs_tries()]).
  406:                         ('&nbsp;'x4);
  407:         $Str .= &Apache::lonlocal::locallocaltime
  408:             ($response->[&Apache::loncoursedata::RDs_timestamp()]).
  409:             ('&nbsp;'x4);
  410:         $Str .= &mt('Awarded: [_1]',
  411:                     $response->[&Apache::loncoursedata::RDs_awarded()]).
  412:                         ('&nbsp;'x4);
  413:         $Str .= '</nobr><br />';
  414:     }
  415:     $Str .= $submission;
  416:     if (defined($correct) && $correct !~ /^\s*$/) {
  417:         $Str .= '<hr /><b>'.&mt('Correct').'</b>'.$correct
  418:     }
  419:     return $Str;
  420: }
  421: 
  422: sub html_results {
  423:     my ($student,$prob,$partid,$respid,$response,$resptype) = @_;
  424:     my $submission =$response->[&Apache::loncoursedata::RDs_submission()];
  425:     $submission = &html_format_sub($submission,$resptype);
  426:     my $correct = '';
  427:     if ($ENV{'form.correctans'} eq 'true') {
  428:         $correct = &Apache::lonstathelpers::analyze_problem_as_student
  429:             ($prob,$student->{'username'},$student->{'domain'},
  430:              $partid,$respid);
  431:         $correct = &html_format_sub($correct,$resptype);
  432:     }
  433:     my $Str;
  434:     $Str .= '<td valign="top">'.$submission.'</td>';
  435:     if ($ENV{'form.correctans'} eq 'true') {
  436:         $Str .= '<td valign="top">'.$correct.'</td>';
  437:     }
  438:     if ($ENV{'form.prob_status'} eq 'true') {
  439:         $Str .= '<td valign="top">'.
  440:             $response->[&Apache::loncoursedata::RDs_awarddetail()].
  441:             '</td>';
  442:         $Str .= '<td valign="top"><nobr>'.
  443:             &Apache::lonlocal::locallocaltime
  444:             ($response->[&Apache::loncoursedata::RDs_timestamp()]).
  445:             '</nobr></td>';
  446:         $Str .= '<td valign="top">'.
  447:             $response->[&Apache::loncoursedata::RDs_tries()].
  448:             '</td>';
  449:         $Str .= '<td valign="top">'.
  450:             $response->[&Apache::loncoursedata::RDs_awarded()].
  451:             '</td>';
  452:     }
  453:     return $Str;
  454: }
  455: 
  456: sub html_format_sub {
  457:     my ($submission,$resptype) = @_;
  458:     return '' if (! defined($submission) || $submission eq '');
  459:     if ($resptype eq 'essay') {
  460:         $submission =~ s|\\r\\n|$/|g;
  461:         $submission = &HTML::Entities::encode($submission,'<>&"');
  462:         $submission =~ s|$/\s*$/|$/</p><p>$/|g;
  463:         $submission =~ s|\\||g;
  464:         $submission = '<p>'.$submission.'</p>';
  465:     } elsif ($resptype eq 'radiobutton') {
  466:         $submission =~ s/=([^=])$//;
  467:     } elsif ($resptype =~ /^(option|match|rank)$/) {
  468:         $submission = 
  469:             '<ul class="sub_studentans">'.
  470:             '<li>'.join('</li><li>',
  471:                         map { 
  472:                             &Apache::lonnet::unescape($_) ;
  473:                         } sort split('&',$submission)
  474:                         ).
  475:                         '</li><ul>';
  476:     }
  477:     return $submission;
  478: }
  479: 
  480: #########################################################
  481: #########################################################
  482: ##
  483: ##    Excel Output Routines
  484: ##
  485: #########################################################
  486: #########################################################
  487: sub prepare_excel_output {
  488:     my ($r,$Problems,$Students) = @_;
  489:     my $c = $r->connection();
  490:     #
  491:     #
  492:     # Determine the number of columns in the spreadsheet
  493:     my $columncount = 3; # username, domain, id
  494:     my $response_multiplier = 1;
  495:     $response_multiplier ++   if ($ENV{'form.correctans'} eq 'true');
  496:     $response_multiplier += 4 if ($ENV{'form.prob_status'} eq 'true');
  497:     my $lastprob;
  498:     foreach my $prob (@$Problems) {
  499:         $columncount += ($response_multiplier * $prob->countResponses);
  500:         last if ($columncount > 255);
  501:         $lastprob = $prob;
  502:     }
  503:     if ($columncount > 255) {
  504:         $r->print('<h1>'.&mt('Unable to complete request').'</h1>'.$/.
  505:                   '<p>'.&mt('LON-CAPA is unable to produce your Excel spreadsheet because your selections will result in more than 255 columns.  Excel allows only 255 columns in a spreadsheet.').'</p>'.$/.
  506:                   '<p>'.&mt('Consider selecting fewer problems to generate reports on, or reducing the number of items per problem.  Or use HTML or CSV output.').'</p>'.$/.
  507:                   '<p>'.&mt('The last problem that will fit in the current spreadsheet is [_1].',&get_title($lastprob->title,$lastprob->src)).'</p>');
  508:         $r->rflush();
  509:         return;
  510:     }
  511:     #
  512:     # Print out a message telling them what we are doing
  513:     if (scalar(@$Problems) > 1) {
  514:         $r->print('<h2>'.
  515:                   &mt('Preparing Excel spreadsheet of student responses to [_1] problems',
  516:                       scalar(@$Problems)).
  517:                   '</h2>');
  518:     } else {
  519:         $r->print('<h2>'.
  520:                   &mt('Preparing Excel spreadsheet of student responses').
  521:                   '</h2>');
  522:     }
  523:     $r->rflush();
  524:     #
  525:     # Create the excel spreadsheet
  526:     my $filename = '/prtspool/'.
  527:         $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
  528:         time.'_'.rand(1000000000).'.xls';
  529:     my $workbook  = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
  530:     if (! defined($workbook)) {
  531:         $r->log_error("Error creating excel spreadsheet $filename: $!");
  532:         $r->print('<p>'.&mt("Unable to create new Excel file.  ".
  533:                             "This error has been logged.  ".
  534:                             "Please alert your LON-CAPA administrator").
  535:                   '</p>');
  536:         return undef;
  537:     }
  538:     #
  539:     $workbook->set_tempdir('/home/httpd/perl/tmp');
  540:     #
  541:     my $format = &Apache::loncommon::define_excel_formats($workbook);
  542:     my $worksheet  = $workbook->addworksheet('Student Submission Data');
  543:     #
  544:     # Add headers to the worksheet
  545:     my $rows_output = 0;
  546:     $worksheet->write($rows_output++,0,
  547:                     $ENV{'course.'.$ENV{'request.course.id'}.'.description'},
  548:                       $format->{'h1'});
  549:     $rows_output++;
  550:     my $cols_output = 0;
  551:     my $title_row  = $rows_output++;
  552:     my $partid_row = $rows_output++;
  553:     my $respid_row = $rows_output++;
  554:     my $header_row = $rows_output++;
  555:     $worksheet->write($title_row ,0,'Problem Title',$format->{'bold'});
  556:     $worksheet->write($partid_row,0,'Part ID',$format->{'bold'});
  557:     $worksheet->write($respid_row,0,'Response ID',$format->{'bold'});
  558:     # Student headers
  559:     my @StudentColumns = ('username','domain','id');
  560:     foreach (@StudentColumns) {
  561:         $worksheet->write($header_row,$cols_output++,ucfirst($_),
  562:                           $format->{'bold'});
  563:     }
  564:     # Problem headers
  565:     foreach my $prob (@$Problems) {
  566:         my $title = &get_title($prob->title,$prob->src);
  567:         $worksheet->write($title_row,$cols_output,
  568:                           $title,$format->{'h3'});
  569:         foreach my $partid (@{$prob->parts}) {
  570:             $worksheet->write($partid_row,$cols_output,$partid);
  571:             my $responses = [$prob->responseIds($partid)];
  572:             my $resptypes = [$prob->responseType($partid)];
  573:             for (my $i=0;$i<scalar(@$responses);$i++) {
  574:                 $worksheet->write($respid_row,$cols_output,
  575:                                   $resptypes->[$i].', '.$responses->[$i]);
  576:                 $worksheet->write($header_row,$cols_output,'Submission');
  577:                 $cols_output++;
  578:                 if ($ENV{'form.correctans'} eq 'true') {
  579:                     $worksheet->write($header_row,$cols_output,'Correct');
  580:                     $cols_output++;
  581:                 }
  582:                 if ($ENV{'form.prob_status'} eq 'true') {
  583:                     $worksheet->write($header_row,$cols_output++,
  584:                                       'Award Detail');
  585:                     $worksheet->write($header_row,$cols_output++,'Attempt');
  586:                     $worksheet->write($header_row,$cols_output++,'Time');
  587:                     $worksheet->write($header_row,$cols_output++,'Awarded');
  588:                 }
  589:             }
  590:         }
  591:     }
  592:     #
  593:     # Populate the worksheet with the student data
  594:     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
  595:         ($r,'Excel File Compilation Status',
  596:          'Excel File Compilation Progress', 
  597:          scalar(@$Students),'inline',undef,'Statistics','stats_status');
  598:     my $max_row = $rows_output;
  599:     foreach my $student (@$Students) {
  600:         last if ($c->aborted());
  601:         $cols_output = 0;
  602:         my $student_row = $max_row;
  603:         foreach my $field (@StudentColumns) {
  604:             $worksheet->write($student_row,$cols_output++,
  605:                               $student->{$field});
  606:         }
  607:         my $last_student_col = $cols_output-1;
  608:         my $response_count;
  609:         foreach my $prob (@$Problems) {
  610:             foreach my $partid (@{$prob->parts}) {
  611:                 my @Response = $prob->responseIds($partid);
  612:                 my @ResponseType = $prob->responseType($partid);
  613:                 for (my $i=0;$i<=$#Response;$i++) {
  614:                     my $response_start_col = $last_student_col + 
  615:                         $response_count * $response_multiplier + 1;
  616:                     $response_count++;
  617:                     my $respid   = $Response[$i];
  618:                     my $resptype = $ResponseType[$i];
  619:                     my $results = 
  620:                         &Apache::loncoursedata::get_response_data_by_student
  621:                         ($student,$prob->symb(),$respid);
  622:                     if (! defined($results)) {
  623:                         $results = [];
  624:                     }
  625:                     #
  626:                     $rows_output = $student_row;
  627:                     #
  628:                     for (my $j=scalar(@$results)-1;$j>=0;$j--) {
  629:                         $cols_output = $response_start_col;
  630:                         my $response = $results->[$j];
  631:                         if ($ENV{'form.all_sub'} ne 'true') {
  632:                             next if ($j ne scalar(@$results)-1);
  633:                         }
  634:                         my $cols_output = &write_excel_row
  635:                             ($worksheet,
  636:                              $rows_output++,$cols_output,
  637:                              $response,$student,
  638:                              $prob,$partid,$respid,
  639:                              $format,$resptype);
  640:                         if ($rows_output > $max_row) {
  641:                             $max_row = $rows_output;
  642:                         }
  643:                     }
  644:                 }
  645:             }
  646:         }
  647:         $rows_output++;
  648:         &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
  649:                                                  'last student');
  650:     }
  651:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
  652:     #
  653:     # Close the excel file
  654:     $workbook->close();
  655:     #
  656:     # Write a link to allow them to download it
  657:     $r->print('<p><a href="'.$filename.'">'.
  658:               &mt('Your Excel spreadsheet.').
  659:               '</a></p>'."\n");
  660:     $r->print('<script>'.
  661:               'window.document.Statistics.stats_status.value="'.
  662:               'Done compiling spreadsheet.  See link below to download.'.
  663:               '";</script>');
  664:     $r->rflush();
  665:     return;
  666: }
  667: 
  668: sub write_excel_row {
  669:     my ($worksheet,$row,$col,$response,$student,$prob,$partid,$respid,
  670:         $format,$resptype) = @_;
  671:     # 
  672:     my $submission =$response->[&Apache::loncoursedata::RDs_submission()];
  673:     $submission = &excel_format_response($submission,$resptype);
  674:     $worksheet->write($row,$col++,$submission);
  675:     if ($ENV{'form.correctans'} eq 'true') {
  676:         my $correct = &Apache::lonstathelpers::analyze_problem_as_student
  677:             ($prob,$student->{'username'},$student->{'domain'},
  678:              $partid,$respid);
  679:         $correct =&excel_format_response($correct,$resptype);
  680:         $worksheet->write($row,$col++,$correct);
  681:     }
  682:     if ($ENV{'form.prob_status'} eq 'true') {
  683:         $worksheet->write
  684:             ($row,$col++,
  685:              $response->[&Apache::loncoursedata::RDs_awarddetail()]);
  686:         $worksheet->write
  687:             ($row,$col++,$response->[&Apache::loncoursedata::RDs_tries()]);
  688:         $worksheet->write
  689:             ($row,$col++,
  690:              &Apache::lonstathelpers::calc_serial
  691:              ($response->[&Apache::loncoursedata::RDs_timestamp()]),
  692:              $format->{'date'});
  693:         $worksheet->write
  694:             ($row,$col++,$response->[&Apache::loncoursedata::RDs_awarded()]);
  695:     }
  696:     return $col;
  697: }
  698: 
  699: sub get_title {
  700:     my ($title,$src) = @_;
  701:     if ($title eq '') {
  702:         ($title) = ($src =~ m|/([^/]+)$|);
  703:     } else {
  704:         $title =~ s/\&colon;/:/g;
  705:     }
  706:     return $title;
  707: }
  708: 
  709: sub excel_format_response {
  710:     my ($answer,$responsetype) = @_;
  711:     if ($responsetype eq 'radiobutton') {
  712:         $answer =~ s/=([^=])$//;
  713:     } elsif ($responsetype =~ /^(option|match)$/) {
  714:         $answer = join("\n",
  715:                        map { 
  716:                            &Apache::lonnet::unescape($_) ;
  717:                        } sort split('&',$answer)
  718:                        );
  719:     }
  720:     if ($answer =~ m/^=/) {
  721:         $answer = ' '.$answer;
  722:     }
  723:     return $answer;
  724: }
  725: 
  726: ##
  727: ## Currently not used
  728: sub get_problem_data {
  729:     my ($r,$Problems) = @_;
  730:     #
  731:     # Analyze 
  732:     my %Data;
  733:     if (scalar(@$Problems) > 5) {
  734:         # progress window
  735:         my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
  736:             ($r,'Problem Analysis Status',
  737:              'Problem Analysis Progress', 
  738:              scalar(@$Problems),
  739:              'inline',undef,'Statistics','stats_status');
  740:         foreach my $problem (@$Problems) {
  741:             $Data{$problem->symb} = 
  742:             {&Apache::lonstathelpers::get_problem_data
  743:                  ($problem->src)};
  744:             &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
  745:                                                      'last problem');
  746:         }
  747:         &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
  748:     } else {
  749:         foreach my $problem (@$Problems) {
  750:             $Data{$problem->symb} = 
  751:             {&Apache::lonstathelpers::get_problem_data
  752:                  ($problem->src)};
  753:         }
  754:     }
  755:     return \%Data;
  756: }
  757: 
  758: 
  759: =pod
  760: 
  761: #########################################################
  762: #########################################################
  763: ##
  764: ##      CSV output of student answers
  765: ##
  766: #########################################################
  767: #########################################################
  768: sub prepare_csv_output {
  769:     my ($r,$Problems,$Students) = @_;
  770:     my $problem;
  771:     #
  772:     my $c = $r->connection();
  773:     my ($resource,$respid,$partid) = ($problem->{'resource'},
  774:                                       $problem->{'respid'},
  775:                                       $problem->{'part'});
  776:     #
  777:     if ($ENV{'form.correctans'} eq 'true') {
  778:         $r->print('<h2>'.&mt('Generating Correct Answers').'</h2>');
  779:         &Apache::lonstathelpers::GetStudentAnswers($r,$problem,$Students,
  780:                                                    'Statistics',
  781:                                                    'stats_status');
  782:     }
  783:     #
  784:     $r->print('<h2>'.
  785:               &mt('Generating CSV report of student responses').'</h2>');
  786:     #
  787:     # Progress window
  788:     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
  789:         ($r,'CSV File Compilation Status',
  790:          'CSV File Compilation Progress', 
  791:          scalar(@$Students),'inline',undef,'Statistics','stats_status');
  792: 
  793:     $r->rflush();
  794:     #
  795:     # Open a file
  796:     my $outputfile;
  797:     my $filename = '/prtspool/'.
  798:         $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
  799:             time.'_'.rand(1000000000).'.csv';
  800:     unless ($outputfile = Apache::File->new('>/home/httpd'.$filename)) {
  801:         $r->log_error("Couldn't open $filename for output $!");
  802:         $r->print("Problems occured in writing the csv file.  ".
  803:                   "This error has been logged.  ".
  804:                   "Please alert your LON-CAPA administrator.");
  805:         $outputfile = undef;
  806:     }
  807:     #
  808:     #
  809:     my @Columns;
  810:     if (exists($ENV{'form.concise'}) && $ENV{'form.concise'} eq 'true') {
  811:         foreach (@DefaultColumns) {
  812:             if ($_->{'name'} =~ /^(username|domain|id)$/){
  813:                 push(@Columns,$_);
  814:             }
  815:         }
  816:     } else {
  817:         @Columns = @DefaultColumns;
  818:     }
  819:     #
  820:     my $response_type = &get_response_type($resource,$partid,$respid);
  821:     if (! defined($response_type) || $response_type eq '') {
  822:         $r->print('<h2>'.&mt('Unable to determine response type').'</h2>');
  823:         return;
  824:     }
  825:     #
  826:     my $header = &csv_headers(\@Columns).','.&csv_generic_headers();
  827:     print $outputfile $header.$/;
  828:     #
  829:     foreach my $student (@$Students) {
  830:         last if ($c->aborted());
  831:         my $results = &Apache::loncoursedata::get_response_data_by_student
  832:             ($student,$resource->{'symb'},$respid);
  833:         next if (! defined($results) || ref($results) ne 'ARRAY');
  834:         for (my $i=0;$i<scalar(@$results);$i++) {
  835:             my $response = $results->[$i];
  836:             if ($ENV{'form.last_sub_only'} eq 'true' && 
  837:                 $i < (scalar(@$results)-1)) {
  838:                 next;
  839:             }
  840:             my $data;
  841:             $data->{'username'} = $student->{'username'};
  842:             $data->{'domain'}   = $student->{'domain'};
  843:             $data->{'id'} = $student->{'id'};
  844:             $data->{'fullname'} = $student->{'fullanem'};
  845:             $data->{'status'} = $student->{'status'};
  846:             $data->{'time'} = &Apache::lonlocal::locallocaltime
  847:                 ($response->[&Apache::loncoursedata::RDs_timestamp()]);
  848:             $data->{'attempt'} = 
  849:                 $response->[&Apache::loncoursedata::RDs_tries()];
  850:             $data->{'awarded'} = 
  851:                 $response->[&Apache::loncoursedata::RDs_awarded()];
  852:             $data->{'awarddetail'} = 
  853:                 $response->[&Apache::loncoursedata::RDs_awarddetail()];
  854:             $data->{'weight'} = &Apache::lonnet::EXT
  855:                 ('resource.'.$partid.'.weight',$resource->{'symb'},
  856:                  undef,undef,undef);
  857:             $data->{'score'} = $data->{'weight'} * $data->{'awarded'};
  858:             my $rowextra = '';
  859:             my $row;
  860:             foreach my $col (@Columns) {
  861:                 $row .= '"'.
  862:                     &Apache::loncommon::csv_translate($data->{$col->{'name'}}).'",';
  863:             }
  864:             if ($response_type eq 'option') {
  865:                 $row .= &csv_option_results
  866:                     ($response->[&Apache::loncoursedata::RDs_submission()],
  867:                      $student->{'answer'},
  868:                      scalar(@Columns),$rowextra);
  869:             } elsif ($response_type eq 'radiobutton') {
  870:                 $row .= &csv_radiobutton_results
  871:                     ($response->[&Apache::loncoursedata::RDs_submission()],
  872:                      $student->{'answer'},
  873:                      scalar(@Columns),$rowextra);
  874:             } else {
  875:                 $row .= &csv_generic_results
  876:                     ($response->[&Apache::loncoursedata::RDs_submission()],
  877:                      $student->{'answer'},
  878:                      scalar(@Columns),$rowextra);
  879:             }
  880:             print $outputfile $row.$/;
  881:         }
  882:         &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
  883:                                                  'last student');
  884:     }
  885:     close($outputfile);
  886:     #
  887:     # Close the progress window
  888:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
  889:     #
  890:     # Tell the user where to get their csv file
  891:     $r->print('<br />'.
  892:               '<a href="'.$filename.'">'.&mt('Your csv file.').'</a>'."\n");
  893:     $r->rflush();
  894:     return;
  895: }
  896: 
  897: sub csv_headers {
  898:     my ($Columns) = @_;
  899:     my $Str;
  900:     foreach my $column (@$Columns) {
  901:         $Str .= 
  902:             '"'.&Apache::loncommon::csv_translate($column->{'display'}).'",';
  903:     }
  904:     chop($Str);
  905:     return $Str;
  906: }
  907: 
  908: sub csv_generic_headers {
  909:     my ($title) = @_;
  910:     if (! defined($title)) {
  911:         $title = &mt('Submission');
  912:     }
  913:     my $header = '"'.&Apache::loncommon::csv_translate($title).'"';
  914:     if ($ENV{'form.correctans'} eq 'true') {
  915:         $header .= ',"'.&Apache::loncommon::csv_translate(&mt('Correct')).'"';
  916:     }
  917:     return $header;
  918: }
  919: 
  920: #------------------------------------------
  921: sub csv_essay_results {
  922:     my ($submission,$correct,$tablewidth,$rowextra)=@_;
  923:     #
  924:     $submission =~ s|\\r|\\\\r|g;
  925:     $submission =~ s|\\n|\\\\n|g;
  926:     #
  927:     return &csv_generic_results($submission,$correct,$tablewidth);
  928: }
  929: 
  930: #------------------------------------------
  931: sub csv_radiobutton_results {
  932:     my ($submission,$correct,$tablewidth,$rowclass)=@_;
  933:     $submission =~ s/=[^=]*$//;
  934:     return &csv_generic_results($submission,$correct,$tablewidth,$rowclass);
  935: }
  936: 
  937: #------------------------------------------
  938: sub csv_option_results {
  939:     my ($submission,$correct,$tablewidth,$rowclass)=@_;
  940:     $submission = join(',',
  941:                        map { 
  942:                            &Apache::lonnet::unescape($_) ;
  943:                        } sort split('&',$submission)
  944:                        );
  945:     if (defined($correct) && $correct !~ /^\s*$/) {
  946:         $correct =join(',',
  947:                        map { 
  948:                            &Apache::lonnet::unescape($_) ;
  949:                        } sort split('&',$submission));
  950:     }
  951:     return &csv_generic_results($submission,$correct,$tablewidth,$rowclass);
  952: }
  953: 
  954: #------------------------------------------
  955: sub csv_generic_results {
  956:     my ($submission,$correct,$tablewidth,$rowclass)=@_;
  957:     my $Str .= 
  958:         '"'.&Apache::loncommon::csv_translate($submission).'"';
  959:     if ($ENV{'form.correctans'} eq 'true') {
  960:         $Str .= ',"'.&Apache::loncommon::csv_translate($correct).'"';
  961:     }
  962:     return $Str;
  963: }
  964: 
  965: =cut
  966: 
  967: #########################################################
  968: #########################################################
  969: ##
  970: ##   Generic Interface Routines
  971: ##
  972: #########################################################
  973: #########################################################
  974: sub CreateInterface {
  975:     ##
  976:     ## Output Selection
  977:     my $output_selector = $/.'<select name="output">'.$/;
  978: #    foreach ('HTML','Excel','CSV') {
  979:     foreach ('HTML','Excel') {
  980:         $output_selector .= '    <option value="'.lc($_).'"';
  981:         if ($ENV{'form.output'} eq lc($_)) {
  982:             $output_selector .= ' selected ';
  983:         }
  984:         $output_selector .='>'.&mt($_).'</option>'.$/;
  985:     } 
  986:     $output_selector .= '</select>'.$/;
  987:     ##
  988:     ## Environment variable initialization
  989:     my $Str = '';
  990:     $Str .= &Apache::lonhtmlcommon::breadcrumbs
  991:         (undef,'Student Submission Reports');
  992:     $Str .= '<p>';
  993:     $Str .= '<table cellspacing="5">'."\n";
  994:     $Str .= '<tr>';
  995:     $Str .= '<th>'.&mt('Sections').'</th>';
  996:     $Str .= '<th>'.&mt('Enrollment Status').'</th>';
  997:     $Str .= '<th>'.&mt('Output as [_1]',$output_selector).'</th>';
  998:     $Str .= '</tr>'."\n";
  999:     #
 1000:     $Str .= '<tr><td align="center">'."\n";
 1001:     $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
 1002:     $Str .= '</td>';
 1003:     #
 1004:     $Str .= '<td align="center">';
 1005:     $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
 1006:     $Str .= '</td>';
 1007:     #
 1008:     # Render problem checkbox
 1009:     my $prob_checkbox = '<input type="checkbox" name="renderprob" ';
 1010:     if (exists($ENV{'form.renderprob'}) && $ENV{'form.renderprob'} eq 'true') {
 1011:         $prob_checkbox .= 'checked ';
 1012:     }
 1013:     $prob_checkbox .= 'value="true" />';
 1014:     #
 1015:     # Compute correct answers checkbox
 1016:     my $ans_checkbox = '<input type="checkbox" name="correctans" ';
 1017:     if (exists($ENV{'form.correctans'}) && $ENV{'form.correctans'} eq 'true') {
 1018:         $ans_checkbox .= 'checked ';
 1019:     }
 1020:     $ans_checkbox .= 'value="true" />';
 1021:     #
 1022:     # Show all submissions checkbox
 1023:     my $all_sub_checkbox = '<input type="checkbox" name="all_sub" ';
 1024:     if (exists($ENV{'form.all_sub'}) && 
 1025:         $ENV{'form.all_sub'} eq 'true') {
 1026:         $all_sub_checkbox .= 'checked ';
 1027:     }
 1028:     $all_sub_checkbox.= 'value="true" />';
 1029:     #
 1030:     # problem status checkbox
 1031:     my $prob_status_checkbox = '<input type="checkbox" name="prob_status" ';
 1032:     if (exists($ENV{'form.prob_status'}) && 
 1033:         $ENV{'form.prob_status'} eq 'true') {
 1034:         $prob_status_checkbox .= 'checked ';
 1035:     }
 1036:     $prob_status_checkbox .= 'value="true" />';
 1037:     #
 1038:     $Str .= '<td align="right" halign="top">'.
 1039:         '<label><b>'.
 1040:         &mt('Show problem [_1]',$prob_checkbox).'</b></label><br />'.
 1041:         '<label><b>'.
 1042:         &mt('Show correct answers [_1]',$ans_checkbox).'</b></label><br />'.
 1043:         '<label><b>'.
 1044:         &mt('Show all submissions [_1]',$all_sub_checkbox).
 1045:         '</b></label><br />'.
 1046:         '<label><b>'.
 1047:         &mt('Show problem grading [_1]',$prob_status_checkbox).
 1048:         '</b></label><br />'.
 1049:         '</td>';
 1050:     #
 1051:     $Str .= '</tr>'."\n";
 1052:     $Str .= '</table>'."\n";
 1053:     #
 1054:     $Str .= '<nobr>'.&mt('Status: [_1]',
 1055:                          '<input type="text" '.
 1056:                          'name="stats_status" size="60" value="" />').
 1057:             '</nobr>'.'</p>';    
 1058:     ##
 1059:     return $Str;
 1060: }
 1061: 
 1062: 1;
 1063: 
 1064: __END__

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>