Annotation of loncom/interface/statistics/lonstudentassessment.pm, revision 1.61

1.1       stredwic    1: # The LearningOnline Network with CAPA
                      2: #
1.61    ! www         3: # $Id: lonstudentassessment.pm,v 1.60 2003/06/17 17:47:54 matthew Exp $
1.1       stredwic    4: #
                      5: # Copyright Michigan State University Board of Trustees
                      6: #
                      7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
                      8: # LON-CAPA is free software; you can redistribute it and/or modify
                      9: # it under the terms of the GNU General Public License as published by
                     10: # the Free Software Foundation; either version 2 of the License, or
                     11: # (at your option) any later version.
                     12: #
                     13: # LON-CAPA is distributed in the hope that it will be useful,
                     14: # but WITHOUT ANY WARRANTY; without even the implied warranty of
                     15: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     16: # GNU General Public License for more details.
                     17: #
                     18: # You should have received a copy of the GNU General Public License
                     19: # along with LON-CAPA; if not, write to the Free Software
                     20: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
                     21: #
                     22: # /home/httpd/html/adm/gpl.txt
                     23: #
                     24: # http://www.lon-capa.org/
                     25: #
                     26: # (Navigate problems for statistical reports
1.28      matthew    27: #
                     28: #######################################################
                     29: #######################################################
                     30: 
                     31: =pod
                     32: 
                     33: =head1 NAME
                     34: 
                     35: lonstudentassessment
                     36: 
                     37: =head1 SYNOPSIS
                     38: 
                     39: Presents assessment data about a student or a group of students.
                     40: 
                     41: =head1 Subroutines
                     42: 
                     43: =over 4 
                     44: 
                     45: =cut
                     46: 
                     47: #######################################################
                     48: #######################################################
1.1       stredwic   49: 
1.21      minaeibi   50: package Apache::lonstudentassessment;
1.1       stredwic   51: 
                     52: use strict;
1.28      matthew    53: use Apache::lonstatistics;
1.1       stredwic   54: use Apache::lonhtmlcommon;
                     55: use Apache::loncoursedata;
1.28      matthew    56: use Apache::lonnet; # for logging porpoises
1.31      matthew    57: use Spreadsheet::WriteExcel;
                     58: 
                     59: #######################################################
                     60: #######################################################
                     61: =pod
                     62: 
                     63: =item Package Variables
                     64: 
                     65: =over 4
                     66: 
                     67: =item $Statistics Hash ref to store student data.  Indexed by symb,
                     68:       contains hashes with keys 'score' and 'max'.
                     69: 
                     70: =cut
                     71: 
                     72: #######################################################
                     73: #######################################################
1.1       stredwic   74: 
1.30      matthew    75: my $Statistics;
                     76: 
1.28      matthew    77: #######################################################
                     78: #######################################################
                     79: 
                     80: =pod
                     81: 
1.31      matthew    82: =item $show_links 'yes' or 'no' for linking to student performance data
                     83: 
                     84: =item $output_mode 'html', 'excel', or 'csv' for output mode
                     85: 
1.32      matthew    86: =item $show 'all', 'totals', or 'scores' determines how much data is output
1.31      matthew    87: 
1.54      matthew    88: =item $data  determines what performance data is shown
                     89: 
                     90: =item $datadescription A short description of the output data selected.
                     91: 
                     92: =item $base 'tries' or 'scores' determines the base of the performance shown
                     93: 
1.49      matthew    94: =item $single_student_mode evaluates to true if we are showing only one
                     95: student.
                     96: 
1.31      matthew    97: =cut
                     98: 
                     99: #######################################################
                    100: #######################################################
                    101: my $show_links;
                    102: my $output_mode;
1.54      matthew   103: my $data;
                    104: my $base;
                    105: my $datadescription;
1.49      matthew   106: my $single_student_mode;
1.28      matthew   107: 
1.31      matthew   108: #######################################################
                    109: #######################################################
                    110: # End of package variable declarations
1.28      matthew   111: 
1.31      matthew   112: =pod
1.28      matthew   113: 
1.31      matthew   114: =back
1.28      matthew   115: 
1.31      matthew   116: =cut
1.28      matthew   117: 
1.31      matthew   118: #######################################################
                    119: #######################################################
1.28      matthew   120: 
1.31      matthew   121: =pod
1.28      matthew   122: 
1.31      matthew   123: =item &BuildStudentAssessmentPage()
1.28      matthew   124: 
1.31      matthew   125: Inputs: 
1.4       stredwic  126: 
1.31      matthew   127: =over 4
1.28      matthew   128: 
                    129: =item $r Apache Request
                    130: 
                    131: =item $c Apache Connection 
                    132: 
                    133: =back
                    134: 
                    135: =cut
                    136: 
                    137: #######################################################
                    138: #######################################################
1.1       stredwic  139: sub BuildStudentAssessmentPage {
1.30      matthew   140:     my ($r,$c)=@_;
                    141:     undef($Statistics);
1.49      matthew   142:     $single_student_mode = 1 if ($ENV{'form.SelectedStudent'});
1.59      matthew   143:     if ($ENV{'form.selectstudent'}) {
                    144:         &Apache::lonstatistics::DisplayClasslist($r);
                    145:         return;
                    146:     }
1.30      matthew   147:     #
1.31      matthew   148:     # Print out the HTML headers for the interface
                    149:     #    This also parses the output mode selector
1.54      matthew   150:     #    This step must *always* be done.
1.30      matthew   151:     $r->print(&CreateInterface());
1.31      matthew   152:     $r->print('<input type="hidden" name="notfirstrun" value="true" />');
1.49      matthew   153:     $r->print('<input type="hidden" name="sort" value="'.
                    154:               $ENV{'form.sort'}.'" />');
1.7       stredwic  155:     $r->rflush();
1.58      matthew   156:     #
1.49      matthew   157:     if (! exists($ENV{'form.notfirstrun'}) && ! $single_student_mode) {
1.31      matthew   158:         return;
                    159:     }
                    160:     #
                    161:     my $initialize     = \&html_initialize;
                    162:     my $output_student = \&html_outputstudent;
                    163:     my $finish         = \&html_finish;
                    164:     #
                    165:     if ($output_mode eq 'excel') {
                    166:         $initialize     = \&excel_initialize;
                    167:         $output_student = \&excel_outputstudent;
                    168:         $finish         = \&excel_finish;
                    169:     } elsif ($output_mode eq 'csv') {
                    170:         $initialize     = \&csv_initialize;
                    171:         $output_student = \&csv_outputstudent;
                    172:         $finish         = \&csv_finish;
                    173:     }
1.30      matthew   174:     #
                    175:     if($c->aborted()) {  return ; }
1.31      matthew   176:     #
1.49      matthew   177:     # Determine which students we want to look at
                    178:     my @Students;
                    179:     if ($single_student_mode) {
                    180:         @Students = (&Apache::lonstatistics::current_student());
                    181:         $r->print(&next_and_previous_buttons());
                    182:         $r->rflush();
                    183:     } else {
                    184:         @Students = @Apache::lonstatistics::Students;
                    185:     }
1.56      matthew   186:     #
                    187:     # Perform generic initialization tasks
                    188:     #       Since we use lonnet::EXT to retrieve problem weights,
                    189:     #       to ensure current data we must clear the caches out.
                    190:     #       This makes sure that parameter changes at the student level
                    191:     #       are immediately reflected in the chart.
                    192:     &Apache::lonnet::clear_EXT_cache_status();
1.49      matthew   193:     #
1.31      matthew   194:     # Call the initialize routine selected above
                    195:     $initialize->($r);
1.49      matthew   196:     foreach my $student (@Students) {
1.31      matthew   197:         if($c->aborted()) { 
                    198:             $finish->($r);
                    199:             return ; 
1.1       stredwic  200:         }
1.31      matthew   201:         # Call the output_student routine selected above
                    202:         $output_student->($r,$student);
                    203:     }
                    204:     # Call the "finish" routine selected above
                    205:     $finish->($r);
                    206:     #
                    207:     return;
                    208: }
                    209: 
                    210: #######################################################
                    211: #######################################################
1.49      matthew   212: sub next_and_previous_buttons {
                    213:     my $Str = '';
                    214:     $Str .= '<input type="hidden" name="SelectedStudent" value="'.
                    215:         $ENV{'form.SelectedStudent'}.'" />';
                    216:     #
                    217:     # Build the previous student link
                    218:     my $previous = &Apache::lonstatistics::previous_student();
                    219:     my $previousbutton = '';
                    220:     if (defined($previous)) {
                    221:         my $sname = $previous->{'username'}.':'.$previous->{'domain'};
                    222:         $previousbutton .= '<input type="button" value="'.
                    223:             'Previous Student ('.
                    224:             $previous->{'username'}.'@'.$previous->{'domain'}.')'.
                    225:             '" onclick="document.Statistics.SelectedStudent.value='.
                    226:             "'".$sname."'".';'.
                    227:             'document.Statistics.submit();" />';
                    228:     } else {
                    229:         $previousbutton .= '<input type="button" value="'.
                    230:             'Previous student (none)'.'" />';
                    231:     }
                    232:     #
                    233:     # Build the next student link
                    234:     my $next = &Apache::lonstatistics::next_student();
                    235:     my $nextbutton = '';
                    236:     if (defined($next)) {
                    237:         my $sname = $next->{'username'}.':'.$next->{'domain'};
                    238:         $nextbutton .= '<input type="button" value="'.
                    239:             'Next Student ('.
                    240:             $next->{'username'}.'@'.$next->{'domain'}.')'.
                    241:             '" onclick="document.Statistics.SelectedStudent.value='.
                    242:             "'".$sname."'".';'.
                    243:             'document.Statistics.submit();" />';
                    244:     } else {
                    245:         $nextbutton .= '<input type="button" value="'.
                    246:             'Next student (none)'.'" />';
                    247:     }
                    248:     #
                    249:     # Build the 'all students' button
                    250:     my $all = '';
                    251:     $all .= '<input type="button" value="All Students" '.
                    252:             '" onclick="document.Statistics.SelectedStudent.value='.
                    253:             "''".';'.'document.Statistics.submit();" />';
                    254:     $Str .= $previousbutton.('&nbsp;'x5).$all.('&nbsp;'x5).$nextbutton;
                    255:     return $Str;
                    256: }
                    257: 
                    258: #######################################################
                    259: #######################################################
1.30      matthew   260: 
1.31      matthew   261: sub get_student_fields_to_show {
                    262:     my @to_show = @Apache::lonstatistics::SelectedStudentData;
                    263:     foreach (@to_show) {
                    264:         if ($_ eq 'all') {
                    265:             @to_show = @Apache::lonstatistics::StudentDataOrder;
                    266:             last;
                    267:         }
                    268:     }
                    269:     return @to_show;
                    270: }
                    271: 
1.28      matthew   272: #######################################################
                    273: #######################################################
                    274: 
                    275: =pod
1.2       stredwic  276: 
1.28      matthew   277: =item &CreateInterface()
1.21      minaeibi  278: 
1.28      matthew   279: Called by &BuildStudentAssessmentPage to create the top part of the
                    280: page which displays the chart.
                    281: 
1.30      matthew   282: Inputs: None
1.28      matthew   283: 
                    284: Returns:  A string containing the HTML for the headers and top table for 
                    285: the chart page.
                    286: 
                    287: =cut
                    288: 
                    289: #######################################################
                    290: #######################################################
1.2       stredwic  291: sub CreateInterface {
1.4       stredwic  292:     my $Str = '';
1.30      matthew   293: #    $Str .= &CreateLegend();
                    294:     $Str .= '<table cellspacing="5">'."\n";
                    295:     $Str .= '<tr>';
                    296:     $Str .= '<td align="center"><b>Sections</b></td>';
                    297:     $Str .= '<td align="center"><b>Student Data</b></td>';
1.46      matthew   298:     $Str .= '<td align="center"><b>Enrollment Status</b></td>';
1.41      matthew   299:     $Str .= '<td align="center"><b>Sequences and Folders</b></td>';
1.58      matthew   300:     $Str .= '<td align="center"><b>Output Format</b>'.
                    301:         &Apache::loncommon::help_open_topic("Chart_Output_Formats").
                    302:         '</td>';
                    303:     $Str .= '<td align="center"><b>Output Data</b>'.
                    304:         &Apache::loncommon::help_open_topic("Chart_Output_Data").
                    305:         '</td>';
1.30      matthew   306:     $Str .= '</tr>'."\n";
                    307:     #
1.4       stredwic  308:     $Str .= '<tr><td align="center">'."\n";
1.29      matthew   309:     $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
1.4       stredwic  310:     $Str .= '</td><td align="center">';
1.30      matthew   311:     my $only_seq_with_assessments = sub { 
                    312:         my $s=shift;
                    313:         if ($s->{'num_assess'} < 1) { 
                    314:             return 0;
                    315:         } else { 
                    316:             return 1;
                    317:         }
                    318:     };
                    319:     $Str .= &Apache::lonstatistics::StudentDataSelect('StudentData','multiple',
                    320:                                                       5,undef);
1.46      matthew   321:     $Str .= '</td><td>'."\n";
                    322:     $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
1.4       stredwic  323:     $Str .= '</td><td>'."\n";
1.30      matthew   324:     $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
                    325:                                               $only_seq_with_assessments);
1.31      matthew   326:     $Str .= '</td><td>'."\n";
                    327:     $Str .= &CreateAndParseOutputSelector();
1.54      matthew   328:     $Str .= '</td><td>'."\n";
                    329:     $Str .= &CreateAndParseOutputDataSelector();
1.30      matthew   330:     $Str .= '</td></tr>'."\n";
                    331:     $Str .= '</table>'."\n";
1.55      matthew   332:     $Str .= '<input type="submit" value="Generate Chart" />';
1.59      matthew   333:     $Str .= '&nbsp;'x5;
                    334:     $Str .= '<input type="submit" name="selectstudent" '.
                    335:                                   'value="Select One Student" />';
                    336:     $Str .= '&nbsp;'x5;
                    337:     $Str .= '<input type="submit" name="ClearCache" value="Clear Caches" />';
1.61    ! www       338:     $Str .= '&nbsp;'x5;
        !           339:     $Str .= '<br />';
1.4       stredwic  340:     return $Str;
1.1       stredwic  341: }
1.30      matthew   342: 
                    343: #######################################################
                    344: #######################################################
                    345: 
                    346: =pod
                    347: 
1.31      matthew   348: =item &CreateAndParseOutputSelector()
1.30      matthew   349: 
                    350: =cut
                    351: 
                    352: #######################################################
                    353: #######################################################
1.32      matthew   354: my @OutputOptions = 
                    355:     ({ name  => 'HTML, with links',
                    356:        value => 'html, with links',
1.33      matthew   357:        description => 'Output HTML with each symbol linked to the problem '.
1.35      matthew   358: 	   'which generated it.',
                    359:        mode => 'html',
                    360:        show_links => 'yes',
                    361:        },
1.47      matthew   362:      { name  => 'HTML, with all links',
                    363:        value => 'html, with all links',
                    364:        description => 'Output HTML with each symbol linked to the problem '.
                    365: 	   'which generated it.  '.
                    366:            'This includes links for unattempted problems.',
                    367:        mode => 'html',
                    368:        show_links => 'all',
                    369:        },
1.32      matthew   370:      { name  => 'HTML, without links',
                    371:        value => 'html, without links',
1.33      matthew   372:        description => 'Output HTML.  By not including links, the size of the'.
                    373: 	   ' web page is greatly reduced.  If your browser crashes on the '.
1.35      matthew   374: 	   'full display, try this.',
                    375:        mode => 'html',
                    376:        show_links => 'no',
                    377:            },
1.54      matthew   378:      { name  => 'Excel',
                    379:        value => 'excel',
                    380:        description => 'Output an Excel file (compatable with Excel 95).',
1.35      matthew   381:        mode => 'excel',
                    382:        show_links => 'no',
1.54      matthew   383:    },
                    384:      { name  => 'CSV',
                    385:        value => 'csv',
                    386:        description => 'Output a comma seperated values file suitable for '.
1.57      matthew   387:            'import into a spreadsheet program.  Using this method as opposed '.
                    388:            'to Excel output allows you to organize your data before importing'.
                    389:            ' it into a spreadsheet program.',
1.35      matthew   390:        mode => 'csv',
                    391:        show_links => 'no',
                    392:            },
1.32      matthew   393:      );
                    394: 
1.33      matthew   395: sub OutputDescriptions {
                    396:     my $Str = '';
1.58      matthew   397:     $Str .= "<h2>Output Formats</h2>\n";
1.33      matthew   398:     $Str .= "<dl>\n";
                    399:     foreach my $outputmode (@OutputOptions) {
                    400: 	$Str .="    <dt>".$outputmode->{'name'}."</dt>\n";
                    401: 	$Str .="        <dd>".$outputmode->{'description'}."</dd>\n";
                    402:     }
                    403:     $Str .= "</dl>\n";
                    404:     return $Str;
                    405: }
                    406: 
1.31      matthew   407: sub CreateAndParseOutputSelector {
                    408:     my $Str = '';
1.44      matthew   409:     my $elementname = 'chartoutputmode';
1.31      matthew   410:     #
                    411:     # Format for output options is 'mode, restrictions';
1.50      matthew   412:     my $selected = 'html, without links';
1.31      matthew   413:     if (exists($ENV{'form.'.$elementname})) {
                    414:         if (ref($ENV{'form.'.$elementname} eq 'ARRAY')) {
                    415:             $selected = $ENV{'form.'.$elementname}->[0];
                    416:         } else {
                    417:             $selected = $ENV{'form.'.$elementname};
                    418:         }
                    419:     }
                    420:     #
                    421:     # Set package variables describing output mode
                    422:     $show_links  = 'no';
                    423:     $output_mode = 'html';
1.35      matthew   424:     foreach my $option (@OutputOptions) {
                    425:         next if ($option->{'value'} ne $selected);
                    426:         $output_mode = $option->{'mode'};
                    427:         $show_links  = $option->{'show_links'};
1.31      matthew   428:     }
1.35      matthew   429: 
1.31      matthew   430:     #
                    431:     # Build the form element
                    432:     $Str = qq/<select size="5" name="$elementname">/;
1.32      matthew   433:     foreach my $option (@OutputOptions) {
                    434:         $Str .= "\n".'    <option value="'.$option->{'value'}.'"';
                    435:         $Str .= " selected " if ($option->{'value'} eq $selected);
                    436:         $Str .= ">".$option->{'name'}."<\/option>";
1.31      matthew   437:     }
                    438:     $Str .= "\n</select>";
                    439:     return $Str;
                    440: }
1.30      matthew   441: 
1.54      matthew   442: ##
                    443: ## Data selector stuff
                    444: ##
                    445: my @OutputDataOptions =
1.57      matthew   446:     (
                    447:      { name  => 'Scores',
                    448:        base  => 'scores',
                    449:        value => 'scores',
                    450:        shortdesc => 'Score on each Problem Part',
                    451:        longdesc =>'The students score on each problem part, computed as'.
                    452:            'the part weight * part awarded',
                    453:        },
                    454:      { name  => 'Scores Sum',
                    455:        base  => 'scores',
                    456:        value => 'sum only',
                    457:        shortdesc => 'Sum of Scores on each Problem Part',
                    458:        longdesc =>'The total of the scores of the student on each problem'.
                    459:            ' part in the sequences or folders selected.',
                    460:        },
                    461:      { name  => 'Scores Sum & Maximums',
                    462:        base  => 'scores',
                    463:        value => 'sum and total',
                    464:        shortdesc => 'Total Score and Maximum Possible for each '.
                    465:            'Sequence or Folder',
                    466:        longdesc => 'The score of each student as well as the '.
                    467:            ' maximum possible on each Sequence or Folder.',
                    468:        },
                    469:      { name  => 'Scores Summary Table Only',
                    470:        base  => 'scores',
                    471:        value => 'final table scores',
                    472:        shortdesc => 'Summary of Scores',
                    473:        longdesc  => 'The average score on each sequence or folder for the '.
                    474:            'selected students.',
                    475:        },
                    476:      { name  =>'Tries',
                    477:        base  =>'tries',
                    478:        value => 'tries',
                    479:        shortdesc => 'Number of Tries before success on each Problem Part',
                    480:        longdesc =>'The number of tries before success on each problem part.',
                    481:        },
                    482:      { name  =>'Parts Correct',
                    483:        base  =>'tries',
                    484:        value => 'parts correct',
                    485:        shortdesc => 'Number of Problem Parts completed successfully.',
                    486:        longdesc => 'The Number of Problem Parts completed successfully'.
                    487:            ' on each sequence or folder.',
                    488:        },
                    489:      { name  =>'Parts Correct & Maximums',
                    490:        base  =>'tries',
                    491:        value => 'parts correct total',
                    492:        shortdesc => 'Number of Problem Parts completed successfully.',
                    493:        longdesc => 'The Number of Problem Parts completed successfully and '.
                    494:            'the maximum possible for each student',
                    495:        },
                    496:      { name  => 'Parts Summary Table Only',
                    497:        base  => 'tries',
                    498:        value => 'final table parts',
                    499:        shortdesc => 'Summary of Parts Correct',
                    500:        longdesc  => 'A summary table of the average number of problem parts '.
                    501:            'students were able to get correct on each sequence.',
                    502:        },
                    503:      );
                    504: 
                    505: sub HTMLifyOutputDataDescriptions {
                    506:     my $Str = '';
1.58      matthew   507:     $Str .= "<h2>Output Data</h2>\n";
1.57      matthew   508:     $Str .= "<dl>\n";
                    509:     foreach my $option (@OutputDataOptions) {
                    510:         $Str .= '    <dt>'.$option->{'name'}.'</dt>';
                    511:         $Str .= '<dd>'.$option->{'longdesc'}.'</dd>'."\n";
                    512:     }
                    513:     $Str .= "</dl>\n";
                    514:     return $Str;
                    515: }
1.54      matthew   516: 
                    517: sub CreateAndParseOutputDataSelector {
                    518:     my $Str = '';
                    519:     my $elementname = 'chartoutputdata';
                    520:     #
                    521:     my $selected = 'scores';
                    522:     if (exists($ENV{'form.'.$elementname})) {
                    523:         if (ref($ENV{'form.'.$elementname} eq 'ARRAY')) {
                    524:             $selected = $ENV{'form.'.$elementname}->[0];
                    525:         } else {
                    526:             $selected = $ENV{'form.'.$elementname};
                    527:         }
                    528:     }
                    529:     #
                    530:     $data = 'scores';
                    531:     foreach my $option (@OutputDataOptions) {
                    532:         if ($option->{'value'} eq $selected) {
                    533:             $data = $option->{'value'};
                    534:             $base = $option->{'base'};
                    535:             $datadescription = $option->{'shortdesc'};
                    536:         }
                    537:     }
                    538:     #
                    539:     # Build the form element
                    540:     $Str = qq/<select size="5" name="$elementname">/;
                    541:     foreach my $option (@OutputDataOptions) {
                    542:         $Str .= "\n".'    <option value="'.$option->{'value'}.'"';
                    543:         $Str .= " selected " if ($option->{'value'} eq $data);
                    544:         $Str .= ">".$option->{'name'}."<\/option>";
                    545:     }
                    546:     $Str .= "\n</select>";
                    547:     return $Str;
                    548: 
                    549: }
                    550: 
1.28      matthew   551: #######################################################
                    552: #######################################################
1.1       stredwic  553: 
1.28      matthew   554: =pod
                    555: 
1.31      matthew   556: =head2 HTML output routines
1.28      matthew   557: 
1.31      matthew   558: =item &html_initialize($r)
1.28      matthew   559: 
1.31      matthew   560: Create labels for the columns of student data to show.
1.28      matthew   561: 
1.31      matthew   562: =item &html_outputstudent($r,$student)
1.28      matthew   563: 
1.31      matthew   564: Return a line of the chart for a student.
1.28      matthew   565: 
1.31      matthew   566: =item &html_finish($r)
1.28      matthew   567: 
                    568: =cut
                    569: 
                    570: #######################################################
                    571: #######################################################
1.31      matthew   572: {
                    573:     my $padding;
                    574:     my $count;
                    575: 
1.39      matthew   576:     my $nodata_count; # The number of students for which there is no data
                    577:     my %prog_state;   # progress state used by loncommon PrgWin routines
                    578: 
1.31      matthew   579: sub html_initialize {
                    580:     my ($r) = @_;
1.30      matthew   581:     #
                    582:     $padding = ' 'x3;
1.35      matthew   583:     $count = 0;
1.39      matthew   584:     $nodata_count = 0;
1.30      matthew   585:     #
1.38      matthew   586:     $r->print("<h3>".$ENV{'course.'.$ENV{'request.course.id'}.'.description'}.
                    587:               "&nbsp;&nbsp;".localtime(time)."</h3>");
1.39      matthew   588: 
1.55      matthew   589:     if ($data !~ /^final table/) {
                    590:         $r->print("<h3>".$datadescription."</h3>");        
                    591:     }
1.39      matthew   592:     #
                    593:     # Set up progress window for 'final table' display only
1.54      matthew   594:     if ($data =~ /^final table/) {
1.39      matthew   595:         my $studentcount = scalar(@Apache::lonstatistics::Students); 
                    596:         %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
                    597:             ($r,'Summary Table Status',
                    598:              'Summary Table Compilation Progress', $studentcount);
                    599:     }
1.31      matthew   600:     my $Str = "<pre>\n";
1.30      matthew   601:     # First, the @StudentData fields need to be listed
1.31      matthew   602:     my @to_show = &get_student_fields_to_show();
1.30      matthew   603:     foreach my $field (@to_show) {
                    604:         my $title=$Apache::lonstatistics::StudentData{$field}->{'title'};
                    605:         my $base =$Apache::lonstatistics::StudentData{$field}->{'base_width'};
                    606:         my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
                    607:         $Str .= $title.' 'x($width-$base).$padding;
                    608:     }
                    609:     # Now the selected sequences need to be listed
1.40      matthew   610:     foreach my $sequence (&Apache::lonstatistics::Sequences_with_Assess()){
1.31      matthew   611:         my $title = $sequence->{'title'};
                    612:         my $base  = $sequence->{'base_width'};
                    613:         my $width = $sequence->{'width'};
                    614:         $Str .= $title.' 'x($width-$base).$padding;
1.30      matthew   615:     }
1.54      matthew   616:     $Str .= "total</pre>\n";
1.31      matthew   617:     $Str .= "<pre>";
1.39      matthew   618:     #
                    619:     # Check for suppression of output
1.54      matthew   620:     if ($data =~ /^final table/) {
1.39      matthew   621:         $Str = '';
                    622:     }
1.31      matthew   623:     $r->print($Str);
                    624:     $r->rflush();
                    625:     return;
1.30      matthew   626: }
                    627: 
1.31      matthew   628: sub html_outputstudent {
                    629:     my ($r,$student) = @_;
1.2       stredwic  630:     my $Str = '';
1.35      matthew   631:     #
1.55      matthew   632:     if($count++ % 5 == 0 && $count > 0 && $data !~ /^final table/) {
1.35      matthew   633:         $r->print("</pre><pre>");
                    634:     }
1.30      matthew   635:     # First, the @StudentData fields need to be listed
1.31      matthew   636:     my @to_show = &get_student_fields_to_show();
1.30      matthew   637:     foreach my $field (@to_show) {
                    638:         my $title=$student->{$field};
1.31      matthew   639:         my $base = length($title);
1.30      matthew   640:         my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
                    641:         $Str .= $title.' 'x($width-$base).$padding;
                    642:     }
                    643:     # Get ALL the students data
                    644:     my %StudentsData;
                    645:     my @tmp = &Apache::loncoursedata::get_current_state
                    646:         ($student->{'username'},$student->{'domain'},undef,
                    647:          $ENV{'request.course.id'});
                    648:     if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
                    649:         %StudentsData = @tmp;
                    650:     }
                    651:     if (scalar(@tmp) < 1) {
1.39      matthew   652:         $nodata_count++;
1.54      matthew   653:         return if ($data =~ /^final table/);
1.30      matthew   654:         $Str .= '<font color="blue">No Course Data</font>'."\n";
1.31      matthew   655:         $r->print($Str);
                    656:         $r->rflush();
                    657:         return;
1.30      matthew   658:     }
                    659:     #
                    660:     # By sequence build up the data
                    661:     my $studentstats;
1.31      matthew   662:     my $PerformanceStr = '';
1.40      matthew   663:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1.54      matthew   664:         my ($performance,$performance_length,$score,$seq_max,$rawdata);
                    665:         if ($base eq 'tries') {
                    666:             ($performance,$performance_length,$score,$seq_max,$rawdata) =
                    667:                 &StudentTriesOnSequence($student,\%StudentsData,
                    668:                                         $seq,$show_links);
                    669:         } else {
                    670:             ($performance,$performance_length,$score,$seq_max,$rawdata) =
                    671:                 &StudentPerformanceOnSequence($student,\%StudentsData,
                    672:                                               $seq,$show_links);
                    673:         }
                    674:         my $ratio = sprintf("%3d",$score).'/'.sprintf("%3d",$seq_max);
1.31      matthew   675:         #
1.54      matthew   676:         if ($data eq 'sum and total' || $data eq 'parts correct total') {
                    677:             $performance  = $ratio;
                    678:             $performance .= ' 'x($seq->{'width'}-length($ratio));
                    679:         } elsif ($data eq 'sum only' || $data eq 'parts correct') {
                    680:             $performance  = $score;
                    681:             $performance .= ' 'x($seq->{'width'}-length($score));
1.31      matthew   682:         } else {
                    683:             # Pad with extra spaces
1.51      matthew   684:             $performance .= ' 'x($seq->{'width'}-$performance_length-
1.31      matthew   685:                                  length($ratio)
                    686:                                  ).$ratio;
1.30      matthew   687:         }
1.31      matthew   688:         #
                    689:         $Str .= $performance.$padding;
                    690:         #
                    691:         $studentstats->{$seq->{'symb'}}->{'score'}= $score;
                    692:         $studentstats->{$seq->{'symb'}}->{'max'}  = $seq_max;
1.30      matthew   693:     }
                    694:     #
                    695:     # Total it up and store the statistics info.
                    696:     my ($score,$max) = (0,0);
                    697:     while (my ($symb,$seq_stats) = each (%{$studentstats})) {
                    698:         $Statistics->{$symb}->{'score'} += $seq_stats->{'score'};
1.54      matthew   699:         if ($Statistics->{$symb}->{'max'} < $seq_stats->{'max'}) {
                    700:             $Statistics->{$symb}->{'max'} = $seq_stats->{'max'};
                    701:         }
1.30      matthew   702:         $score += $seq_stats->{'score'};
                    703:         $max   += $seq_stats->{'max'};
                    704:     }
1.31      matthew   705:     $Str .= ' '.' 'x(length($max)-length($score)).$score.'/'.$max;
1.30      matthew   706:     $Str .= " \n";
1.39      matthew   707:     #
                    708:     # Check for suppressed output and update the progress window if so...
1.54      matthew   709:     if ($data =~ /^final table/) {
1.39      matthew   710:         $Str = '';
                    711:         &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
                    712:                                                  'last student');
                    713:     }
                    714:     #
1.31      matthew   715:     $r->print($Str);
                    716:     #
                    717:     $r->rflush();
                    718:     return;
1.30      matthew   719: }    
1.2       stredwic  720: 
1.31      matthew   721: sub html_finish {
                    722:     my ($r) = @_;
1.39      matthew   723:     #
                    724:     # Check for suppressed output and close the progress window if so
1.54      matthew   725:     if ($data =~ /^final table/) {
1.39      matthew   726:         &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
                    727:     } else {
                    728:         $r->print("</pre>\n"); 
                    729:     }
1.49      matthew   730:     if ($single_student_mode) {
                    731:         $r->print(&SingleStudentTotal());
                    732:     } else {
                    733:         $r->print(&StudentAverageTotal());
                    734:     }
1.31      matthew   735:     $r->rflush();
                    736:     return;
                    737: }
                    738: 
1.39      matthew   739: sub StudentAverageTotal {
                    740:     my $Str = "<h3>Summary Tables</h3>\n";
                    741:     my $num_students = scalar(@Apache::lonstatistics::Students);
                    742:     my $total_ave = 0;
                    743:     my $total_max = 0;
                    744:     $Str .= '<table border=2 cellspacing="1">'."\n";
                    745:     $Str .= "<tr><th>Title</th><th>Average</th><th>Maximum</th></tr>\n";
1.40      matthew   746:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1.42      matthew   747:         my $ave;
                    748:         if ($num_students > $nodata_count) {
                    749:             $ave = int(100*($Statistics->{$seq->{'symb'}}->{'score'}/
                    750:                             ($num_students-$nodata_count)))/100;
                    751:         } else {
                    752:             $ave = 0;
                    753:         }
1.39      matthew   754:         $total_ave += $ave;
1.54      matthew   755:         my $max = $Statistics->{$seq->{'symb'}}->{'max'};
1.39      matthew   756:         $total_max += $max;
                    757:         if ($ave == 0) {
                    758:             $ave = "0.00";
                    759:         }
                    760:         $ave .= '&nbsp;';
                    761:         $max .= '&nbsp;&nbsp;&nbsp;';
                    762:         $Str .= '<tr><td>'.$seq->{'title'}.'</td>'.
                    763:             '<td align="right">'.$ave.'</td>'.
                    764:                 '<td align="right">'.$max.'</td></tr>'."\n";
                    765:     }
                    766:     $total_ave = int(100*$total_ave)/100; # only two digit
                    767:     $Str .= "</table>\n";
                    768:     $Str .= '<table border=2 cellspacing="1">'."\n";
                    769:     $Str .= '<tr><th>Number of Students</th><th>Average</th>'.
                    770:         "<th>Maximum</th></tr>\n";
                    771:     $Str .= '<tr><td>'.($num_students-$nodata_count).'</td>'.
                    772:         '<td>'.$total_ave.'</td><td>'.$total_max.'</td>';
                    773:     $Str .= "</table>\n";
                    774:     return $Str;
                    775: }
                    776: 
1.49      matthew   777: sub SingleStudentTotal {
                    778:     my $student = &Apache::lonstatistics::current_student();
1.52      matthew   779:     my $Str = "<h3>Summary table for ".$student->{'fullname'}." ".
                    780:         $student->{'username'}.'@'.$student->{'domain'}."</h3>\n";
1.49      matthew   781:     $Str .= '<table border=2 cellspacing="1">'."\n";
                    782:     $Str .= 
                    783:         "<tr><th>Sequence or Folder</th><th>Score</th><th>Maximum</th></tr>\n";
                    784:     my $total = 0;
                    785:     my $total_max = 0;
                    786:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
                    787:         my $value = $Statistics->{$seq->{'symb'}}->{'score'};
                    788:         my $max = $Statistics->{$seq->{'symb'}}->{'max'};
                    789:         $Str .= '<tr><td>'.$seq->{'title'}.'</td>'.
                    790:             '<td align="right">'.$value.'</td>'.
                    791:                 '<td align="right">'.$max.'</td></tr>'."\n";
                    792:         $total += $value;
                    793:         $total_max +=$max;
                    794:     }
                    795:     $Str .= '<tr><td><b>Total</b></td>'.
                    796:         '<td align="right">'.$total.'</td>'.
                    797:         '<td align="right">'.$total_max."</td></tr>\n";
                    798:     $Str .= "</table>\n";
                    799:     return $Str;
                    800: }
                    801: 
1.31      matthew   802: }
                    803: 
                    804: #######################################################
                    805: #######################################################
                    806: 
                    807: =pod
                    808: 
                    809: =head2 EXCEL subroutines
                    810: 
                    811: =item &excel_initialize($r)
                    812: 
                    813: =item &excel_outputstudent($r,$student)
                    814: 
                    815: =item &excel_finish($r)
                    816: 
                    817: =cut
                    818: 
                    819: #######################################################
                    820: #######################################################
                    821: {
                    822: 
                    823: my $excel_sheet;
1.32      matthew   824: my $excel_workbook;
                    825: 
                    826: my $filename;
                    827: my $rows_output;
                    828: my $cols_output;
                    829: 
1.36      matthew   830: my %prog_state; # progress window state
1.54      matthew   831: my $request_aborted;
1.31      matthew   832: 
                    833: sub excel_initialize {
                    834:     my ($r) = @_;
                    835:     #
1.54      matthew   836:     $request_aborted = undef;
                    837:     my $total_columns = scalar(&get_student_fields_to_show());
                    838:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
                    839:         # Add 2 because we need a 'sum' and 'total' column for each
                    840:         $total_columns += $seq->{'num_assess_parts'}+2;
                    841:     }
                    842:     if ($data eq 'tries' && $total_columns > 255) {
                    843:         $r->print(<<END);
                    844: <h2>Unable to Complete Request</h2>
                    845: <p>
                    846: LON-CAPA is unable to produce your Excel spreadsheet because your selections
                    847: will result in more than 255 columns.  Excel allows only 255 columns in a
                    848: spreadsheet.
                    849: </p><p>
                    850: You may consider reducing the number of <b>Sequences or Folders</b> you
                    851: have selected.  
                    852: </p><p>
                    853: LON-CAPA can produce <b>CSV</b> files of this data or Excel files of the
                    854: summary data (<b>Parts Correct</b> or <b>Parts Correct & Totals</b>).
                    855: </p>
                    856: END
                    857:        $request_aborted = 1;
                    858:     }
                    859:     if ($data eq 'scores' && $total_columns > 255) {
                    860:         $r->print(<<END);
                    861: <h2>Unable to Complete Request</h2>
                    862: <p>
                    863: LON-CAPA is unable to produce your Excel spreadsheet because your selections
                    864: will result in more than 255 columns.  Excel allows only 255 columns in a
                    865: spreadsheet.
                    866: </p><p>
                    867: You may consider reducing the number of <b>Sequences or Folders</b> you
                    868: have selected.  
                    869: </p><p>
                    870: LON-CAPA can produce <b>CSV</b> files of this data or Excel files of the
                    871: summary data (<b>Scores Sum</b> or <b>Scores Sum & Totals</b>).
                    872: </p>
                    873: END
                    874:        $request_aborted = 1;
                    875:     }
                    876:     if ($data =~ /^final table/) {
                    877:         $r->print(<<END);
                    878: <h2>Unable to Complete Request</h2>
                    879: <p>
                    880: The <b>Summary Table (Scores)</b> option is not available for non-HTML output.
                    881: </p>
                    882: END
                    883:        $request_aborted = 1;
                    884:     }
                    885:     return if ($request_aborted);
                    886:     #
1.32      matthew   887:     $filename = '/prtspool/'.
1.31      matthew   888:         $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
                    889:             time.'_'.rand(1000000000).'.xls';
1.32      matthew   890:     #
                    891:     $excel_workbook = undef;
                    892:     $excel_sheet = undef;
                    893:     #
                    894:     $rows_output = 0;
                    895:     $cols_output = 0;
                    896:     #
                    897:     # Create sheet
                    898:     $excel_workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
                    899:     #
                    900:     # Check for errors
                    901:     if (! defined($excel_workbook)) {
1.31      matthew   902:         $r->log_error("Error creating excel spreadsheet $filename: $!");
                    903:         $r->print("Problems creating new Excel file.  ".
                    904:                   "This error has been logged.  ".
                    905:                   "Please alert your LON-CAPA administrator");
1.32      matthew   906:         return ;
1.31      matthew   907:     }
                    908:     #
                    909:     # The excel spreadsheet stores temporary data in files, then put them
                    910:     # together.  If needed we should be able to disable this (memory only).
                    911:     # The temporary directory must be specified before calling 'addworksheet'.
                    912:     # File::Temp is used to determine the temporary directory.
1.32      matthew   913:     $excel_workbook->set_tempdir($Apache::lonnet::tmpdir);
                    914:     #
                    915:     # Add a worksheet
1.33      matthew   916:     my $sheetname = $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
                    917:     if (length($sheetname) > 31) {
                    918:         $sheetname = substr($sheetname,0,31);
                    919:     }
                    920:     $excel_sheet = $excel_workbook->addworksheet($sheetname);
1.32      matthew   921:     #
1.34      matthew   922:     # Put the course description in the header
                    923:     $excel_sheet->write($rows_output,$cols_output++,
                    924:                    $ENV{'course.'.$ENV{'request.course.id'}.'.description'});
                    925:     $cols_output += 3;
                    926:     #
                    927:     # Put a description of the sections listed
                    928:     my $sectionstring = '';
                    929:     my @Sections = @Apache::lonstatistics::SelectedSections;
                    930:     if (scalar(@Sections) > 1) {
                    931:         if (scalar(@Sections) > 2) {
                    932:             my $last = pop(@Sections);
                    933:             $sectionstring = "Sections ".join(', ',@Sections).', and '.$last;
                    934:         } else {
                    935:             $sectionstring = "Sections ".join(' and ',@Sections);
                    936:         }
                    937:     } else {
                    938:         if ($Sections[0] eq 'all') {
                    939:             $sectionstring = "All sections";
                    940:         } else {
                    941:             $sectionstring = "Section ".$Sections[0];
                    942:         }
                    943:     }
                    944:     $excel_sheet->write($rows_output,$cols_output++,$sectionstring);
                    945:     $cols_output += scalar(@Sections);
                    946:     #
                    947:     # Put the date in there too
1.54      matthew   948:     $excel_sheet->write($rows_output++,$cols_output++,
1.34      matthew   949:                         'Compiled on '.localtime(time));
                    950:     #
1.54      matthew   951:     $cols_output = 0;
                    952:     $excel_sheet->write($rows_output++,$cols_output++,$datadescription);
                    953:     #
                    954:     if ($data eq 'tries' || $data eq 'scores') {
                    955:         $rows_output++;
                    956:     }
1.34      matthew   957:     #
1.32      matthew   958:     # Add the student headers
1.34      matthew   959:     $cols_output = 0;
1.32      matthew   960:     foreach my $field (&get_student_fields_to_show()) {
1.34      matthew   961:         $excel_sheet->write($rows_output,$cols_output++,$field);
1.32      matthew   962:     }
1.54      matthew   963:     my $row_offset = 0;
                    964:     if ($data eq 'tries' || $data eq 'scores') {
                    965:         $row_offset = -1;
                    966:     }
1.32      matthew   967:     #
1.54      matthew   968:     # Add the remaining column headers
1.40      matthew   969:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1.54      matthew   970:         $excel_sheet->write($rows_output+$row_offset,
                    971:                             $cols_output,$seq->{'title'});
                    972:         if ($data eq 'tries' || $data eq 'scores') {
                    973:             foreach my $res (@{$seq->{'contents'}}) {
                    974:                 next if ($res->{'type'} ne 'assessment');
                    975:                 if (scalar(@{$res->{'parts'}}) > 1) {
                    976:                     foreach my $part (@{$res->{'parts'}}) {
                    977:                         $excel_sheet->write($rows_output,
                    978:                                             $cols_output++,
                    979:                                             $res->{'title'}.' part '.$part);
                    980:                     }
                    981:                 } else {
                    982:                     $excel_sheet->write($rows_output,
                    983:                                         $cols_output++,
                    984:                                         $res->{'title'});
                    985:                 }
                    986:             }
                    987:             $excel_sheet->write($rows_output,$cols_output++,'score');
                    988:             $excel_sheet->write($rows_output,$cols_output++,'maximum');
                    989:         } elsif ($data eq 'sum and total' || $data eq 'parts correct total') {
1.34      matthew   990:             $excel_sheet->write($rows_output+1,$cols_output,'score');
                    991:             $excel_sheet->write($rows_output+1,$cols_output+1,'maximum');
1.32      matthew   992:             $cols_output += 2;
                    993:         } else {
                    994:             $cols_output++;
                    995:         }
                    996:     }
                    997:     #
                    998:     # Bookkeeping
1.54      matthew   999:     if ($data eq 'sum and total' || $data eq 'parts correct total') {
1.34      matthew  1000:         $rows_output += 2;
1.32      matthew  1001:     } else {
1.34      matthew  1002:         $rows_output += 1;
1.45      matthew  1003:     }
                   1004:     #
                   1005:     # Output a row for MAX
1.54      matthew  1006:     $cols_output = 0;
                   1007:     foreach my $field (&get_student_fields_to_show()) {
                   1008:         if ($field eq 'username' || $field eq 'fullname' || 
                   1009:             $field eq 'id') {
                   1010:             $excel_sheet->write($rows_output,$cols_output++,'Maximum');
                   1011:         } else {
                   1012:             $excel_sheet->write($rows_output,$cols_output++,'');
                   1013:         }
                   1014:     }
                   1015:     #
                   1016:     # Add the maximums for each sequence or assessment
                   1017:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
                   1018:         my $weight;
                   1019:         my $max = 0;
                   1020:         foreach my $resource (@{$seq->{'contents'}}) {
                   1021:             next if ($resource->{'type'} ne 'assessment');
                   1022:             foreach my $part (@{$resource->{'parts'}}) {
                   1023:                 $weight = 1;
                   1024:                 if ($base eq 'scores') {
                   1025:                     $weight = &Apache::lonnet::EXT
                   1026:                         ('resource.'.$part.'.weight',$resource->{'symb'},
                   1027:                          undef,undef,undef);
                   1028:                     if (!defined($weight) || ($weight eq '')) { 
                   1029:                         $weight=1;
                   1030:                     }
                   1031:                 }
                   1032:                 if ($data eq 'scores') {
                   1033:                     $excel_sheet->write($rows_output,$cols_output++,$weight);
                   1034:                 } elsif ($data eq 'tries') {
                   1035:                     $excel_sheet->write($rows_output,$cols_output++,'');
                   1036:                 }
                   1037:                 $max += $weight;
1.45      matthew  1038:             }
1.54      matthew  1039:         } 
                   1040:         if (! ($data eq 'sum only' || $data eq 'parts correct')) {
                   1041:             $excel_sheet->write($rows_output,$cols_output++,'');
1.45      matthew  1042:         }
1.54      matthew  1043:         $excel_sheet->write($rows_output,$cols_output++,$max);
1.32      matthew  1044:     }
1.54      matthew  1045:     $rows_output++;
1.32      matthew  1046:     #
                   1047:     # Let the user know what we are doing
                   1048:     my $studentcount = scalar(@Apache::lonstatistics::Students); 
                   1049:     $r->print("<h1>Compiling Excel spreadsheet for ".
                   1050:               $studentcount.' student');
                   1051:     $r->print('s') if ($studentcount > 1);
                   1052:     $r->print("</h1>\n");
                   1053:     $r->rflush();
1.31      matthew  1054:     #
1.36      matthew  1055:     # Initialize progress window
                   1056:     %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
                   1057:         ($r,'Excel File Compilation Status',
                   1058:          'Excel File Compilation Progress', $studentcount);
                   1059:     #
1.31      matthew  1060:     return;
                   1061: }
                   1062: 
                   1063: sub excel_outputstudent {
                   1064:     my ($r,$student) = @_;
1.54      matthew  1065:     return if ($request_aborted);
1.32      matthew  1066:     return if (! defined($excel_sheet));
                   1067:     $cols_output=0;
                   1068:     #
                   1069:     # Write out student data
                   1070:     my @to_show = &get_student_fields_to_show();
                   1071:     foreach my $field (@to_show) {
                   1072:         $excel_sheet->write($rows_output,$cols_output++,$student->{$field});
                   1073:     }
                   1074:     #
                   1075:     # Get student assessment data
                   1076:     my %StudentsData;
                   1077:     my @tmp = &Apache::loncoursedata::get_current_state($student->{'username'},
                   1078:                                                         $student->{'domain'},
                   1079:                                                         undef,
                   1080:                                                    $ENV{'request.course.id'});
                   1081:     if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
                   1082:         %StudentsData = @tmp;
                   1083:     }
                   1084:     #
                   1085:     # Write out sequence scores and totals data
1.40      matthew  1086:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1.54      matthew  1087:         my ($performance,$performance_length,$score,$seq_max,$rawdata);
                   1088:         if ($base eq 'tries') {
                   1089:             ($performance,$performance_length,$score,$seq_max,$rawdata) =
                   1090:                 &StudentTriesOnSequence($student,\%StudentsData,
                   1091:                                         $seq,'no');
                   1092:         } else {
                   1093:             ($performance,$performance_length,$score,$seq_max,$rawdata) =
                   1094:                 &StudentPerformanceOnSequence($student,\%StudentsData,
                   1095:                                               $seq,'no');
                   1096:         }
                   1097:         if ($data eq 'tries' || $data eq 'scores') {
                   1098:             foreach my $value (@$rawdata) {
                   1099:                 $excel_sheet->write($rows_output,$cols_output++,$value);
                   1100:             }
                   1101:             $excel_sheet->write($rows_output,$cols_output++,$score);
                   1102:             $excel_sheet->write($rows_output,$cols_output++,$seq_max);
                   1103:         } elsif ($data eq 'sum and total' || $data eq 'sum only' || 
                   1104:             $data eq 'parts correct' || $data eq 'parts correct total') {
1.32      matthew  1105:             $excel_sheet->write($rows_output,$cols_output++,$score);
                   1106:         }
1.54      matthew  1107:         if ($data eq 'sum and total' || $data eq 'parts correct total') {
1.32      matthew  1108:             $excel_sheet->write($rows_output,$cols_output++,$seq_max);
                   1109:         }
                   1110:     }
                   1111:     #
                   1112:     # Bookkeeping
                   1113:     $rows_output++; 
                   1114:     $cols_output=0;
                   1115:     #
1.36      matthew  1116:     # Update the progress window
                   1117:     &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
1.32      matthew  1118:     return;
1.31      matthew  1119: }
                   1120: 
                   1121: sub excel_finish {
                   1122:     my ($r) = @_;
1.54      matthew  1123:     return if ($request_aborted);
1.32      matthew  1124:     return if (! defined($excel_sheet));
                   1125:     #
                   1126:     # Write the excel file
                   1127:     $excel_workbook->close();
                   1128:     my $c = $r->connection();
                   1129:     #
                   1130:     return if($c->aborted());
                   1131:     #
1.36      matthew  1132:     # Close the progress window
                   1133:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
                   1134:     #
1.32      matthew  1135:     # Tell the user where to get their excel file
1.36      matthew  1136:     $r->print('<br />'.
1.32      matthew  1137:               '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n");
                   1138:     $r->rflush();
                   1139:     return;
1.31      matthew  1140: }
                   1141: 
                   1142: }
1.30      matthew  1143: #######################################################
                   1144: #######################################################
                   1145: 
                   1146: =pod
                   1147: 
1.31      matthew  1148: =head2 CSV output routines
                   1149: 
                   1150: =item &csv_initialize($r)
                   1151: 
                   1152: =item &csv_outputstudent($r,$student)
                   1153: 
                   1154: =item &csv_finish($r)
1.30      matthew  1155: 
                   1156: =cut
                   1157: 
                   1158: #######################################################
                   1159: #######################################################
1.31      matthew  1160: {
                   1161: 
1.37      matthew  1162: my $outputfile;
                   1163: my $filename;
1.54      matthew  1164: my $request_aborted;
1.37      matthew  1165: my %prog_state; # progress window state
                   1166: 
1.31      matthew  1167: sub csv_initialize{
                   1168:     my ($r) = @_;
1.37      matthew  1169:     # 
                   1170:     # Clean up
                   1171:     $filename = undef;
                   1172:     $outputfile = undef;
                   1173:     undef(%prog_state);
                   1174:     #
1.54      matthew  1175:     # Deal with unimplemented requests
                   1176:     $request_aborted = undef;
                   1177:     if ($data =~ /final table/) {
                   1178:         $r->print(<<END);
                   1179: <h2>Unable to Complete Request</h2>
                   1180: <p>
                   1181: The <b>Summary Table (Scores)</b> option is not available for non-HTML output.
                   1182: </p>
                   1183: END
                   1184:        $request_aborted = 1;
                   1185:     }
                   1186:     return if ($request_aborted);
                   1187: 
                   1188:     #
1.37      matthew  1189:     # Open a file
                   1190:     $filename = '/prtspool/'.
                   1191:         $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
                   1192:             time.'_'.rand(1000000000).'.csv';
                   1193:     unless ($outputfile = Apache::File->new('>/home/httpd'.$filename)) {
                   1194:         $r->log_error("Couldn't open $filename for output $!");
                   1195:         $r->print("Problems occured in writing the csv file.  ".
                   1196:                   "This error has been logged.  ".
                   1197:                   "Please alert your LON-CAPA administrator.");
                   1198:         $outputfile = undef;
                   1199:     }
1.38      matthew  1200:     #
                   1201:     # Datestamp
                   1202:     my $description = $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
                   1203:     print $outputfile '"'.&Apache::loncommon::csv_translate($description).'",'.
                   1204:         '"'.&Apache::loncommon::csv_translate(scalar(localtime(time))).'"'.
                   1205:             "\n";
1.37      matthew  1206:     #
                   1207:     # Print out the headings
                   1208:     my $Str = '';
                   1209:     my $Str2 = undef;
                   1210:     foreach my $field (&get_student_fields_to_show()) {
1.54      matthew  1211:         if ($data eq 'sum only') {
1.37      matthew  1212:             $Str .= '"'.&Apache::loncommon::csv_translate($field).'",';
1.54      matthew  1213:         } elsif ($data eq 'sum and total' || $data eq 'parts correct total') {
1.37      matthew  1214:             $Str .= '"",'; # first row empty on the student fields
                   1215:             $Str2 .= '"'.&Apache::loncommon::csv_translate($field).'",';
1.54      matthew  1216:         } elsif ($data eq 'scores' || $data eq 'tries' || 
                   1217:                  $data eq 'parts correct') {
1.53      matthew  1218:             $Str  .= '"",';
                   1219:             $Str2 .= '"'.&Apache::loncommon::csv_translate($field).'",';
1.37      matthew  1220:         }
                   1221:     }
1.40      matthew  1222:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1.54      matthew  1223:         if ($data eq 'sum only' || $data eq 'parts correct') {
1.37      matthew  1224:             $Str .= '"'.&Apache::loncommon::csv_translate($seq->{'title'}).
                   1225:                 '",';
1.54      matthew  1226:         } elsif ($data eq 'sum and total' || $data eq 'parts correct total') {
1.37      matthew  1227:             $Str  .= '"'.&Apache::loncommon::csv_translate($seq->{'title'}).
                   1228:                 '","",';
                   1229:             $Str2 .= '"score","total possible",';
1.54      matthew  1230:         } elsif ($data eq 'scores' || $data eq 'tries') {
1.37      matthew  1231:             $Str  .= '"'.&Apache::loncommon::csv_translate($seq->{'title'}).
                   1232:                 '",';
1.53      matthew  1233:             $Str .= '"",'x($seq->{'num_assess_parts'}-1+2);
                   1234:             foreach my $res (@{$seq->{'contents'}}) {
1.54      matthew  1235:                 next if ($res->{'type'} ne 'assessment');
1.53      matthew  1236:                 foreach my $part (@{$res->{'parts'}}) {
                   1237:                     $Str2 .= '"'.&Apache::loncommon::csv_translate($res->{'title'}.', Part '.$part).'",';
                   1238:                 }
                   1239:             }
                   1240:             $Str2 .= '"score","total possible",';
1.37      matthew  1241:         }
                   1242:     }
                   1243:     chop($Str);
                   1244:     $Str .= "\n";
                   1245:     print $outputfile $Str;
                   1246:     if (defined($Str2)) {
                   1247:         chop($Str2);
                   1248:         $Str2 .= "\n";
                   1249:         print $outputfile $Str2;
                   1250:     }
                   1251:     #
                   1252:     # Initialize progress window
                   1253:     my $studentcount = scalar(@Apache::lonstatistics::Students);
                   1254:     %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
                   1255:         ($r,'CSV File Compilation Status',
                   1256:          'CSV File Compilation Progress', $studentcount);
1.31      matthew  1257:     return;
                   1258: }
                   1259: 
                   1260: sub csv_outputstudent {
                   1261:     my ($r,$student) = @_;
1.54      matthew  1262:     return if ($request_aborted);
1.37      matthew  1263:     return if (! defined($outputfile));
                   1264:     my $Str = '';
                   1265:     #
                   1266:     # Output student fields
                   1267:     my @to_show = &get_student_fields_to_show();
                   1268:     foreach my $field (@to_show) {
                   1269:         $Str .= '"'.&Apache::loncommon::csv_translate($student->{$field}).'",';
                   1270:     }
                   1271:     #
                   1272:     # Get student assessment data
                   1273:     my %StudentsData;
                   1274:     my @tmp = &Apache::loncoursedata::get_current_state($student->{'username'},
                   1275:                                                         $student->{'domain'},
                   1276:                                                         undef,
                   1277:                                                    $ENV{'request.course.id'});
                   1278:     if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
                   1279:         %StudentsData = @tmp;
                   1280:     }
                   1281:     #
                   1282:     # Output performance data
1.40      matthew  1283:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
1.54      matthew  1284:         my ($performance,$performance_length,$score,$seq_max,$rawdata);
                   1285:         if ($base eq 'tries') {
                   1286:             ($performance,$performance_length,$score,$seq_max,$rawdata) =
                   1287:                 &StudentTriesOnSequence($student,\%StudentsData,
                   1288:                                         $seq,'no');
                   1289:         } else {
                   1290:             ($performance,$performance_length,$score,$seq_max,$rawdata) =
                   1291:                 &StudentPerformanceOnSequence($student,\%StudentsData,
                   1292:                                               $seq,'no');
                   1293:         }
                   1294:         if ($data eq 'sum only' || $data eq 'parts correct') {
1.37      matthew  1295:             $Str .= '"'.$score.'",';
1.54      matthew  1296:         } elsif ($data eq 'sum and total' || $data eq 'parts correct total') {
1.37      matthew  1297:             $Str  .= '"'.$score.'","'.$seq_max.'",';
1.54      matthew  1298:         } elsif ($data eq 'scores' || $data eq 'tries') {
                   1299:             $Str .= '"'.join('","',(@$rawdata,$score,$seq_max)).'",';
1.37      matthew  1300:         }
                   1301:     }
                   1302:     chop($Str);
                   1303:     $Str .= "\n";
                   1304:     print $outputfile $Str;
                   1305:     #
                   1306:     # Update the progress window
                   1307:     &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
                   1308:     return;
1.31      matthew  1309: }
                   1310: 
                   1311: sub csv_finish {
                   1312:     my ($r) = @_;
1.54      matthew  1313:     return if ($request_aborted);
1.37      matthew  1314:     return if (! defined($outputfile));
                   1315:     close($outputfile);
                   1316:     #
                   1317:     my $c = $r->connection();
                   1318:     return if ($c->aborted());
                   1319:     #
                   1320:     # Close the progress window
                   1321:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
                   1322:     #
                   1323:     # Tell the user where to get their csv file
                   1324:     $r->print('<br />'.
                   1325:               '<a href="'.$filename.'">Your csv file.</a>'."\n");
                   1326:     $r->rflush();
                   1327:     return;
                   1328:     
1.31      matthew  1329: }
1.2       stredwic 1330: 
                   1331: }
                   1332: 
1.28      matthew  1333: #######################################################
                   1334: #######################################################
                   1335: 
1.2       stredwic 1336: =pod
                   1337: 
1.54      matthew  1338: =item &StudentTriesOnSequence()
1.2       stredwic 1339: 
1.30      matthew  1340: Inputs:
1.2       stredwic 1341: 
                   1342: =over 4
                   1343: 
1.30      matthew  1344: =item $student
1.28      matthew  1345: 
1.30      matthew  1346: =item $studentdata Hash ref to all student data
1.2       stredwic 1347: 
1.30      matthew  1348: =item $seq Hash ref, the sequence we are working on
1.2       stredwic 1349: 
1.30      matthew  1350: =item $links if defined we will output links to each resource.
1.2       stredwic 1351: 
1.28      matthew  1352: =back
1.2       stredwic 1353: 
                   1354: =cut
1.1       stredwic 1355: 
1.28      matthew  1356: #######################################################
                   1357: #######################################################
1.54      matthew  1358: sub StudentTriesOnSequence {
1.32      matthew  1359:     my ($student,$studentdata,$seq,$links) = @_;
1.31      matthew  1360:     $links = 'no' if (! defined($links));
1.1       stredwic 1361:     my $Str = '';
1.30      matthew  1362:     my ($sum,$max) = (0,0);
1.51      matthew  1363:     my $performance_length = 0;
1.54      matthew  1364:     my @TriesData = ();
                   1365:     my $tries;
1.30      matthew  1366:     foreach my $resource (@{$seq->{'contents'}}) {
                   1367:         next if ($resource->{'type'} ne 'assessment');
                   1368:         my $resource_data = $studentdata->{$resource->{'symb'}};
                   1369:         my $value = '';
                   1370:         foreach my $partnum (@{$resource->{'parts'}}) {
1.54      matthew  1371:             $tries = undef;
1.30      matthew  1372:             $max++;
1.51      matthew  1373:             $performance_length++;
1.30      matthew  1374:             my $symbol = ' '; # default to space
                   1375:             #
                   1376:             if (exists($resource_data->{'resource.'.$partnum.'.solved'})) {
                   1377:                 my $status = $resource_data->{'resource.'.$partnum.'.solved'};
                   1378:                 if ($status eq 'correct_by_override') {
                   1379:                     $symbol = '+';
                   1380:                     $sum++;
                   1381:                 } elsif ($status eq 'incorrect_by_override') {
                   1382:                     $symbol = '-';
                   1383:                 } elsif ($status eq 'ungraded_attempted') {
                   1384:                     $symbol = '#';
                   1385:                 } elsif ($status eq 'incorrect_attempted')  {
                   1386:                     $symbol = '.';
                   1387:                 } elsif ($status eq 'excused') {
                   1388:                     $symbol = 'x';
                   1389:                     $max--;
                   1390:                 } elsif ($status eq 'correct_by_student' &&
                   1391:                     exists($resource_data->{'resource.'.$partnum.'.tries'})){
1.54      matthew  1392:                     $tries = $resource_data->{'resource.'.$partnum.'.tries'};
                   1393:                     if ($tries > 9) {
1.30      matthew  1394:                         $symbol = '*';
1.54      matthew  1395:                     } elsif ($tries > 0) {
                   1396:                         $symbol = $tries;
1.30      matthew  1397:                     } else {
                   1398:                         $symbol = ' ';
                   1399:                     }
                   1400:                     $sum++;
1.43      matthew  1401:                 } elsif (exists($resource_data->{'resource.'.
                   1402:                                                      $partnum.'.tries'})){
                   1403:                     $symbol = '.';
1.30      matthew  1404:                 } else {
                   1405:                     $symbol = ' ';
1.2       stredwic 1406:                 }
1.30      matthew  1407:             } else {
                   1408:                 # Unsolved.  Did they try?
                   1409:                 if (exists($resource_data->{'resource.'.$partnum.'.tries'})){
                   1410:                     $symbol = '.';
                   1411:                 } else {
                   1412:                     $symbol = ' ';
1.18      matthew  1413:                 }
1.2       stredwic 1414:             }
1.54      matthew  1415:             #
                   1416:             if (! defined($tries)) {
                   1417:                 $tries = $symbol;
                   1418:             }
                   1419:             push (@TriesData,$tries);
1.30      matthew  1420:             #
1.47      matthew  1421:             if ( ($links eq 'yes' && $symbol ne ' ') ||
                   1422:                  ($links eq 'all')) {
1.49      matthew  1423:                 if (length($symbol) > 1) {
                   1424:                     &Apache::lonnet::logthis('length of symbol "'.$symbol.'" > 1');
                   1425:                 }
1.30      matthew  1426:                 $symbol = '<a href="/adm/grades'.
                   1427:                     '?symb='.&Apache::lonnet::escape($resource->{'symb'}).
                   1428:                         '&student='.$student->{'username'}.
                   1429:                             '&domain='.$student->{'domain'}.
                   1430:                                 '&command=submission">'.$symbol.'</a>';
                   1431:             }
                   1432:             $value .= $symbol;
1.2       stredwic 1433:         }
1.30      matthew  1434:         $Str .= $value;
1.17      minaeibi 1435:     }
1.51      matthew  1436:     if ($seq->{'randompick'}) {
                   1437:         $max = $seq->{'randompick'};
                   1438:     }
1.54      matthew  1439:     return ($Str,$performance_length,$sum,$max,\@TriesData);
                   1440: }
                   1441: 
                   1442: #######################################################
                   1443: #######################################################
                   1444: 
                   1445: =pod
                   1446: 
                   1447: =item &StudentPerformanceOnSequence()
                   1448: 
                   1449: Inputs:
                   1450: 
                   1451: =over 4
                   1452: 
                   1453: =item $student
                   1454: 
                   1455: =item $studentdata Hash ref to all student data
                   1456: 
                   1457: =item $seq Hash ref, the sequence we are working on
                   1458: 
                   1459: =item $links if defined we will output links to each resource.
                   1460: 
                   1461: =back
                   1462: 
                   1463: =cut
                   1464: 
                   1465: #######################################################
                   1466: #######################################################
                   1467: sub StudentPerformanceOnSequence {
                   1468:     my ($student,$studentdata,$seq,$links) = @_;
                   1469:     $links = 'no' if (! defined($links));
                   1470:     my $Str = ''; # final result string
                   1471:     my ($score,$max) = (0,0);
                   1472:     my $performance_length = 0;
                   1473:     my $symbol;
                   1474:     my @ScoreData = ();
                   1475:     my $partscore;
                   1476:     foreach my $resource (@{$seq->{'contents'}}) {
                   1477:         next if ($resource->{'type'} ne 'assessment');
                   1478:         my $resource_data = $studentdata->{$resource->{'symb'}};
                   1479:         foreach my $part (@{$resource->{'parts'}}) {
                   1480:             $partscore = undef;
                   1481:             my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight',
                   1482:                                               $resource->{'symb'},
                   1483:                                               $student->{'domain'},
                   1484:                                               $student->{'username'},
                   1485:                                               $student->{'section'});
                   1486:             if (!defined($weight) || ($weight eq '')) { 
                   1487:                 $weight=1;
                   1488:             }
                   1489:             #
                   1490:             $max += $weight; # see the 'excused' branch below...
                   1491:             $performance_length++; # one character per part
                   1492:             $symbol = ' '; # default to space
                   1493:             #
                   1494:             my $awarded = 0;
                   1495:             if (exists($resource_data->{'resource.'.$part.'.awarded'})) {
                   1496:                 $awarded = $resource_data->{'resource.'.$part.'.awarded'};
                   1497:             }
                   1498:             #
                   1499:             $partscore = $weight*$awarded;
                   1500:             $score += $partscore;
                   1501:             $symbol = $weight; 
                   1502:             if (length($symbol) > 1) {
                   1503:                 $symbol = '*';
                   1504:             }
                   1505:             if (exists($resource_data->{'resource.'.$part.'.solved'})) {
                   1506:                 my $status = $resource_data->{'resource.'.$part.'.solved'};
                   1507:                 if ($status eq 'excused') {
                   1508:                     $symbol = 'x';
                   1509:                     $max -= $weight; # Do not count 'excused' problems.
                   1510:                 }
                   1511:             } else {
                   1512:                 # Unsolved.  Did they try?
                   1513:                 if (exists($resource_data->{'resource.'.$part.'.tries'})){
                   1514:                     $symbol = '.';
                   1515:                 } else {
                   1516:                     $symbol = ' ';
                   1517:                 }
                   1518:             }
                   1519:             #
1.60      matthew  1520:             if (! defined($partscore)) {
                   1521:                 $partscore = $symbol;
                   1522:             }
                   1523:             push (@ScoreData,$partscore);
                   1524:             #
1.54      matthew  1525:             if ( ($links eq 'yes' && $symbol ne ' ') || ($links eq 'all')) {
                   1526:                 $symbol = '<a href="/adm/grades'.
                   1527:                     '?symb='.&Apache::lonnet::escape($resource->{'symb'}).
                   1528:                     '&student='.$student->{'username'}.
                   1529:                     '&domain='.$student->{'domain'}.
                   1530:                     '&command=submission">'.$symbol.'</a>';
                   1531:             }
1.60      matthew  1532:             $Str .= $symbol;
1.54      matthew  1533:         }
                   1534:     }
                   1535:     return ($Str,$performance_length,$score,$max,\@ScoreData);
1.1       stredwic 1536: }
1.23      minaeibi 1537: 
1.28      matthew  1538: #######################################################
                   1539: #######################################################
1.23      minaeibi 1540: 
1.2       stredwic 1541: =pod
                   1542: 
                   1543: =item &CreateLegend()
                   1544: 
                   1545: This function returns a formatted string containing the legend for the
                   1546: chart.  The legend describes the symbols used to represent grades for
                   1547: problems.
                   1548: 
                   1549: =cut
                   1550: 
1.28      matthew  1551: #######################################################
                   1552: #######################################################
1.2       stredwic 1553: sub CreateLegend {
                   1554:     my $Str = "<p><pre>".
1.13      minaeibi 1555:               "   1  correct by student in 1 try\n".
                   1556:               "   7  correct by student in 7 tries\n".
1.12      minaeibi 1557:               "   *  correct by student in more than 9 tries\n".
1.20      minaeibi 1558: 	      "   +  correct by hand grading or override\n".
1.12      minaeibi 1559:               "   -  incorrect by override\n".
                   1560: 	      "   .  incorrect attempted\n".
                   1561: 	      "   #  ungraded attempted\n".
1.13      minaeibi 1562:               "      not attempted (blank field)\n".
1.12      minaeibi 1563: 	      "   x  excused".
1.17      minaeibi 1564:               "</pre><p>";
1.2       stredwic 1565:     return $Str;
                   1566: }
                   1567: 
1.28      matthew  1568: #######################################################
                   1569: #######################################################
                   1570: 
1.30      matthew  1571: =pod 
1.2       stredwic 1572: 
                   1573: =back
                   1574: 
                   1575: =cut
                   1576: 
1.28      matthew  1577: #######################################################
                   1578: #######################################################
1.2       stredwic 1579: 
1.28      matthew  1580: 1;
1.2       stredwic 1581: 
1.1       stredwic 1582: __END__

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>
500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.