Annotation of loncom/interface/statistics/lonproblemstatistics.pm, revision 1.73

1.1       stredwic    1: # The LearningOnline Network with CAPA
                      2: #
1.73    ! matthew     3: # $Id: lonproblemstatistics.pm,v 1.72 2004/03/23 20:08:58 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: #
                      9: # LON-CAPA is free software; you can redistribute it and/or modify
                     10: # it under the terms of the GNU General Public License as published by
                     11: # the Free Software Foundation; either version 2 of the License, or
                     12: # (at your option) any later version.
                     13: #
                     14: # LON-CAPA is distributed in the hope that it will be useful,
                     15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
                     16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     17: # GNU General Public License for more details.
                     18: #
                     19: # You should have received a copy of the GNU General Public License
                     20: # along with LON-CAPA; if not, write to the Free Software
                     21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
                     22: #
                     23: # /home/httpd/html/adm/gpl.txt
                     24: #
                     25: # http://www.lon-capa.org/
                     26: #
                     27: # (Navigate problems for statistical reports
                     28: #
1.47      matthew    29: ###############################################
                     30: ###############################################
                     31: 
                     32: =pod
                     33: 
                     34: =head1 NAME
                     35: 
                     36: lonproblemstatistics
                     37: 
                     38: =head1 SYNOPSIS
                     39: 
                     40: Routines to present problem statistics to instructors via tables,
                     41: Excel files, and plots.
                     42: 
                     43: =over 4
                     44: 
                     45: =cut
                     46: 
                     47: ###############################################
                     48: ###############################################
1.1       stredwic   49: 
1.36      minaeibi   50: package Apache::lonproblemstatistics;
1.1       stredwic   51: 
                     52: use strict;
                     53: use Apache::lonnet();
1.62      matthew    54: use Apache::loncommon();
1.1       stredwic   55: use Apache::lonhtmlcommon;
                     56: use Apache::loncoursedata;
1.41      matthew    57: use Apache::lonstatistics;
1.59      matthew    58: use Apache::lonlocal;
1.44      matthew    59: use Spreadsheet::WriteExcel;
1.70      matthew    60: use Apache::lonstathelpers();
1.71      matthew    61: use Time::HiRes;
1.73    ! matthew    62: 
        !            63: my @StatsArray;
        !            64: 
1.59      matthew    65: ##
                     66: ## Localization notes:
                     67: ##
                     68: ## in @Fields[0]->{'long_title'} is placed in Excel files and is used as the
                     69: ## header for plots created with Graph.pm, both of which more than likely do
                     70: ## not support localization.
                     71: ##
1.49      matthew    72: my @Fields = (
                     73:            { name => 'problem_num',
                     74:              title => 'P#',
                     75:              align => 'right',
                     76:              color => '#FFFFE6' },
                     77:            { name   => 'container',
1.51      matthew    78:              title  => 'Sequence or Folder',
1.49      matthew    79:              align  => 'left',
                     80:              color  => '#FFFFE6',
                     81:              sortable => 'yes' },
                     82:            { name   => 'title',
                     83:              title  => 'Title',
                     84:              align  => 'left',
                     85:              color  => '#FFFFE6',
                     86:              special  => 'link',
                     87:              sortable => 'yes', },
                     88:            { name   => 'part', 
                     89:              title  => 'Part',
                     90:              align  => 'left',
1.55      matthew    91:              color  => '#FFFFE6',
                     92:              },
1.49      matthew    93:            { name   => 'num_students',
                     94:              title  => '#Stdnts',
                     95:              align  => 'right',
                     96:              color  => '#EEFFCC',
                     97:              format => '%d',
                     98:              sortable  => 'yes',
                     99:              graphable => 'yes',
                    100:              long_title => 'Number of Students Attempting Problem' },
                    101:            { name   => 'tries',
                    102:              title  => 'Tries',
                    103:              align  => 'right',
                    104:              color  => '#EEFFCC',
                    105:              format => '%d',
                    106:              sortable  => 'yes',
                    107:              graphable => 'yes',
                    108:              long_title => 'Total Number of Tries' },
                    109:            { name   => 'max_tries',
                    110:              title  => 'Max Tries',
                    111:              align  => 'right',
                    112:              color  => '#DDFFFF',
                    113:              format => '%d',
                    114:              sortable  => 'yes',
                    115:              graphable => 'yes',
                    116:              long_title => 'Maximum Number of Tries' },
1.73    ! matthew   117:            { name   => 'min_tries',
        !           118:              title  => 'Min Tries',
        !           119:              align  => 'right',
        !           120:              color  => '#DDFFFF',
        !           121:              format => '%d',
        !           122:              sortable  => 'yes',
        !           123:              graphable => 'yes',
        !           124:              long_title => 'Minumum Number of Tries' },
1.49      matthew   125:            { name   => 'mean_tries',
                    126:              title  => 'Mean Tries',
                    127:              align  => 'right',
                    128:              color  => '#DDFFFF',
                    129:              format => '%5.2f',
                    130:              sortable  => 'yes',
                    131:              graphable => 'yes',
                    132:              long_title => 'Average Number of Tries' },
                    133:            { name   => 'std_tries',
                    134:              title  => 'S.D. tries',
                    135:              align  => 'right',
                    136:              color  => '#DDFFFF',
                    137:              format => '%5.2f',
                    138:              sortable  => 'yes',
                    139:              graphable => 'yes',
                    140:              long_title => 'Standard Deviation of Number of Tries' },
                    141:            { name   => 'skew_tries',
                    142:              title  => 'Skew Tries',
                    143:              align  => 'right',
                    144:              color  => '#DDFFFF',
                    145:              format => '%5.2f',
                    146:              sortable  => 'yes',
                    147:              graphable => 'yes',
                    148:              long_title => 'Skew of Number of Tries' },
                    149:            { name   => 'num_solved',
                    150:              title  => '#YES',
                    151:              align  => 'right',
                    152:              color  => '#FFDDDD',
1.63      matthew   153:              format => '%4.1f',#             format => '%d',
1.49      matthew   154:              sortable  => 'yes',
                    155:              graphable => 'yes',
                    156:              long_title => 'Number of Students able to Solve' },
                    157:            { name   => 'num_override',
                    158:              title  => '#yes',
                    159:              align  => 'right',
                    160:              color  => '#FFDDDD',
1.63      matthew   161:              format => '%4.1f',#             format => '%d',
1.49      matthew   162:              sortable  => 'yes',
                    163:              graphable => 'yes',
                    164:              long_title => 'Number of Students given Override' },
1.73    ! matthew   165:            { name   => 'num_wrong',
        !           166:              title  => '#Wrng',
1.49      matthew   167:              align  => 'right',
1.73    ! matthew   168:              color  => '#FFDDDD',
1.49      matthew   169:              format => '%4.1f',
                    170:              sortable  => 'yes',
                    171:              graphable => 'yes',
1.55      matthew   172:              long_title => 'Percent of students whose final answer is wrong' },
1.73    ! matthew   173:            { name   => 'deg_of_diff',
        !           174:              title  => 'DoDiff',
        !           175:              align  => 'right',
        !           176:              color  => '#FFFFE6',
        !           177:              format => '%5.2f',
        !           178:              sortable  => 'yes',
        !           179:              graphable => 'yes',
        !           180:              long_title => 'Degree of Difficulty'.
        !           181:                            '[ 1 - ((#YES+#yes) / Tries) ]'},
1.71      matthew   182:            { name   => 'deg_of_disc',
1.73    ! matthew   183:              title  => 'DoDisc',
1.71      matthew   184:              align  => 'right',
                    185:              color  => '#FFFFE6',
                    186:              format => '%4.2f',
                    187:              sortable  => 'yes',
                    188:              graphable => 'yes',
                    189:              long_title => 'Degree of Discrimination' },
1.49      matthew   190: );
                    191: 
1.47      matthew   192: ###############################################
                    193: ###############################################
                    194: 
                    195: =pod 
                    196: 
                    197: =item &CreateInterface()
                    198: 
                    199: Create the main intereface for the statistics page.  Allows the user to
                    200: select sections, maps, and output.
                    201: 
                    202: =cut
1.1       stredwic  203: 
1.47      matthew   204: ###############################################
                    205: ###############################################
1.41      matthew   206: sub CreateInterface {
                    207:     my $Str = '';
1.67      matthew   208:     $Str .= &Apache::lonhtmlcommon::breadcrumbs
1.69      matthew   209:         (undef,'Overall Problem Statistics','Statistics_Overall_Key');
1.41      matthew   210:     $Str .= '<table cellspacing="5">'."\n";
                    211:     $Str .= '<tr>';
1.59      matthew   212:     $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
                    213:     $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
                    214:     $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
1.70      matthew   215:     $Str .= '<td rowspan="2">'.
                    216:         &Apache::lonstathelpers::limit_by_time_form().'</td>';
1.41      matthew   217:     $Str .= '</tr>'."\n";
                    218:     #
                    219:     $Str .= '<tr><td align="center">'."\n";
                    220:     $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
1.50      matthew   221:     $Str .= '</td><td align="center">';
                    222:     $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
1.41      matthew   223:     $Str .= '</td><td align="center">';
                    224:     #
                    225:     my $only_seq_with_assessments = sub { 
                    226:         my $s=shift;
                    227:         if ($s->{'num_assess'} < 1) { 
                    228:             return 0;
                    229:         } else { 
                    230:             return 1;
                    231:         }
                    232:     };
                    233:     $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,
                    234:                                               $only_seq_with_assessments);
                    235:     $Str .= '</td></tr>'."\n";
                    236:     $Str .= '</table>'."\n";
1.59      matthew   237:     $Str .= '<input type="submit" name="GenerateStatistics" value="'.
                    238:         &mt('Generate Statistics').'" />';
1.54      matthew   239:     $Str .= '&nbsp;'x5;
1.73    ! matthew   240:     $Str .= 'Plot '.&plot_dropdown().('&nbsp;'x10);
1.59      matthew   241:     $Str .= '<input type="submit" name="ClearCache" value="'.
                    242:         &mt('Clear Caches').'" />';
1.54      matthew   243:     $Str .= '&nbsp;'x5;
1.73    ! matthew   244:     $Str .= '<input type="submit" name="UpdateCache" value="'.
        !           245:         &mt('Update Student Data').'" />';
        !           246:     $Str .= '&nbsp;'x5;
        !           247:     $Str .= '<input type="submit" name="Excel" value="'.
        !           248:         &mt('Produce Excel Output').'" />';
        !           249:     $Str .= '&nbsp;'x5;
        !           250:     return $Str;
1.41      matthew   251: }
1.25      stredwic  252: 
1.41      matthew   253: ###############################################
                    254: ###############################################
1.28      stredwic  255: 
1.47      matthew   256: =pod 
                    257: 
                    258: =item &BuildProblemStatisticsPage()
                    259: 
                    260: Main interface to problem statistics.
                    261: 
                    262: =cut
                    263: 
1.41      matthew   264: ###############################################
                    265: ###############################################
                    266: sub BuildProblemStatisticsPage {
                    267:     my ($r,$c)=@_;
1.61      matthew   268:     #
                    269:     my %Saveable_Parameters = ('Status' => 'scalar',
                    270:                                'statsoutputmode' => 'scalar',
                    271:                                'Section' => 'array',
                    272:                                'StudentData' => 'array',
                    273:                                'Maps' => 'array');
                    274:     &Apache::loncommon::store_course_settings('statistics',
                    275:                                               \%Saveable_Parameters);
                    276:     &Apache::loncommon::restore_course_settings('statistics',
                    277:                                                 \%Saveable_Parameters);
                    278:     #
                    279:     &Apache::lonstatistics::PrepareClasslist();
1.41      matthew   280:     #
1.73    ! matthew   281:     # Clear the package variables
        !           282:     undef(@StatsArray);
1.71      matthew   283:     #
1.73    ! matthew   284:     # Finally let the user know we are here
        !           285:     my $interface = &CreateInterface();
1.57      matthew   286:     $r->print($interface);
1.41      matthew   287:     $r->print('<input type="hidden" name="sortby" value="'.$ENV{'form.sortby'}.
                    288:               '" />');
1.73    ! matthew   289:     #
1.41      matthew   290:     if (! exists($ENV{'form.statsfirstcall'})) {
1.73    ! matthew   291:         $r->print('<input type="hidden" name="statsfirstcall" value="yes" />');
        !           292:         $r->print('<h3>'.
        !           293:                   &mt('Press "Generate Statistics" when you are ready.').
        !           294:                   '</h3><p>'.
        !           295:                   &mt('It may take some time to update the student data '.
        !           296:                       'for the first analysis.  Future analysis this session '.
        !           297:                       ' will not have this delay.').
        !           298:                   '</p>');
1.41      matthew   299:         return;
1.73    ! matthew   300:     } elsif ($ENV{'form.statsfirstcall'} eq 'yes' || 
        !           301:              exists($ENV{'form.UpdateCache'}) ||
        !           302:              exists($ENV{'form.ClearCache'}) ) {
        !           303:         $r->print('<input type="hidden" name="statsfirstcall" value="no" />');
        !           304:         &Apache::lonstatistics::Gather_Student_Data($r);
        !           305:     } else {
        !           306:         $r->print('<input type="hidden" name="statsfirstcall" value="no" />');
1.28      stredwic  307:     }
1.73    ! matthew   308:     $r->rflush();
1.41      matthew   309:     #
1.73    ! matthew   310:     # This probably does not need to be done each time we are called, but
        !           311:     # it does not slow things down noticably.
        !           312:     &Apache::loncoursedata::populate_weight_table();
        !           313:     if (exists($ENV{'form.Excel'})) {
        !           314:         &Excel_output($r);
        !           315:     } else {
        !           316:         my $sortby = $ENV{'form.sortby'};
        !           317:         $sortby = 'container' if (! defined($sortby) || $sortby =~ /^\s*$/);
        !           318:         my $plot = $ENV{'form.plot'};
        !           319:         &Apache::lonnet::logthis('form.plot = '.$plot);
        !           320:         if ($sortby eq 'container' && ! defined($plot)) {
        !           321:             &output_html_by_sequence($r);
        !           322:         } else {
        !           323:             if (defined($plot)) {
        !           324:                 &Apache::lonnet::logthis('calling plot routine');
        !           325:                 &make_plot($r,$plot);
        !           326:             }
        !           327:             &output_html_stats($r);
        !           328:         }
        !           329:     }
        !           330:     return;
        !           331: }
        !           332: 
        !           333: ##########################################################
        !           334: ##########################################################
        !           335: ##
        !           336: ## HTML output routines
        !           337: ##
        !           338: ##########################################################
        !           339: ##########################################################
        !           340: sub output_html_by_sequence {
        !           341:     my ($r) = @_;
        !           342:     my $c = $r->connection();
        !           343:     $r->print(&html_preamble());
1.41      matthew   344:     #
1.73    ! matthew   345:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
        !           346:         last if ($c->aborted);
        !           347:         next if ($seq->{'num_assess'} < 1);
        !           348:         $r->print("<h3>".$seq->{'title'}."</h3>".
        !           349:                   '<table border="0"><tr><td bgcolor="#777777">'."\n".
        !           350:                   '<table border="0" cellpadding="3">'."\n".
        !           351:                   '<tr bgcolor="#FFFFE6">'.
        !           352:                   &statistics_table_header('no container')."</tr>\n");
        !           353:         my @Data = &compute_statistics_on_sequence($seq);
        !           354:         foreach my $data (@Data) {
        !           355:             $r->print('<tr>'.&statistics_html_table_data($data,
        !           356:                                                          'no container').
        !           357:                       "</tr>\n");
1.70      matthew   358:         }
1.73    ! matthew   359:         $r->print('</table>'."\n".'</table>'."\n");
1.41      matthew   360:         $r->rflush();
1.28      stredwic  361:     }
1.41      matthew   362:     return;
                    363: }
1.21      stredwic  364: 
1.73    ! matthew   365: sub output_html_stats {
        !           366:     my ($r)=@_;
        !           367:     &compute_all_statistics($r);
        !           368:     $r->print(&html_preamble());
        !           369:     &sort_data($ENV{'form.sortby'});
        !           370:     #
        !           371:     my $count=0;
        !           372:     foreach my $data (@StatsArray) {
        !           373:         if ($count++ % 50 == 0) {
        !           374:             $r->print("</table>\n</table>\n");
        !           375:             $r->print('<table border="0"><tr><td bgcolor="#777777">'."\n".
        !           376:                       '<table border="0" cellpadding="3">'."\n".
        !           377:                       '<tr bgcolor="#FFFFE6">'.
        !           378:                       '<tr bgcolor="#FFFFE6">'.
        !           379:                       &statistics_table_header().
        !           380:                       "</tr>\n");
        !           381:         }
        !           382:         $r->print('<tr>'.&statistics_html_table_data($data)."</tr>\n");
        !           383:     }
        !           384:     $r->print("</table>\n</table>\n");
        !           385:     return;
        !           386: }
1.44      matthew   387: 
1.47      matthew   388: 
1.73    ! matthew   389: sub html_preamble {
        !           390:     my $Str='';
        !           391:     $Str .= "<h2>".
        !           392:         $ENV{'course.'.$ENV{'request.course.id'}.'.description'}.
        !           393:         "</h2>\n";
        !           394:     my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
        !           395:     if (defined($starttime) || defined($endtime)) {
        !           396:         # Inform the user what the time limits on the data are.
        !           397:         $Str .= '<h3>'.&mt('Statistics on submissions from [_1] to [_2]',
        !           398:                            &Apache::lonlocal::locallocaltime($starttime),
        !           399:                            &Apache::lonlocal::locallocaltime($endtime)
        !           400:                            ).'</h3>';
        !           401:     }
        !           402:     $Str .= "<h3>".&mt('Compiled on [_1]',
        !           403:                        &Apache::lonlocal::locallocaltime(time))."</h3>";
        !           404:     return $Str;
        !           405: }
1.47      matthew   406: 
                    407: 
1.44      matthew   408: ###############################################
                    409: ###############################################
1.73    ! matthew   410: ##
        !           411: ## Misc HTML output routines
        !           412: ##
        !           413: ###############################################
        !           414: ###############################################
        !           415: sub statistics_html_table_data {
        !           416:     my ($data,$options) = @_;
        !           417:     my $row = '';
        !           418:     foreach my $field (@Fields) {
        !           419:         next if ($options =~ /no $field->{'name'}/);
        !           420:         $row .= '<td bgcolor="'.$field->{'color'}.'"';
        !           421:         if (exists($field->{'align'})) {
        !           422:             $row .= ' align="'.$field->{'align'}.'"';
1.41      matthew   423:             }
1.73    ! matthew   424:         $row .= '>';
        !           425:         if (exists($field->{'special'}) && $field->{'special'} eq 'link') {
        !           426:             $row .= '<a href="'.$data->{$field->{'name'}.'.link'}.'">';
1.41      matthew   427:         }
1.73    ! matthew   428:         if (exists($field->{'format'})) {
        !           429:             $row .= sprintf($field->{'format'},$data->{$field->{'name'}});
        !           430:         } else {
        !           431:             $row .= $data->{$field->{'name'}};
        !           432:         }
        !           433:         if (exists($field->{'special'}) && $field->{'special'} eq 'link') {
        !           434:             $row.= '</a>';
        !           435:         }
        !           436:         $row .= '</td>';
1.21      stredwic  437:     }
1.73    ! matthew   438:     return $row;
1.41      matthew   439: }
1.25      stredwic  440: 
1.73    ! matthew   441: sub statistics_table_header {
        !           442:     my ($options) = @_;
        !           443:     my $header_row;
        !           444:     foreach my $field (@Fields) {
        !           445:         next if ($options =~ /no $field->{'name'}/);
        !           446:         $header_row .= '<th>';
        !           447:         if (exists($field->{'sortable'}) && $field->{'sortable'} eq 'yes') {
        !           448:             $header_row .= '<a href="javascript:'.
        !           449:                 'document.Statistics.sortby.value='."'".$field->{'name'}."'".
        !           450:                     ';document.Statistics.submit();">';
        !           451:         }
        !           452:         $header_row .= &mt($field->{'title'});
        !           453:         if ($options =~ /sortable/) {
        !           454:             $header_row.= '</a>';
        !           455:         }
        !           456:         if ($options !~ /no plots/        && 
        !           457:             exists($field->{'graphable'}) && 
        !           458:             $field->{'graphable'} eq 'yes') {
        !           459:             $header_row.=' (';
        !           460:             $header_row .= '<a href="javascript:'.
        !           461:                 "document.Statistics.plot.value='$field->{'name'}'".
        !           462:                     ';document.Statistics.submit();">';
        !           463:             $header_row .= &mt('plot').'</a>)';
        !           464:         }
        !           465:         $header_row .= '</th>';
        !           466:     }
        !           467:     return $header_row;
        !           468: }
1.26      stredwic  469: 
1.73    ! matthew   470: ####################################################
        !           471: ####################################################
        !           472: ##
        !           473: ##    Plotting Routines
        !           474: ##
        !           475: ####################################################
        !           476: ####################################################
        !           477: sub make_plot {
        !           478:     my ($r,$plot) = @_;
        !           479:     &compute_all_statistics($r);
        !           480:     &sort_data($ENV{'form.sortby'});
        !           481:     if ($plot eq 'degrees') {
        !           482:         &degrees_plot($r);
        !           483:     } else {
        !           484:         &make_single_stat_plot($r,$plot);
        !           485:     }
        !           486:     return;
        !           487: }
1.47      matthew   488: 
1.73    ! matthew   489: sub make_single_stat_plot {
        !           490:     my ($r,$datafield) = @_;
1.41      matthew   491:     #
1.73    ! matthew   492:     my $title; my $yaxis;
        !           493:     foreach my $field (@Fields) {
        !           494:         next if ($field->{'name'} ne $datafield);
        !           495:         $title = $field->{'long_title'};
        !           496:         $yaxis = $field->{'title'};
        !           497:         last;
        !           498:     }
        !           499:     if ($title eq '' || $yaxis eq '') {
        !           500:         # datafield is something we do not know enough about to plot
        !           501:         $r->print('<h3>'.
        !           502:                   &mt('Unable to plot the requested statistic.').
        !           503:                   '</h3>');
        !           504:         return;
1.49      matthew   505:     }
                    506:     #
1.73    ! matthew   507:     # Build up the data sets to plot
        !           508:     my @Labels; 
        !           509:     my @Data;
        !           510:     my $max = 1;
        !           511:     foreach my $data (@StatsArray) {
        !           512:         push(@Labels,$data->{'problem_num'});
        !           513:         push(@Data,$data->{$datafield});
        !           514:         if ($data->{$datafield}>$max) {
        !           515:             $max = $data->{$datafield};
        !           516:         }
        !           517:     }
        !           518:     foreach (1,2,3,4,5,10,15,20,25,40,50,75,100,150,200,250,300,500,600,750,
        !           519:              1000,1500,2000,2500,3000,3500,4000,5000,7500,10000,15000,20000) {
        !           520:         if ($max <= $_) {
        !           521:             $max = $_;
        !           522:             last;
1.42      matthew   523:         }
                    524:     }
1.73    ! matthew   525:     if ($max > 20000) {
        !           526:         $max = 10000*(int($max/10000)+1);
1.42      matthew   527:     }
1.73    ! matthew   528:     #
        !           529:     $r->print("<p>".&Apache::loncommon::DrawBarGraph($title,
        !           530:                                                      'Problem Number',
        !           531:                                                      $yaxis,
        !           532:                                                      $max,
        !           533:                                                      undef, # colors
        !           534:                                                      \@Labels,
        !           535:                                                      \@Data)."</p>\n");
        !           536:     return;
        !           537: }
        !           538: 
        !           539: sub degrees_plot {
        !           540:     my ($r)=@_;
        !           541:     my $count = scalar(@StatsArray);
        !           542:     my $width = 50 + 10*$count;
        !           543:     $width = 300 if ($width < 300);
        !           544:     my $height = 300;
        !           545:     my $plot = '';
        !           546:     my $ymax = 0;
        !           547:     my $ymin = 0;
        !           548:     my @Disc; my @Diff; my @Labels;    
        !           549:     foreach my $data (@StatsArray) {
        !           550:         push(@Labels,$data->{'problem_num'});
        !           551:         my $disc = $data->{'deg_of_disc'};
        !           552:         my $diff = $data->{'deg_of_diff'};
        !           553:         push(@Disc,$disc);
        !           554:         push(@Diff,$diff);
        !           555:         #
        !           556:         $ymin = $disc if ($ymin > $disc);
        !           557:         $ymin = $diff if ($ymin > $diff);
        !           558:         $ymax = $disc if ($ymax < $disc);
        !           559:         $ymax = $diff if ($ymax < $diff);
        !           560:     }
        !           561:     #
        !           562:     # Make sure we show relevant information.
        !           563:     if ($ymin < 0) {
        !           564:         if (abs($ymin) < 0.05) {
        !           565:             $ymin = 0;
        !           566:         } else {
        !           567:             $ymin = -1;
1.42      matthew   568:         }
                    569:     }
1.73    ! matthew   570:     if ($ymax > 0) {
        !           571:         if (abs($ymax) < 0.05) {
        !           572:             $ymax = 0;
1.42      matthew   573:         } else {
1.73    ! matthew   574:             $ymax = 1;
1.42      matthew   575:         }
1.43      matthew   576:     }
1.49      matthew   577:     #
1.73    ! matthew   578:     my $xmax = $Labels[-1];
        !           579:     if ($xmax > 50) {
        !           580:         if ($xmax % 10 != 0) {
        !           581:             $xmax = 10 * (int($xmax/10)+1);
        !           582:         }
        !           583:     } else {
        !           584:         if ($xmax % 5 != 0) {
        !           585:             $xmax = 5 * (int($xmax/5)+1);
1.49      matthew   586:         }
1.26      stredwic  587:     }
1.41      matthew   588:     #
1.73    ! matthew   589:     my $discdata .= '<data>'.join(',',@Labels).'</data>'.$/.
        !           590:                     '<data>'.join(',',@Disc).'</data>'.$/;
        !           591:     #
        !           592:     my $diffdata .= '<data>'.join(',',@Labels).'</data>'.$/.
        !           593:                     '<data>'.join(',',@Diff).'</data>'.$/;
        !           594:     #
        !           595:     $plot=<<"END";
        !           596: <gnuplot 
        !           597:     texfont="10"
        !           598:     fgcolor="x000000"
        !           599:     plottype="Cartesian"
        !           600:     font="large"
        !           601:     grid="on"
        !           602:     align="center"
        !           603:     border="on"
        !           604:     transparent="on"
        !           605:     alttag="Sample Plot"
        !           606:     samples="100"
        !           607:     bgcolor="xffffff"
        !           608:     height="$height"
        !           609:     width="$width">
        !           610:     <key 
        !           611:         pos="top right"
        !           612:         title=""
        !           613:         box="off" />
        !           614:     <title>Degree of Discrmination and Degree of Difficulty</title>
        !           615:     <axis xmin="0" ymin="$ymin" xmax="$xmax" ymax="$ymax" color="x000000" />
        !           616:     <xlabel>Problem Number</xlabel>
        !           617:     <curve 
        !           618:         linestyle="linespoints" 
        !           619:         name="DoDisc" 
        !           620:         pointtype="0" 
        !           621:         color="x000000">
        !           622:         $discdata
        !           623:     </curve>
        !           624:     <curve 
        !           625:         linestyle="linespoints" 
        !           626:         name="DoDiff" 
        !           627:         pointtype="0" 
        !           628:         color="xFF0000">
        !           629:         $diffdata
        !           630:     </curve>
        !           631: </gnuplot>
        !           632: END
        !           633:     my $plotresult = 
        !           634:         '<p>'.&Apache::lonxml::xmlparse($r,'web',$plot).'</p>'.$/;
        !           635:     $r->print($plotresult);
1.41      matthew   636:     return;
1.42      matthew   637: }
                    638: 
1.73    ! matthew   639: sub plot_dropdown {
        !           640:     my $current = '';
        !           641:     #
        !           642:     if (defined($ENV{'form.plot'})) {
        !           643:         $current = $ENV{'form.plot'};
        !           644:     }
        !           645:     #
        !           646:     my @Additional_Plots = (
        !           647:                             { graphable=>'yes',
        !           648:                               name => 'degrees',
        !           649:                               title => 'DoDisc and DoDiff' });
        !           650:     #
        !           651:     my $Str= "\n".'<select name="plot" size="1">';
        !           652:     $Str .= '<option name="none"></option>'."\n";
        !           653:     $Str .= '<option name="none2">none</option>'."\n";
        !           654:     foreach my $field (@Fields,@Additional_Plots) {
        !           655:         if (! exists($field->{'graphable'}) ||
        !           656:             $field->{'graphable'} ne 'yes') {
        !           657:             next;
        !           658:         }
        !           659:         $Str .= '<option value="'.$field->{'name'}.'"';
        !           660:         if ($field->{'name'} eq $current) {
        !           661:             $Str .= ' selected ';
        !           662:         }
        !           663:         $Str.= '>'.&mt($field->{'title'}).'</option>'."\n";
        !           664:     }
        !           665:     $Str .= '</select>'."\n";
        !           666:     return $Str;
        !           667: }
        !           668: 
1.41      matthew   669: ###############################################
                    670: ###############################################
1.73    ! matthew   671: ##
        !           672: ## Excel output routines
        !           673: ##
1.41      matthew   674: ###############################################
                    675: ###############################################
1.73    ! matthew   676: sub Excel_output {
1.44      matthew   677:     my ($r) = @_;
1.73    ! matthew   678:     $r->print('<h2>'.&mt('Preparing Excel Spreadsheet').'</h2>');
        !           679:     ##
        !           680:     ## Compute the statistics
        !           681:     &compute_all_statistics($r);
        !           682:     my $c = $r->connection;
        !           683:     return if ($c->aborted());
        !           684:     ##
        !           685:     ## Create the excel workbook
1.44      matthew   686:     my $filename = '/prtspool/'.
                    687:         $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
1.73    ! matthew   688:         time.'_'.rand(1000000000).'.xls';
1.70      matthew   689:     my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
                    690:     #
1.44      matthew   691:     # Create sheet
1.73    ! matthew   692:     my $excel_workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
1.44      matthew   693:     #
                    694:     # Check for errors
                    695:     if (! defined($excel_workbook)) {
                    696:         $r->log_error("Error creating excel spreadsheet $filename: $!");
1.59      matthew   697:         $r->print(&mt("Problems creating new Excel file.  ".
1.44      matthew   698:                   "This error has been logged.  ".
1.59      matthew   699:                   "Please alert your LON-CAPA administrator."));
1.73    ! matthew   700:         return 0;
1.44      matthew   701:     }
                    702:     #
                    703:     # The excel spreadsheet stores temporary data in files, then put them
                    704:     # together.  If needed we should be able to disable this (memory only).
                    705:     # The temporary directory must be specified before calling 'addworksheet'.
                    706:     # File::Temp is used to determine the temporary directory.
                    707:     $excel_workbook->set_tempdir($Apache::lonnet::tmpdir);
                    708:     #
                    709:     # Add a worksheet
                    710:     my $sheetname = $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
                    711:     if (length($sheetname) > 31) {
                    712:         $sheetname = substr($sheetname,0,31);
                    713:     }
1.73    ! matthew   714:     my $excel_sheet = $excel_workbook->addworksheet(
        !           715:         &Apache::loncommon::clean_excel_name($sheetname));
        !           716:     ##
        !           717:     ## Begin creating excel sheet
        !           718:     ##
        !           719:     my ($rows_output,$cols_output) = (0,0);
1.44      matthew   720:     #
                    721:     # Put the course description in the header
                    722:     $excel_sheet->write($rows_output,$cols_output++,
                    723:                    $ENV{'course.'.$ENV{'request.course.id'}.'.description'});
                    724:     $cols_output += 3;
                    725:     #
                    726:     # Put a description of the sections listed
                    727:     my $sectionstring = '';
                    728:     my @Sections = @Apache::lonstatistics::SelectedSections;
                    729:     if (scalar(@Sections) > 1) {
                    730:         if (scalar(@Sections) > 2) {
                    731:             my $last = pop(@Sections);
                    732:             $sectionstring = "Sections ".join(', ',@Sections).', and '.$last;
                    733:         } else {
                    734:             $sectionstring = "Sections ".join(' and ',@Sections);
                    735:         }
                    736:     } else {
                    737:         if ($Sections[0] eq 'all') {
                    738:             $sectionstring = "All sections";
                    739:         } else {
                    740:             $sectionstring = "Section ".$Sections[0];
                    741:         }
                    742:     }
                    743:     $excel_sheet->write($rows_output,$cols_output++,$sectionstring);
                    744:     $cols_output += scalar(@Sections);
                    745:     #
1.70      matthew   746:     # Time restrictions
                    747:     my $time_string;
                    748:     if (defined($starttime)) {
                    749:         # call localtime but not lonlocal:locallocaltime because excel probably
                    750:         # cannot handle localized text.  Probably.
                    751:         $time_string .= 'Data collected from '.localtime($time_string);
                    752:         if (defined($endtime)) {
                    753:             $time_string .= ' to '.localtime($endtime);
                    754:         }
                    755:         $time_string .= '.';
                    756:     } elsif (defined($endtime)) {
                    757:         # See note above about lonlocal:locallocaltime
                    758:         $time_string .= 'Data collected before '.localtime($endtime).'.';
                    759:     }
                    760:     #
1.44      matthew   761:     # Put the date in there too
                    762:     $excel_sheet->write($rows_output,$cols_output++,
                    763:                         'Compiled on '.localtime(time));
                    764:     #
                    765:     $rows_output++; 
                    766:     $cols_output=0;
                    767:     #
1.73    ! matthew   768:     # Long Headers
1.55      matthew   769:     foreach my $field (@Fields) {
                    770:         next if ($field->{'name'} eq 'problem_num');
                    771:         if (exists($field->{'long_title'})) {
                    772:             $excel_sheet->write($rows_output,$cols_output++,
                    773:                                 $field->{'long_title'});
                    774:         } else {
                    775:             $excel_sheet->write($rows_output,$cols_output++,'');
                    776:         }
                    777:     }
                    778:     $rows_output++;
                    779:     $cols_output=0;
                    780:     # Brief headers
1.49      matthew   781:     foreach my $field (@Fields) {
                    782:         next if ($field->{'name'} eq 'problem_num');
1.59      matthew   783:         # Use english for excel as I am not sure how well excel handles 
                    784:         # other character sets....
1.49      matthew   785:         $excel_sheet->write($rows_output,$cols_output++,$field->{'title'});
1.44      matthew   786:     }
                    787:     $rows_output++;
1.73    ! matthew   788:     foreach my $data (@StatsArray) {
        !           789:         $cols_output=0;
        !           790:         foreach my $field (@Fields) {
        !           791:             next if ($field->{'name'} eq 'problem_num');
        !           792:             $excel_sheet->write($rows_output,$cols_output++,
        !           793:                                 $data->{$field->{'name'}});
1.44      matthew   794:         }
1.73    ! matthew   795:         $rows_output++;
1.44      matthew   796:     }
                    797:     #
                    798:     $excel_workbook->close();
1.73    ! matthew   799:     #
1.44      matthew   800:     # Tell the user where to get their excel file
                    801:     $r->print('<br />'.
1.59      matthew   802:               '<a href="'.$filename.'">'.
                    803:               &mt('Your Excel Spreadsheet').'</a>'."\n");
1.44      matthew   804:     $r->rflush();
                    805:     return;
                    806: }
                    807: 
1.73    ! matthew   808: ##################################################
        !           809: ##################################################
        !           810: ##
        !           811: ## Statistics Gathering and Manipulation Routines
        !           812: ##
        !           813: ##################################################
        !           814: ##################################################
        !           815: sub compute_statistics_on_sequence {
        !           816:     my ($seq) = @_;
        !           817:     my @Data;
        !           818:     foreach my $res (@{$seq->{'contents'}}) {
        !           819:         next if ($res->{'type'} ne 'assessment');
        !           820:         foreach my $part (@{$res->{'parts'}}) {
        !           821:             #
        !           822:             # This is where all the work happens
        !           823:             my $data = &get_statistics($seq,$res,$part,scalar(@StatsArray)+1);
        !           824:             push (@Data,$data);
        !           825:             push (@StatsArray,$data);
1.49      matthew   826:         }
1.26      stredwic  827:     }
1.73    ! matthew   828:     return @Data;
1.41      matthew   829: }
1.26      stredwic  830: 
1.73    ! matthew   831: sub compute_all_statistics {
        !           832:     my ($r) = @_;
        !           833:     if (@StatsArray > 0) {
        !           834:         # Assume we have already computed the statistics
        !           835:         return;
        !           836:     }
        !           837:     my $c = $r->connection;
        !           838:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
        !           839:         last if ($c->aborted);
        !           840:         next if ($seq->{'num_assess'} < 1);
        !           841:         &compute_statistics_on_sequence($seq);
1.49      matthew   842:     }
                    843: }
                    844: 
1.73    ! matthew   845: sub sort_data {
        !           846:     my ($sortkey) = @_;
        !           847:     return if (! @StatsArray);
1.45      matthew   848:     #
1.73    ! matthew   849:     # Sort the data
        !           850:     my $sortby = undef;
1.49      matthew   851:     foreach my $field (@Fields) {
1.73    ! matthew   852:         if ($sortkey eq $field->{'name'}) {
        !           853:             $sortby = $field->{'name'};
1.45      matthew   854:         }
1.26      stredwic  855:     }
1.73    ! matthew   856:     if (! defined($sortby) || $sortby eq '' || $sortby eq 'problem_num') {
        !           857:         $sortby = 'container';
        !           858:     }
        !           859:     if ($sortby ne 'container') {
        !           860:         # $sortby is already defined, so we can charge ahead
        !           861:         if ($sortby =~ /^(title|part)$/i) {
        !           862:             # Alpha comparison
        !           863:             @StatsArray = sort {
        !           864:                 lc($a->{$sortby}) cmp lc($b->{$sortby}) ||
        !           865:                 lc($a->{'title'}) cmp lc($b->{'title'}) ||
        !           866:                 lc($a->{'part'}) cmp lc($b->{'part'});
        !           867:             } @StatsArray;
1.24      stredwic  868:         } else {
1.73    ! matthew   869:             # Numerical comparison
        !           870:             @StatsArray = sort {
        !           871:                 my $retvalue = 0;
        !           872:                 if ($b->{$sortby} eq 'nan') {
        !           873:                     if ($a->{$sortby} ne 'nan') {
        !           874:                         $retvalue = -1;
        !           875:                     } else {
        !           876:                         $retvalue = 0;
        !           877:                     }
        !           878:                 }
        !           879:                 if ($a->{$sortby} eq 'nan') {
        !           880:                     if ($b->{$sortby} ne 'nan') {
        !           881:                         $retvalue = 1;
        !           882:                     }
        !           883:                 }
        !           884:                 if ($retvalue eq '0') {
        !           885:                     $retvalue = $b->{$sortby} <=> $a->{$sortby}     ||
        !           886:                             lc($a->{'title'}) <=> lc($b->{'title'}) ||
        !           887:                             lc($a->{'part'})  <=> lc($b->{'part'});
        !           888:                 }
        !           889:                 $retvalue;
        !           890:             } @StatsArray;
1.24      stredwic  891:         }
                    892:     }
1.45      matthew   893:     #
1.73    ! matthew   894:     # Renumber the data set
        !           895:     my $count;
        !           896:     foreach my $data (@StatsArray) {
        !           897:         $data->{'problem_num'} = ++$count;
        !           898:     }
1.24      stredwic  899:     return;
1.48      matthew   900: }
                    901: 
1.70      matthew   902: ########################################################
                    903: ########################################################
                    904: 
                    905: =pod
                    906: 
                    907: =item &get_statistics()
                    908: 
                    909: Wrapper routine from the call to loncoursedata::get_problem_statistics.  
1.73    ! matthew   910: Calls lonstathelpers::get_time_limits() to limit the data set by time
        !           911: and &compute_discrimination_factor
1.70      matthew   912: 
                    913: Inputs: $sequence, $resource, $part, $problem_num
                    914: 
                    915: Returns: Hash reference with statistics data from 
                    916: loncoursedata::get_problem_statistics.
                    917: 
                    918: =cut
                    919: 
                    920: ########################################################
                    921: ########################################################
1.48      matthew   922: sub get_statistics {
1.49      matthew   923:     my ($sequence,$resource,$part,$problem_num) = @_;
1.48      matthew   924:     #
1.70      matthew   925:     my ($starttime,$endtime) = &Apache::lonstathelpers::get_time_limits();
1.49      matthew   926:     my $symb = $resource->{'symb'};
1.48      matthew   927:     my $courseid = $ENV{'request.course.id'};
                    928:     #
1.49      matthew   929:     my $data = &Apache::loncoursedata::get_problem_statistics
1.66      matthew   930:                         (\@Apache::lonstatistics::SelectedSections,
                    931:                          $Apache::lonstatistics::enrollment_status,
1.70      matthew   932:                          $symb,$part,$courseid,$starttime,$endtime);
1.49      matthew   933:     $data->{'part'}        = $part;
                    934:     $data->{'problem_num'} = $problem_num;
                    935:     $data->{'container'}   = $sequence->{'title'};
                    936:     $data->{'title'}       = $resource->{'title'};
1.53      matthew   937:     $data->{'title.link'}  = $resource->{'src'}.'?symb='.
                    938:         &Apache::lonnet::escape($resource->{'symb'});
1.49      matthew   939:     #
1.71      matthew   940:     $data->{'deg_of_disc'} = &compute_discrimination_factor($resource,$part,$sequence);
1.49      matthew   941:     return $data;
1.71      matthew   942: }
                    943: 
                    944: 
                    945: ###############################################
                    946: ###############################################
                    947: 
                    948: =pod
                    949: 
                    950: =item &compute_discrimination_factor()
                    951: 
                    952: Inputs: $Resource, $Sequence
                    953: 
                    954: Returns: integer between -1 and 1
                    955: 
                    956: =cut
                    957: 
                    958: ###############################################
                    959: ###############################################
                    960: sub compute_discrimination_factor {
                    961:     my ($resource,$part,$sequence) = @_;
                    962:     my @Resources;
                    963:     foreach my $res (@{$sequence->{'contents'}}) {
                    964:         next if ($res->{'symb'} eq $resource->{'symb'});
                    965:         push (@Resources,$res->{'symb'});
                    966:     }
                    967:     #
                    968:     # rank
                    969:     my $ranking = 
                    970:         &Apache::loncoursedata::rank_students_by_scores_on_resources
                    971:         (\@Resources,
                    972:          \@Apache::lonstatistics::SelectedSections,
                    973:          $Apache::lonstatistics::enrollment_status,undef);
                    974:     #
                    975:     # compute their percent scores on the problems in the sequence,
                    976:     my $number_to_grab = int(scalar(@{$ranking})/4);
                    977:     my $num_students = scalar(@{$ranking});
                    978:     my @BottomSet = map { $_->[&Apache::loncoursedata::RNK_student()]; 
                    979:                       } @{$ranking}[0..$number_to_grab];
                    980:     my @TopSet    = 
                    981:         map { 
                    982:             $_->[&Apache::loncoursedata::RNK_student()]; 
                    983:           } @{$ranking}[($num_students-$number_to_grab)..($num_students-1)];
                    984:     my ($bottom_sum,$bottom_max) = 
                    985:         &Apache::loncoursedata::get_sum_of_scores($resource,$part,\@BottomSet);
                    986:     my ($top_sum,$top_max) = 
                    987:         &Apache::loncoursedata::get_sum_of_scores($resource,$part,\@TopSet);
                    988:     my $deg_of_disc;
                    989:     if ($top_max == 0 || $bottom_max==0) {
                    990:         $deg_of_disc = 'nan';
                    991:     } else {
                    992:         $deg_of_disc = ($top_sum/$top_max) - ($bottom_sum/$bottom_max);
                    993:     }
                    994:     #&Apache::lonnet::logthis('    '.$top_sum.'/'.$top_max.
                    995:     #                         ' - '.$bottom_sum.'/'.$bottom_max);
                    996:     return $deg_of_disc;
1.1       stredwic  997: }
1.12      minaeibi  998: 
1.45      matthew   999: ###############################################
                   1000: ###############################################
1.47      matthew  1001: 
                   1002: =pod 
                   1003: 
1.73    ! matthew  1004: =item ProblemStatisticsLegend
        !          1005: 
        !          1006: =over 4
        !          1007: 
        !          1008: =item #Stdnts
        !          1009: Total number of students attempted the problem.
        !          1010: 
        !          1011: =item Tries
        !          1012: Total number of tries for solving the problem.
1.59      matthew  1013: 
1.73    ! matthew  1014: =item Max Tries
        !          1015: Largest number of tries for solving the problem by a student.
        !          1016: 
        !          1017: =item Mean
        !          1018: Average number of tries. [ Tries / #Stdnts ]
        !          1019: 
        !          1020: =item #YES
        !          1021: Number of students solved the problem correctly.
        !          1022: 
        !          1023: =item #yes
        !          1024: Number of students solved the problem by override.
        !          1025: 
        !          1026: =item %Wrong
        !          1027: Percentage of students who tried to solve the problem 
        !          1028: but is still incorrect. [ 100*((#Stdnts-(#YES+#yes))/#Stdnts) ]
        !          1029: 
        !          1030: =item DoDiff
        !          1031: Degree of Difficulty of the problem.  
        !          1032: [ 1 - ((#YES+#yes) / Tries) ]
        !          1033: 
        !          1034: =item S.D.
        !          1035: Standard Deviation of the tries.  
        !          1036: [ sqrt(sum((Xi - Mean)^2)) / (#Stdnts-1) 
        !          1037: where Xi denotes every student\'s tries ]
        !          1038: 
        !          1039: =item Skew.
        !          1040: Skewness of the students tries.
        !          1041: [(sqrt( sum((Xi - Mean)^3) / #Stdnts)) / (S.D.^3)]
        !          1042: 
        !          1043: =item Dis.F.
        !          1044: Discrimination Factor: A Standard for evaluating the 
        !          1045: problem according to a Criterion<br>
        !          1046: 
        !          1047: =item [Criterion to group students into %27 Upper Students - 
        !          1048: and %27 Lower Students]
        !          1049: 1st Criterion for Sorting the Students: 
        !          1050: Sum of Partial Credit Awarded / Total Number of Tries
        !          1051: 2nd Criterion for Sorting the Students: 
        !          1052: Total number of Correct Answers / Total Number of Tries
        !          1053: 
        !          1054: =item Disc.
        !          1055: Number of Students had at least one discussion.
        !          1056: 
        !          1057: =back
1.47      matthew  1058: 
                   1059: =cut
1.1       stredwic 1060: 
1.73    ! matthew  1061: 
        !          1062: ############################################################
        !          1063: ############################################################
        !          1064: ##
        !          1065: ##  How this all works:
        !          1066: ##     Statistics are computed by calling &get_statistics with the sequence,
        !          1067: ##     resource, and part id to run statistics on.  At various places within
        !          1068: ##     the loops which compute the statistics, as well as before and after 
        !          1069: ##     the entire process, subroutines can be called.  The subroutines are
        !          1070: ##     registered to the following hooks:
        !          1071: ##
        !          1072: ##         hook          subroutine inputs
        !          1073: ##     ----------------------------------------------------------
        !          1074: ##         pre           $r,$count
        !          1075: ##         pre_seq       $r,$count,$seq
        !          1076: ##         pre_res       $r,$count,$seq,$res
        !          1077: ##         calc          $r,$count,$seq,$res,$data
        !          1078: ##         post_res      $r,$count,$seq,$res
        !          1079: ##         post_seq      $r,$count,$seq
        !          1080: ##         post          $r,$count
        !          1081: ##
        !          1082: ##         abort         $r
        !          1083: ##
        !          1084: ##     subroutines will be called in the order in which they are registered.
        !          1085: ##   
        !          1086: ############################################################
        !          1087: ############################################################
        !          1088: {
        !          1089: 
        !          1090: my %hooks;
        !          1091: my $aborted = 0;
        !          1092: 
        !          1093: sub abort_computation {
        !          1094:     $aborted = 1;
        !          1095: }
        !          1096: 
        !          1097: sub clear_hooks {
        !          1098:     $aborted = 0;
        !          1099:     undef(%hooks);
        !          1100: }
        !          1101: 
        !          1102: sub register_hook {
        !          1103:     my ($hookname,$subref)=@_;
        !          1104:     if ($hookname !~ /^(pre|pre_seq|pre_res|post|post_seq|post_res|calc)$/){
        !          1105:         return;
        !          1106:     }
        !          1107:     if (ref($subref) ne 'CODE') {
        !          1108:         &Apache::lonnet::logthis('attempt to register hook to non-code: '.
        !          1109:                                  $hookname,' = '.$subref);
        !          1110:     } else {
        !          1111:         if (exists($hooks{$hookname})) {
        !          1112:             push(@{$hooks{$hookname}},$subref);
        !          1113:         } else {
        !          1114:             $hooks{$hookname} = [$subref];
        !          1115:         }
        !          1116:     }
        !          1117:     return;
        !          1118: }
        !          1119: 
        !          1120: sub run_hooks {
        !          1121:     my $context = shift();
        !          1122:     foreach my $hook (@{$hooks{$context}}) { 
        !          1123:         if ($aborted && $context ne 'abort') {
        !          1124:             last;
        !          1125:         }
        !          1126:         my $retvalue = $hook->(@_);
        !          1127:         if (defined($retvalue) && $retvalue eq '0') {
        !          1128:             $aborted = 1 if (! $aborted);
        !          1129:         }
        !          1130:     }
        !          1131: }
        !          1132: 
        !          1133: sub run_statistics {
        !          1134:     my ($r) = @_;
        !          1135:     my $count = 0;
        !          1136:     &run_hooks('pre',$r,$count);
        !          1137:     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {
        !          1138:         last if ($aborted);
        !          1139:         next if ($seq->{'num_assess'}<1);
        !          1140:         &run_hooks('pre_seq',$r,$count,$seq);
        !          1141:         foreach my $res (@{$seq->{'contents'}}) {
        !          1142:             last if ($aborted);
        !          1143:             next if ($res->{'type'} ne 'assessment');
        !          1144:             &run_hooks('pre_res',$r,$count,$seq,$res);            
        !          1145:             foreach my $part (@{$res->{'parts'}}) {
        !          1146:                 last if ($aborted);
        !          1147:                 #
        !          1148:                 # This is where all the work happens
        !          1149:                 my $data = &get_statistics($seq,$res,$part,++$count);
        !          1150:                 &run_hooks('calc',$r,$count,$seq,$res,$part,$data); 
        !          1151:             }
        !          1152:             &run_hooks('post_res',$r,$count,$seq,$res);
        !          1153:         }
        !          1154:         &run_hooks('post_seq',$r,$count,$seq);
        !          1155:     }
        !          1156:     if ($aborted) {
        !          1157:         &run_hooks('abort',$r);
        !          1158:     } else {
        !          1159:         &run_hooks('post',$r,$count);
        !          1160:     }
        !          1161:     return;
1.1       stredwic 1162: }
1.24      stredwic 1163: 
1.73    ! matthew  1164: } # End of %hooks scope
        !          1165: 
        !          1166: ############################################################
        !          1167: ############################################################
1.4       minaeibi 1168: 
1.1       stredwic 1169: 1;
                   1170: __END__

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