File:  [LON-CAPA] / loncom / interface / statistics / lonstathelpers.pm
Revision 1.55: download - view: text, annotated - select for diffs
Sun Sep 14 15:16:29 2008 UTC (15 years, 9 months ago) by raeburn
Branches: MAIN
CVS tags: version_2_7_X, version_2_7_1, HEAD
Resource and symb in links to encrypted resources included in CHRT (assessment progress chart) and also STAT (course assessment statistics) need to be encrypted if viewer does not an advanced role (i.e., TA role) to prevent access denied message when following link.

    1: # The LearningOnline Network with CAPA
    2: #
    3: # $Id: lonstathelpers.pm,v 1.55 2008/09/14 15:16:29 raeburn Exp $
    4: #
    5: # Copyright Michigan State University Board of Trustees
    6: #
    7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    8: #
    9: # LON-CAPA is free software; you can redistribute it and/or modify
   10: # it under the terms of the GNU General Public License as published by
   11: # the Free Software Foundation; either version 2 of the License, or
   12: # (at your option) any later version.
   13: #
   14: # LON-CAPA is distributed in the hope that it will be useful,
   15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   17: # GNU General Public License for more details.
   18: #
   19: # You should have received a copy of the GNU General Public License
   20: # along with LON-CAPA; if not, write to the Free Software
   21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   22: #
   23: # /home/httpd/html/adm/gpl.txt
   24: #
   25: # http://www.lon-capa.org/
   26: #
   27: ####################################################
   28: ####################################################
   29: 
   30: =pod
   31: 
   32: =head1 NAME
   33: 
   34: Apache::lonstathelpers - helper routines used by statistics
   35: 
   36: =head1 SYNOPSIS
   37: 
   38: This module provides a place to consolidate much of the statistics 
   39: routines that are needed across multiple statistics functions.
   40: 
   41: =head1 OVERVIEW
   42: 
   43: =over 4
   44: 
   45: =cut
   46: 
   47: ####################################################
   48: ####################################################
   49: package Apache::lonstathelpers;
   50: 
   51: use strict;
   52: use Apache::lonnet;
   53: use Apache::loncommon();
   54: use Apache::lonhtmlcommon();
   55: use Apache::loncoursedata();
   56: use Apache::lonstatistics;
   57: use Apache::lonlocal;
   58: use HTML::Entities();
   59: use Time::Local();
   60: use Spreadsheet::WriteExcel();
   61: use GDBM_File;
   62: use Storable qw(freeze thaw);
   63: use lib '/home/httpd/lib/perl/';
   64: use LONCAPA;
   65:  
   66: 
   67: ####################################################
   68: ####################################################
   69: 
   70: =pod
   71: 
   72: =item &render_resource($resource)
   73: 
   74: Input: a navmaps resource
   75: 
   76: Retunrs: a scalar containing html for a rendering of the problem
   77: within a table.
   78: 
   79: =cut
   80: 
   81: ####################################################
   82: ####################################################
   83: sub render_resource {
   84:     my ($resource) = @_;
   85:     ##
   86:     ## Render the problem
   87:     my ($base) = ($resource->src =~ m|^(.*/)[^/]*$|);
   88:     $base="http://".$ENV{'SERVER_NAME'}.$base;
   89:     my ($src,$symb)=($resource->link,&escape($resource->shown_symb));
   90:     my $rendered_problem = &Apache::lonnet::ssi_body($src.'?symb='.$symb);
   91:     $rendered_problem =~ s/<\s*form\s*/<nop /g;
   92:     $rendered_problem =~ s|(<\s*/form\s*>)|<\/nop>|g;
   93:     return '<table bgcolor="ffffff"><tr><td>'.
   94:         '<base href="'.$base.'" />'.
   95:         $rendered_problem.
   96:         '</td></tr></table>';
   97: }
   98: 
   99: ####################################################
  100: ####################################################
  101: 
  102: =pod
  103: 
  104: =item &get_resources
  105: 
  106: =cut
  107: 
  108: ####################################################
  109: ####################################################
  110: sub get_resources {
  111:     my ($navmap,$sequence) = @_;
  112:     my @resources = $navmap->retrieveResources($sequence,
  113:                                                sub { shift->is_problem(); },
  114:                                                0,0,0);
  115:     return @resources;
  116: }
  117: 
  118: ####################################################
  119: ####################################################
  120: 
  121: =pod
  122: 
  123: =item &problem_selector($AcceptedResponseTypes)
  124: 
  125: Input: scalar containing regular expression which matches response
  126: types to show.  '.' will yield all, '(option|radiobutton)' will match
  127: all option response and radiobutton problems.
  128: 
  129: Returns: A string containing html for a table which lists the sequences
  130: and their contents.  A radiobutton is provided for each problem.
  131: Skips 'survey' problems.
  132: 
  133: =cut
  134: 
  135: ####################################################
  136: ####################################################
  137: sub problem_selector {
  138:     my ($AcceptedResponseTypes,$sequence_addendum) = @_;
  139:     my $Str;
  140:     $Str = "\n<table>\n";
  141:     my $rb_count =0;
  142:     my ($navmap,@sequences) = 
  143:         &Apache::lonstatistics::selected_sequences_with_assessments('all');
  144:     return $navmap if (! ref($navmap)); # error
  145:     foreach my $seq (@sequences) {
  146:         my $seq_str = '';
  147:         foreach my $res (&get_resources($navmap,$seq)) {
  148:             foreach my $part (@{$res->parts}) {
  149:                 my @response_ids   = $res->responseIds($part);
  150:                 my @response_types = $res->responseType($part);
  151:                 for (my $i=0;$i<scalar(@response_types);$i++){
  152:                     my $respid = $response_ids[$i];
  153:                     my $resptype = $response_types[$i];
  154:                     if ($resptype =~ m/$AcceptedResponseTypes/) {
  155:                         my $value = &make_target_id({symb=>$res->symb,
  156:                                                      part=>$part,
  157:                                                      respid=>$respid,
  158:                                                      resptype=>$resptype});
  159:                         my $checked = '';
  160:                         if ($env{'form.problemchoice'} eq $value) {
  161:                             $checked = 'checked ';
  162:                         }
  163:                         my $title = $res->compTitle;
  164:                         if (! defined($title) || $title eq '') {
  165:                             ($title) = ($res->src =~ m:/([^/]*)$:);
  166:                         }
  167:                         $seq_str .= '<tr>'.
  168:                             qq{<td><input type="radio" id="$rb_count" name="problemchoice" value="$value" $checked /></td>}.
  169:                             '<td><label for="'.$rb_count.'">'.$resptype.'</label></td>'.
  170:                             '<td><label for="'.$rb_count.'">'.$title.'</label>';
  171:                         if (scalar(@response_ids) > 1) {
  172:                             $seq_str .= &mt('response').' '.$respid;
  173:                         }
  174:                         my $link = $res->link.'?symb='.&escape($res->shown_symb);
  175:                         $seq_str .= ('&nbsp;'x2).
  176:                             qq{<a target="preview" href="$link">view</a>};
  177:                         $seq_str .= "</td></tr>\n";
  178:                         $rb_count++;
  179:                     }
  180:                 }
  181:             }
  182:         }
  183:         if ($seq_str ne '') {
  184:             $Str .= '<tr><td>&nbsp</td>'.
  185:                 '<td colspan="2"><b>'.$seq->compTitle.'</b></td>'.
  186:                 "</tr>\n".$seq_str;
  187:             if (defined($sequence_addendum)) {
  188:                 $Str .= '<tr>'.
  189:                     ('<td>&nbsp</td>'x2).
  190:                     '<td align="right">'.$sequence_addendum.'</td>'.
  191:                     "</tr>\n";
  192:             }
  193:         }
  194:     }
  195:     $Str .= "</table>\n";
  196:     return $Str;
  197: }
  198: 
  199: ####################################################
  200: ####################################################
  201: 
  202: =pod
  203: 
  204: =item &MultipleProblemSelector($navmap,$selected,$inputname)
  205: 
  206: Generate HTML with checkboxes for problem selection.
  207: 
  208: Input: 
  209: 
  210: $navmap: a navmap object.  If undef, navmaps will be called to create a
  211: new object.
  212: 
  213: $selected: Scalar, Array, or hash reference of currently selected items.
  214: 
  215: $inputname: The name of the form elements to use for the checkboxs.
  216: 
  217: Returns: A string containing html for a table which lists the sequences
  218: and their contents.  A checkbox is provided for each problem.
  219: 
  220: =cut
  221: 
  222: ####################################################
  223: ####################################################
  224: sub MultipleProblemSelector {
  225:     my ($navmap,$inputname,$formname)=@_;
  226:     my $cid = $env{'request.course.id'};
  227:     my $Str;
  228:     # Massage the input as needed.
  229:     if (! defined($navmap)) {
  230:         $navmap = Apache::lonnavmaps::navmap->new();
  231:         if (! defined($navmap)) {
  232:             $Str .= 
  233:                 '<h1>'.&mt('Error: cannot process course structure').'</h1>';
  234:             return $Str;
  235:         }
  236:     }
  237:     my $selected = {map { ($_,1) } (&get_selected_symbs($inputname))};
  238:     # Header
  239:     $Str .= <<"END";
  240: <script language="JavaScript" type="text/javascript">
  241:     function checkall(value,seqid) {
  242:         for (i=0; i<document.forms.$formname.elements.length; i++) {
  243:             ele = document.forms.$formname.elements[i];
  244:             if (ele.name == '$inputname') {
  245:                 if (seqid != null) {
  246:                     itemid = document.forms.$formname.elements[i].id;
  247:                     thing = itemid.split(':');
  248:                     if (thing[0] == seqid) {
  249:                         document.forms.$formname.elements[i].checked=value;
  250:                     }
  251:                 } else {
  252:                     document.forms.$formname.elements[i].checked=value;
  253:                 }
  254:             }
  255:         }
  256:     }
  257: </script>
  258: END
  259:     $Str .= 
  260:         '<a href="javascript:checkall(true)">'.&mt('Select All').'</a>'.
  261:         ('&nbsp;'x4).
  262:         '<a href="javascript:checkall(false)">'.&mt('Unselect All').'</a>';
  263:     $Str .= $/.'<table>'.$/;
  264:     my $iterator = $navmap->getIterator(undef, undef, undef, 1);
  265:     my $sequence_string;
  266:     my $seq_id = 0;
  267:     my @Accumulator = (&new_accumulator($env{'course.'.$cid.'.description'},
  268:                                         '',
  269:                                         '',
  270:                                         $seq_id++,
  271:                                         $inputname));
  272:     my @Sequence_Data;
  273:     while (my $curRes = $iterator->next()) {
  274:         if ($curRes == $iterator->END_MAP) {
  275:             if (ref($Accumulator[-1]) eq 'CODE') {
  276:                 my $old_accumulator = pop(@Accumulator);
  277:                 push(@Sequence_Data,&{$old_accumulator}());
  278:             }
  279:         } elsif ($curRes == $iterator->BEGIN_MAP) {
  280:             # Not much to do here.
  281:         }
  282:         next if (! ref($curRes));
  283:         if ($curRes->is_map) {
  284:             push(@Accumulator,&new_accumulator($curRes->compTitle,
  285:                                                $curRes->src,
  286:                                                $curRes->symb,
  287:                                                $seq_id++,
  288:                                                $inputname));
  289:         } elsif ($curRes->is_problem) {
  290:             if (@Accumulator && $Accumulator[-1] ne '') {
  291:                 &{$Accumulator[-1]}($curRes,
  292:                                     exists($selected->{$curRes->symb}));
  293:             }
  294:         }
  295:     }
  296:     my $course_seq = pop(@Sequence_Data);
  297:     foreach my $seq ($course_seq,@Sequence_Data) {
  298:         #my $seq = pop(@Sequence_Data);
  299:         next if (! defined($seq) || ref($seq) ne 'HASH');
  300:         $Str.= '<tr><td colspan="2">'.
  301:             '<b>'.$seq->{'title'}.'</b>'.('&nbsp;'x2).
  302:             '<a href="javascript:checkall(true,'.$seq->{'id'}.')">'.
  303:                                   &mt('Select').'</a>'.('&nbsp;'x2).
  304:             '<a href="javascript:checkall(false,'.$seq->{'id'}.')">'.
  305:                                   &mt('Unselect').'</a>'.('&nbsp;'x2).
  306:             '</td></tr>'.$/;
  307:         $Str.= $seq->{'html'};
  308:     }
  309:     $Str .= '</table>'.$/;
  310:     return $Str;
  311: }
  312: 
  313: sub new_accumulator {
  314:     my ($title,$src,$symb,$seq_id,$inputname) = @_;
  315:     my $target;
  316:     my $item_id=0;
  317:     return 
  318:         sub {
  319:             if (@_) { 
  320:                 my ($res,$checked) = @_;
  321:                 $target.='<tr><td><label>'.
  322:                     '<input type="checkbox" name="'.$inputname.'" ';
  323:                 if ($checked) {
  324:                     $target .= 'checked ';
  325:                 }
  326:                 $target .= 'id="'.$seq_id.':'.$item_id++.'" ';
  327:                 $target.= 
  328:                     'value="'.&escape($res->symb).'" />'.
  329:                     '&nbsp;'.$res->compTitle.'</label>'.
  330:                     ('&nbsp;'x2).'<a target="preview" '.
  331:                     'href="'.$res->link.'?symb='.
  332:                     &escape($res->shown_symb).'">view</a>'.
  333:                     '</td></tr>'.$/;
  334:             } else { 
  335:                 if (defined($target)) {
  336:                     return { title => $title,
  337:                              symb  => $symb,
  338:                              src   => $src,
  339:                              id    => $seq_id,
  340:                              html  => $target, }; 
  341:                 }
  342:                 return undef;
  343:             }
  344:         };
  345: }
  346: 
  347: sub get_selected_symbs {
  348:     my ($inputfield) = @_;
  349:     my $field = 'form.'.$inputfield;
  350:     my @symbs = (map {
  351:                      &unescape($_);
  352:                      } &Apache::loncommon::get_env_multiple($field));
  353:     return @symbs;
  354: }
  355: 
  356: ####################################################
  357: ####################################################
  358: 
  359: =pod
  360: 
  361: =item &make_target_id($target)
  362: 
  363: Inputs: Hash ref with the following entries:
  364:     $target->{'symb'}, $target->{'part'}, $target->{'respid'}, 
  365:     $target->{'resptype'}.
  366: 
  367: Returns: A string, suitable for a form parameter, which uniquely identifies
  368: the problem, part, and response to do statistical analysis on.
  369: 
  370: Used by Apache::lonstathelpers::ProblemSelector().
  371: 
  372: =cut
  373: 
  374: ####################################################
  375: ####################################################
  376: sub make_target_id {
  377:     my ($target) = @_;
  378:     my $id = &escape($target->{'symb'}).':'.
  379:              &escape($target->{'part'}).':'.
  380:              &escape($target->{'respid'}).':'.
  381:              &escape($target->{'resptype'});
  382:     return $id;
  383: }
  384: 
  385: ####################################################
  386: ####################################################
  387: 
  388: =pod
  389: 
  390: =item &get_target_from_id($id)
  391: 
  392: Inputs: $id, a scalar string from Apache::lonstathelpers::make_target_id().
  393: 
  394: Returns: A hash reference, $target, containing the following keys:
  395:     $target->{'symb'}, $target->{'part'}, $target->{'respid'}, 
  396:     $target->{'resptype'}.
  397: 
  398: =cut
  399: 
  400: ####################################################
  401: ####################################################
  402: sub get_target_from_id {
  403:     my ($id) = @_;
  404:     if (! ref($id)) {
  405:         my ($symb,$part,$respid,$resptype) = split(':',$id);
  406:         return ({ symb     => &unescape($symb),
  407:                   part     => &unescape($part),
  408:                   respid   => &unescape($respid),
  409:                   resptype => &unescape($resptype)});
  410:     } elsif (ref($id) eq 'ARRAY') {
  411:         my @Return;
  412:         foreach my $selected (@$id) {
  413:             my ($symb,$part,$respid,$resptype) = split(':',$selected);
  414:             push(@Return,{ symb     => &unescape($symb),
  415:                            part     => &unescape($part),
  416:                            respid   => &unescape($respid),
  417:                            resptype => &unescape($resptype)});
  418:         }
  419:         return \@Return;
  420:     }
  421: }
  422: 
  423: ####################################################
  424: ####################################################
  425: 
  426: =pod
  427: 
  428: =item &get_prev_curr_next($target,$AcceptableResponseTypes,$granularity)
  429: 
  430: Determine the problem parts or responses preceeding and following the
  431: current resource.
  432: 
  433: Inputs: $target (see &Apache::lonstathelpers::get_target_from_id())
  434:   $AcceptableResponseTypes, regular expression matching acceptable
  435:                             response types,
  436:   $granularity, either 'part', 'response', 'part_survey', or 'part_task'
  437: 
  438: Returns: three hash references, $prev, $curr, $next, which refer to the
  439: preceeding, current, or following problem parts or responses, depending
  440: on the value of $granularity.  Values of undef indicate there is no
  441: previous or next part/response.  A value of undef for all three indicates
  442: there was no match found to the current part/resource.
  443: 
  444: The hash references contain the following keys:
  445:     symb, part, resource
  446: 
  447: If $granularity eq 'response', the following ADDITIONAL keys will be present:
  448:     respid, resptype
  449: 
  450: =cut
  451: 
  452: ####################################################
  453: ####################################################
  454: sub get_prev_curr_next {
  455:     my ($target,$AcceptableResponseTypes,$granularity) = @_;
  456:     #
  457:     # Build an array with the data we need to search through
  458:     my @Resource;
  459:     my ($navmap,@sequences) = 
  460:         &Apache::lonstatistics::selected_sequences_with_assessments('all');
  461:     return $navmap if (! ref($navmap));
  462:     foreach my $seq (@sequences) {
  463:         my @resources = &get_resources($navmap,$seq);
  464:         foreach my $res (@resources) {
  465:             foreach my $part (@{$res->parts}) {
  466:                 if ($res->is_survey($part) && ($granularity eq 'part_survey')){
  467:                     push (@Resource,
  468:                           { symb     => $res->symb,
  469:                             part     => $part,
  470:                             resource => $res,
  471:                         } );
  472: 		} elsif ($res->is_task($part) && ($granularity eq 'part_task')){
  473:                     push (@Resource,
  474:                           { symb     => $res->symb,
  475:                             part     => $part,
  476:                             resource => $res,
  477:                         } );
  478:                 } elsif ($granularity eq 'part') {
  479:                     push (@Resource,
  480:                           { symb     => $res->symb,
  481:                             part     => $part,
  482:                             resource => $res,
  483:                         } );
  484:                 } elsif ($granularity eq 'response') {
  485:                     my @response_ids   = $res->responseIds($part);
  486:                     my @response_types = $res->responseType($part);
  487:                     for (my $i=0;
  488:                          $i<scalar(@response_ids);
  489:                          $i++){
  490:                         my $respid   = $response_ids[$i];
  491:                         my $resptype = $response_types[$i];
  492:                         next if ($resptype !~ m/$AcceptableResponseTypes/);
  493:                         push (@Resource,
  494:                               { symb     => $res->symb,
  495:                                 part     => $part,
  496:                                 respid   => $respid,
  497:                                 resptype => $resptype,
  498:                                 resource => $res,
  499:                                 } );
  500:                     }
  501:                 }
  502:             }
  503:         }
  504:     }
  505:     #
  506:     # Get the index of the current situation
  507:     my $curr_idx;
  508:     for ($curr_idx=0;$curr_idx<$#Resource;$curr_idx++) {
  509:         my $curr_item = $Resource[$curr_idx];
  510:         if ($granularity =~ /^(part|part_survey|part_task)$/) {
  511:             if ($curr_item->{'symb'} eq $target->{'symb'} &&
  512:                 $curr_item->{'part'} eq $target->{'part'}) {
  513:                 last;
  514:             }
  515:         } elsif ($granularity eq 'response') {
  516:             if ($curr_item->{'symb'} eq $target->{'symb'} &&
  517:                 $curr_item->{'part'} eq $target->{'part'} &&
  518:                 $curr_item->{'respid'} eq $target->{'respid'} &&
  519:                 $curr_item->{'resptype'} eq $target->{'resptype'}) {
  520:                 last;
  521:             }
  522:         }
  523:     }
  524:     my $curr_item = $Resource[$curr_idx];
  525:     if ($granularity =~ /^(part|part_survey|part_task)$/) {
  526:         if ($curr_item->{'symb'}     ne $target->{'symb'} ||
  527:             $curr_item->{'part'}     ne $target->{'part'}) {
  528:             # bogus symb - return nothing
  529:             return (undef,undef,undef);
  530:         }
  531:     } elsif ($granularity eq 'response') {
  532:         if ($curr_item->{'symb'}     ne $target->{'symb'} ||
  533:             $curr_item->{'part'}     ne $target->{'part'} ||
  534:             $curr_item->{'respid'}   ne $target->{'respid'} ||
  535:             $curr_item->{'resptype'} ne $target->{'resptype'}){
  536:             # bogus symb - return nothing
  537:             return (undef,undef,undef);
  538:         }
  539:     }
  540:     #
  541:     # Now just pick up the data we need
  542:     my ($prev,$curr,$next);
  543:     if ($curr_idx == 0) {
  544:         $prev = undef;
  545:         $curr = $Resource[$curr_idx  ];
  546:         $next = $Resource[$curr_idx+1];
  547:     } elsif ($curr_idx == $#Resource) {
  548:         $prev = $Resource[$curr_idx-1];
  549:         $curr = $Resource[$curr_idx  ];
  550:         $next = undef;
  551:     } else {
  552:         $prev = $Resource[$curr_idx-1];
  553:         $curr = $Resource[$curr_idx  ];
  554:         $next = $Resource[$curr_idx+1];
  555:     }
  556:     return ($navmap,$prev,$curr,$next);
  557: }
  558: 
  559: 
  560: #####################################################
  561: #####################################################
  562: 
  563: =pod
  564: 
  565: =item GetStudentAnswers($r,$problem,$Students)
  566: 
  567: Determines the correct answer for a set of students on a given problem.
  568: The students answers are stored in the student hashes pointed to by the
  569: array @$Students under the key 'answer'.
  570: 
  571: Inputs: $r
  572: $problem: hash reference containing the keys 'resource', 'part', and 'respid'.
  573: $Students: reference to array containing student hashes (need 'username', 
  574:     'domain').  
  575: 
  576: Returns: nothing 
  577: 
  578: =cut
  579: 
  580: #####################################################
  581: #####################################################
  582: sub GetStudentAnswers {
  583:     my ($r,$problem,$Students,$formname,$inputname) = @_;
  584:     my %answers;
  585:     my $status_type;
  586:     if (defined($formname)) {
  587:         $status_type = 'inline';
  588:     } else {
  589:         $status_type = 'popup';
  590:     }    
  591:     my $c = $r->connection();
  592:     my %Answers;
  593:     my ($resource,$partid,$respid) = ($problem->{'resource'},
  594:                                       $problem->{'part'},
  595:                                       $problem->{'respid'});
  596:     # Read in the cache (if it exists) before we start timing things.
  597:     &Apache::lonstathelpers::ensure_proper_cache($resource->{'symb'});
  598:     # Open progress window
  599:     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
  600:         ($r,'Student Answer Compilation Status',
  601:          'Student Answer Compilation Progress', scalar(@$Students),
  602:          $status_type,undef,$formname,$inputname);
  603:     $r->rflush();
  604:     foreach my $student (@$Students) {
  605:         last if ($c->aborted());
  606:         my $sname = $student->{'username'};
  607:         my $sdom = $student->{'domain'};
  608:         my $answer = &Apache::lonstathelpers::get_student_answer
  609:             ($resource,$sname,$sdom,$partid,$respid);
  610:         &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
  611:                                                  &mt('last student'));
  612:         $answers{$answer}++;
  613:         $student->{'answer'} = $answer;
  614:     }
  615:     &Apache::lonstathelpers::write_analysis_cache();
  616:     return if ($c->aborted());
  617:     $r->rflush();
  618:     # close progress window
  619:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
  620:     return \%answers;
  621: }
  622: 
  623: #####################################################
  624: #####################################################
  625: 
  626: =pod
  627: 
  628: =item analyze_problem_as_student
  629: 
  630: Analyzes a homework problem for a student
  631: 
  632: Inputs: $resource: a resource object
  633:         $sname, $sdom, $partid, $respid
  634: 
  635: Returns: the problem analysis hash
  636: 
  637: =cut
  638: 
  639: #####################################################
  640: #####################################################
  641: sub analyze_problem_as_student {
  642:     my ($resource,$sname,$sdom) = @_;
  643:     if (ref($resource) ne 'HASH') {
  644:         my $res = $resource;
  645:         $resource = { 'src' => $res->src,
  646:                       'symb' => $res->symb,
  647:                       'parts' => $res->parts };
  648:         foreach my $part (@{$resource->{'parts'}}) {
  649:             $resource->{'partdata'}->{$part}->{'ResponseIds'}=
  650:                 [$res->responseIds($part)];
  651:         }
  652:     }
  653:     my $url = $resource->{'src'};
  654:     my $symb = $resource->{'symb'};
  655:     my $analysis = &get_from_analysis_cache($sname,$sdom,$symb);
  656:     if (! defined($analysis)) {
  657:         my $courseid = $env{'request.course.id'};
  658:         my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze',
  659:                                             'grade_domain' => $sdom,
  660:                                             'grade_username' => $sname,
  661:                                             'grade_symb' => $symb,
  662:                                             'grade_courseid' => $courseid));
  663:         (my $garbage,$analysis)=split(/_HASH_REF__/,$Answ,2);
  664:         &store_analysis($sname,$sdom,$symb,$analysis);
  665:     }
  666:     my %Answer=&Apache::lonnet::str2hash($analysis);
  667:     #
  668:     return \%Answer;
  669: }
  670: 
  671: #####################################################
  672: #####################################################
  673: 
  674: =pod
  675: 
  676: =item get_student_answer
  677: 
  678: Analyzes a homework problem for a particular student and returns the correct 
  679: answer.  Attempts to put together an answer for problem types 
  680: that do not natively support it.
  681: 
  682: Inputs: $resource: a resource object (from navmaps or hash from loncoursedata)
  683:         $sname, $sdom, $partid, $respid
  684: 
  685: Returns: $answer
  686: 
  687: If $partid and $respid are specified, $answer is simply a scalar containing
  688: the correct answer for the response.
  689: 
  690: If $partid or $respid are undefined, $answer will be a hash reference with
  691: keys $partid.'.'.$respid.'.answer'.
  692: 
  693: =cut
  694: 
  695: #####################################################
  696: #####################################################
  697: sub get_student_answer {
  698:     my ($resource,$sname,$sdom,$partid,$respid) = @_;
  699:     #
  700:     if (ref($resource) ne 'HASH') {
  701:         my $res = $resource;
  702:         $resource = { 'src' => $res->src,
  703:                       'symb' => $res->symb,
  704:                       'parts' => $res->parts };
  705:         foreach my $part (@{$resource->{'parts'}}) {
  706:             $resource->{'partdata'}->{$part}->{'ResponseIds'}=
  707:                 [$res->responseIds($part)];
  708:         }
  709:     }
  710:     #
  711:     my $analysis = 
  712:         &analyze_problem_as_student($resource,$sname,$sdom);
  713:     my $answer;
  714:     foreach my $partid (@{$resource->{'parts'}}) {
  715:         my $partdata = $resource->{'partdata'}->{$partid};
  716:         foreach my $respid (@{$partdata->{'ResponseIds'}}) {
  717:             my $prefix = $partid.'.'.$respid;
  718:             my $key = $prefix.'.answer';
  719:             $answer->{$partid}->{$respid} = 
  720:                 &get_answer($prefix,$key,%$analysis);
  721:         }
  722:     }
  723:     my $returnvalue;
  724:     if (! defined($partid)) {
  725:         $returnvalue = $answer;
  726:     } elsif (! defined($respid)) {
  727:         $returnvalue = $answer->{$partid};
  728:     } else {
  729:         $returnvalue = $answer->{$partid}->{$respid};
  730:     }
  731:     return $returnvalue;
  732: }
  733: 
  734: sub get_answer {
  735:     my ($prefix,$key,%Answer) = @_;
  736:     my $returnvalue;
  737:     if (exists($Answer{$key})) {
  738: 	if (ref($Answer{$key}) eq 'HASH') {
  739: 	    my $which = 'INTERNAL';
  740: 	    if (!exists($Answer{$key}{$which})) {
  741: 		$which = (sort(keys(%{ $Answer{$key} })))[0];
  742: 	    }
  743: 	    my $student_answer = $Answer{$key}{$which}[0][0];
  744: 	    $returnvalue = $student_answer; 
  745: 	} else {
  746: 	    &Apache::lonnet::logthis("error analyzing problem. got a answer of type ".ref($Answer{$key}));
  747: 	}
  748:     } else {
  749:         if (exists($Answer{$prefix.'.shown'})) {
  750:             # The response has foils
  751:             my %values;
  752:             while (my ($k,$v) = each(%Answer)) {
  753:                 next if ($k !~ /^$prefix\.foil\.(value|area)\.(.*)$/);
  754:                 my $foilname = $2;
  755:                 $values{$foilname}=$v;
  756:             }
  757:             foreach my $foil (@{$Answer{$prefix.'.shown'}}) {
  758:                 if (ref($values{$foil}) eq 'ARRAY') {
  759:                     $returnvalue.=&HTML::Entities::encode($foil,'<>&"').'='.
  760:                         join(',',map {&HTML::Entities::encode($_,'<>&"')} @{$values{$foil}}).'&';
  761:                 } else {
  762:                     $returnvalue.=&HTML::Entities::encode($foil,'<>&"').'='.
  763:                         &HTML::Entities::encode($values{$foil},'<>&"').'&';
  764:                 }
  765:             }
  766:             $returnvalue =~ s/ /\%20/g;
  767:             chop ($returnvalue);
  768:         }
  769:     }
  770:     return $returnvalue;
  771: }
  772: 
  773: #####################################################
  774: #####################################################
  775: 
  776: =pod
  777: 
  778: =item Caching routines
  779: 
  780: =over 4
  781: 
  782: =item &load_analysis_cache($symb)
  783: 
  784: Loads the cache for the given symb into memory from disk.  
  785: Requires the cache filename be set.  
  786: Only should be called by &ensure_proper_cache.
  787: 
  788: =cut
  789: 
  790: #####################################################
  791: #####################################################
  792: {
  793:     my $cache_filename = undef;
  794:     my $current_symb = undef;
  795:     my %cache;
  796: 
  797: sub load_analysis_cache {
  798:     my ($symb) = @_;
  799:     return if (! defined($cache_filename));
  800:     if (! defined($current_symb) || $current_symb ne $symb) {
  801:         undef(%cache);
  802:         my $storedstring;
  803:         my %cache_db;
  804:         if (tie(%cache_db,'GDBM_File',$cache_filename,&GDBM_READER(),0640)) {
  805:             $storedstring = $cache_db{&escape($symb)};
  806:             untie(%cache_db);
  807:         }
  808:         if (defined($storedstring)) {
  809:             %cache = %{thaw($storedstring)};
  810:         }
  811:     }
  812:     return;
  813: }
  814: 
  815: #####################################################
  816: #####################################################
  817: 
  818: =pod
  819: 
  820: =item &get_from_analysis_cache($sname,$sdom,$symb,$partid,$respid)
  821: 
  822: Returns the appropriate data from the cache, or undef if no data exists.
  823: 
  824: =cut
  825: 
  826: #####################################################
  827: #####################################################
  828: sub get_from_analysis_cache {
  829:     my ($sname,$sdom,$symb) = @_;
  830:     &ensure_proper_cache($symb);
  831:     my $returnvalue;
  832:     if (exists($cache{$sname.':'.$sdom})) {
  833:         $returnvalue = $cache{$sname.':'.$sdom};
  834:     } else {
  835:         $returnvalue = undef;
  836:     }
  837:     return $returnvalue;
  838: }
  839: 
  840: #####################################################
  841: #####################################################
  842: 
  843: =pod
  844: 
  845: =item &write_analysis_cache($symb)
  846: 
  847: Writes the in memory cache to disk so that it can be read in with
  848: &load_analysis_cache($symb).
  849: 
  850: =cut
  851: 
  852: #####################################################
  853: #####################################################
  854: sub write_analysis_cache {
  855:     return if (! defined($current_symb) || ! defined($cache_filename));
  856:     my %cache_db;
  857:     my $key = &escape($current_symb);
  858:     if (tie(%cache_db,'GDBM_File',$cache_filename,&GDBM_WRCREAT(),0640)) {
  859:         my $storestring = freeze(\%cache);
  860:         $cache_db{$key}=$storestring;
  861:         $cache_db{$key.'.time'}=time;
  862:         untie(%cache_db);
  863:     }
  864:     undef(%cache);
  865:     undef($current_symb);
  866:     undef($cache_filename);
  867:     return;
  868: }
  869: 
  870: #####################################################
  871: #####################################################
  872: 
  873: =pod
  874: 
  875: =item &ensure_proper_cache($symb)
  876: 
  877: Called to make sure we have the proper cache set up.  This is called
  878: prior to every analysis lookup.
  879: 
  880: =cut
  881: 
  882: #####################################################
  883: #####################################################
  884: sub ensure_proper_cache {
  885:     my ($symb) = @_;
  886:     my $cid = $env{'request.course.id'};
  887:     my $new_filename =  '/home/httpd/perl/tmp/'.
  888:         'problemanalysis_'.$cid.'_analysis_cache.db';
  889:     if (! defined($cache_filename) ||
  890:         $cache_filename ne $new_filename ||
  891:         ! defined($current_symb)   ||
  892:         $current_symb ne $symb) {
  893:         $cache_filename = $new_filename;
  894:         # Notice: $current_symb is not set to $symb until after the cache is
  895:         # loaded.  This is what tells &load_analysis_cache to load in a new
  896:         # symb cache.
  897:         &load_analysis_cache($symb);
  898:         $current_symb = $symb;
  899:     }
  900: }
  901: 
  902: #####################################################
  903: #####################################################
  904: 
  905: =pod
  906: 
  907: =item &store_analysis($sname,$sdom,$symb,$partid,$respid,$dataset)
  908: 
  909: Stores the analysis data in the in memory cache.
  910: 
  911: =cut
  912: 
  913: #####################################################
  914: #####################################################
  915: sub store_analysis {
  916:     my ($sname,$sdom,$symb,$dataset) = @_;
  917:     return if ($symb ne $current_symb);
  918:     $cache{$sname.':'.$sdom}=$dataset;
  919:     return;
  920: }
  921: 
  922: }
  923: #####################################################
  924: #####################################################
  925: 
  926: =pod
  927: 
  928: =back
  929: 
  930: =cut
  931: 
  932: #####################################################
  933: #####################################################
  934: 
  935: ##
  936: ## The following is copied from datecalc1.pl, part of the 
  937: ## Spreadsheet::WriteExcel CPAN module.
  938: ##
  939: ##
  940: ######################################################################
  941: #
  942: # Demonstration of writing date/time cells to Excel spreadsheets,
  943: # using UNIX/Perl time as source of date/time.
  944: #
  945: # Copyright 2000, Andrew Benham, adsb@bigfoot.com
  946: #
  947: ######################################################################
  948: #
  949: # UNIX/Perl time is the time since the Epoch (00:00:00 GMT, 1 Jan 1970)
  950: # measured in seconds.
  951: #
  952: # An Excel file can use exactly one of two different date/time systems.
  953: # In these systems, a floating point number represents the number of days
  954: # (and fractional parts of the day) since a start point. The floating point
  955: # number is referred to as a 'serial'.
  956: # The two systems ('1900' and '1904') use different starting points:
  957: #  '1900'; '1.00' is 1 Jan 1900 BUT 1900 is erroneously regarded as
  958: #          a leap year - see:
  959: #            http://support.microsoft.com/support/kb/articles/Q181/3/70.asp
  960: #          for the excuse^H^H^H^H^H^Hreason.
  961: #  '1904'; '1.00' is 2 Jan 1904.
  962: #
  963: # The '1904' system is the default for Apple Macs. Windows versions of
  964: # Excel have the option to use the '1904' system.
  965: #
  966: # Note that Visual Basic's "DateSerial" function does NOT erroneously
  967: # regard 1900 as a leap year, and thus its serials do not agree with
  968: # the 1900 serials of Excel for dates before 1 Mar 1900.
  969: #
  970: # Note that StarOffice (at least at version 5.2) does NOT erroneously
  971: # regard 1900 as a leap year, and thus its serials do not agree with
  972: # the 1900 serials of Excel for dates before 1 Mar 1900.
  973: #
  974: ######################################################################
  975: #
  976: # Calculation description
  977: # =======================
  978: #
  979: # 1900 system
  980: # -----------
  981: # Unix time is '0' at 00:00:00 GMT 1 Jan 1970, i.e. 70 years after 1 Jan 1900.
  982: # Of those 70 years, 17 (1904,08,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68)
  983: # were leap years with an extra day.
  984: # Thus there were 17 + 70*365 days = 25567 days between 1 Jan 1900 and
  985: # 1 Jan 1970.
  986: # In the 1900 system, '1' is 1 Jan 1900, but as 1900 was not a leap year
  987: # 1 Jan 1900 should really be '2', so 1 Jan 1970 is '25569'.
  988: #
  989: # 1904 system
  990: # -----------
  991: # Unix time is '0' at 00:00:00 GMT 1 Jan 1970, i.e. 66 years after 1 Jan 1904.
  992: # Of those 66 years, 17 (1904,08,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68)
  993: # were leap years with an extra day.
  994: # Thus there were 17 + 66*365 days = 24107 days between 1 Jan 1904 and
  995: # 1 Jan 1970.
  996: # In the 1904 system, 2 Jan 1904 being '1', 1 Jan 1970 is '24107'.
  997: #
  998: ######################################################################
  999: #
 1000: # Copyright (c) 2000, Andrew Benham.
 1001: # This program is free software. It may be used, redistributed and/or
 1002: # modified under the same terms as Perl itself.
 1003: #
 1004: # Andrew Benham, adsb@bigfoot.com
 1005: # London, United Kingdom
 1006: # 11 Nov 2000
 1007: #
 1008: ######################################################################
 1009: #-----------------------------------------------------------
 1010: # calc_serial()
 1011: #
 1012: # Called with (up to) 2 parameters.
 1013: #   1.  Unix timestamp.  If omitted, uses current time.
 1014: #   2.  GMT flag. Set to '1' to return serial in GMT.
 1015: #       If omitted, returns serial in appropriate timezone.
 1016: #
 1017: # Returns date/time serial according to $DATE_SYSTEM selected
 1018: #-----------------------------------------------------------
 1019: sub calc_serial {
 1020:     # Use 1900 date system on all platforms other than Apple Mac (for which
 1021:     # use 1904 date system).
 1022:     my $DATE_SYSTEM = ($^O eq 'MacOS') ? 1 : 0;
 1023:     my $time = (defined $_[0]) ? $_[0] : time();
 1024:     my $gmtflag = (defined $_[1]) ? $_[1] : 0;
 1025:     #
 1026:     # Divide timestamp by number of seconds in a day.
 1027:     # This gives a date serial with '0' on 1 Jan 1970.
 1028:     my $serial = $time / 86400;
 1029:     #
 1030:     # Adjust the date serial by the offset appropriate to the
 1031:     # currently selected system (1900/1904).
 1032:     if ($DATE_SYSTEM == 0) {        # use 1900 system
 1033:         $serial += 25569;
 1034:     } else {                        # use 1904 system
 1035:         $serial += 24107;
 1036:     }
 1037:     #
 1038:     unless ($gmtflag) {
 1039:         # Now have a 'raw' serial with the right offset. But this
 1040:         # gives a serial in GMT, which is false unless the timezone
 1041:         # is GMT. We need to adjust the serial by the appropriate
 1042:         # timezone offset.
 1043:         # Calculate the appropriate timezone offset by seeing what
 1044:         # the differences between localtime and gmtime for the given
 1045:         # time are.
 1046:         #    
 1047:         my @gmtime = gmtime($time);
 1048:         my @ltime  = localtime($time);
 1049:         #
 1050:         # For the first 7 elements of the two arrays, adjust the
 1051:         # date serial where the elements differ.
 1052:         for (0 .. 6) {
 1053:             my $diff = $ltime[$_] - $gmtime[$_];
 1054:             if ($diff) {
 1055:                 $serial += _adjustment($diff,$_);
 1056:             }
 1057:         }
 1058:     }
 1059:     #
 1060:     # Perpetuate the error that 1900 was a leap year by decrementing
 1061:     # the serial if we're using the 1900 system and the date is prior to
 1062:     # 1 Mar 1900. This has the effect of making serial value '60'
 1063:     # 29 Feb 1900.
 1064:     #
 1065:     # This fix only has any effect if UNIX/Perl time on the platform
 1066:     # can represent 1900. Many can't.
 1067:     #
 1068:     unless ($DATE_SYSTEM) {
 1069:         $serial-- if ($serial < 61);    # '61' is 1 Mar 1900
 1070:     }
 1071:     return $serial;
 1072: }
 1073: 
 1074: sub _adjustment {
 1075:     # Based on the difference in the localtime/gmtime array elements
 1076:     # number, return the adjustment required to the serial.
 1077:     #
 1078:     # We only look at some elements of the localtime/gmtime arrays:
 1079:     #    seconds    unlikely to be different as all known timezones
 1080:     #               have an offset of integral multiples of 15 minutes,
 1081:     #               but it's easy to do.
 1082:     #    minutes    will be different for timezone offsets which are
 1083:     #               not an exact number of hours.
 1084:     #    hours      very likely to be different.
 1085:     #    weekday    will differ when localtime/gmtime difference
 1086:     #               straddles midnight.
 1087:     #
 1088:     # Assume that difference between localtime and gmtime is less than
 1089:     # 5 days, then don't have to do maths for day of month, month number,
 1090:     # year number, etc...
 1091:     #
 1092:     my ($delta,$element) = @_;
 1093:     my $adjust = 0;
 1094:     #
 1095:     if ($element == 0) {            # Seconds
 1096:         $adjust = $delta/86400;         # 60 * 60 * 24
 1097:     } elsif ($element == 1) {       # Minutes
 1098:         $adjust = $delta/1440;          # 60 * 24
 1099:     } elsif ($element == 2) {       # Hours
 1100:         $adjust = $delta/24;            # 24
 1101:     } elsif ($element == 6) {       # Day of week number
 1102:         # Catch difference straddling Sat/Sun in either direction
 1103:         $delta += 7 if ($delta < -4);
 1104:         $delta -= 7 if ($delta > 4);
 1105:         #    
 1106:         $adjust = $delta;
 1107:     }
 1108:     return $adjust;
 1109: }
 1110: 
 1111: ###########################################################
 1112: ###########################################################
 1113: 
 1114: =pod
 1115: 
 1116: =item get_problem_data
 1117: 
 1118: Returns a data structure describing the problem.
 1119: 
 1120: Inputs: $url
 1121: 
 1122: Returns: %Partdata
 1123: 
 1124: =cut
 1125: 
 1126: ## note: we must force each foil and option to not begin or end with
 1127: ##       spaces as they are stored without such data.
 1128: ##
 1129: ###########################################################
 1130: ###########################################################
 1131: sub get_problem_data {
 1132:     my ($url) = @_;
 1133:     my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze'));
 1134:     (my $garbage,$Answ)=split(/_HASH_REF__/,$Answ,2);
 1135:     my %Answer;
 1136:     %Answer=&Apache::lonnet::str2hash($Answ);
 1137:     my %Partdata;
 1138:     foreach my $part (@{$Answer{'parts'}}) {
 1139:         while (my($key,$value) = each(%Answer)) {
 1140:             #
 1141:             # Logging code:
 1142:             if (0) {
 1143:                 &Apache::lonnet::logthis($part.' got key "'.$key.'"');
 1144:                 if (ref($value) eq 'ARRAY') {
 1145:                     &Apache::lonnet::logthis('    @'.join(',',@$value));
 1146:                 } else {
 1147:                     &Apache::lonnet::logthis('    '.$value);
 1148:                 }
 1149:             }
 1150:             # End of logging code
 1151:             next if ($key !~ /^\Q$part\E/);
 1152:             $key =~ s/^\Q$part\E\.//;
 1153:             if (ref($value) eq 'ARRAY') {
 1154:                 if ($key eq 'options') {
 1155:                     $Partdata{$part}->{'_Options'}=$value;
 1156:                 } elsif ($key eq 'concepts') {
 1157:                     $Partdata{$part}->{'_Concepts'}=$value;
 1158:                 } elsif ($key eq 'items') {
 1159:                     $Partdata{$part}->{'_Items'}=$value;
 1160:                 } elsif ($key =~ /^concept\.(.*)$/) {
 1161:                     my $concept = $1;
 1162:                     foreach my $foil (@$value) {
 1163:                         $Partdata{$part}->{'_Foils'}->{$foil}->{'_Concept'}=
 1164:                                                                       $concept;
 1165:                     }
 1166:                 } elsif ($key =~ /^(unit|incorrect|answer|ans_low|ans_high|str_type)$/) {
 1167:                     $Partdata{$part}->{$key}=$value;
 1168:                 }
 1169:             } else {
 1170:                 if ($key=~ /^foil\.text\.(.*)$/) {
 1171:                     my $foil = $1;
 1172:                     $Partdata{$part}->{'_Foils'}->{$foil}->{'name'}=$foil;
 1173:                     $value =~ s/(\s*$|^\s*)//g;
 1174:                     $Partdata{$part}->{'_Foils'}->{$foil}->{'text'}=$value;
 1175:                 } elsif ($key =~ /^foil\.value\.(.*)$/) {
 1176:                     my $foil = $1;
 1177:                     $Partdata{$part}->{'_Foils'}->{$foil}->{'value'}=$value;
 1178:                 } elsif ($key eq 'answercomputed') {
 1179:                     $Partdata{$part}->{'answercomputed'} = $value;
 1180:                 }
 1181:             }
 1182:         }
 1183:     }
 1184:     # Further debugging code
 1185:     if (0) {
 1186:         &Apache::lonnet::logthis('lonstathelpers::get_problem_data');
 1187:         &log_hash_ref(\%Partdata);
 1188:     }
 1189:     return %Partdata;
 1190: }
 1191: 
 1192: sub log_array_ref {
 1193:     my ($arrayref,$prefix) = @_;
 1194:     return if (ref($arrayref) ne 'ARRAY');
 1195:     if (! defined($prefix)) { $prefix = ''; };
 1196:     foreach my $v (@$arrayref) {
 1197:         if (ref($v) eq 'ARRAY') {
 1198:             &log_array_ref($v,$prefix.'  ');
 1199:         } elsif (ref($v) eq 'HASH') {
 1200:             &log_hash_ref($v,$prefix.'  ');
 1201:         } else {
 1202:             &Apache::lonnet::logthis($prefix.'"'.$v.'"');
 1203:         }
 1204:     }
 1205: }
 1206: 
 1207: sub log_hash_ref {
 1208:     my ($hashref,$prefix) = @_;
 1209:     return if (ref($hashref) ne 'HASH');
 1210:     if (! defined($prefix)) { $prefix = ''; };
 1211:     while (my ($k,$v) = each(%$hashref)) {
 1212:         if (ref($v) eq 'ARRAY') {
 1213:             &Apache::lonnet::logthis($prefix.'"'.$k.'" = array');
 1214:             &log_array_ref($v,$prefix.'  ');
 1215:         } elsif (ref($v) eq 'HASH') {
 1216:             &Apache::lonnet::logthis($prefix.'"'.$k.'" = hash');
 1217:             &log_hash_ref($v,$prefix.'  ');
 1218:         } else {
 1219:             &Apache::lonnet::logthis($prefix.'"'.$k.'" => "'.$v.'"');
 1220:         }
 1221:     }
 1222: }
 1223: ####################################################
 1224: ####################################################
 1225: 
 1226: =pod
 1227: 
 1228: =item &limit_by_time()
 1229: 
 1230: =cut
 1231: 
 1232: ####################################################
 1233: ####################################################
 1234: sub limit_by_time_form {
 1235:     my $Starttime_form = '';
 1236:     my $starttime = &Apache::lonhtmlcommon::get_date_from_form
 1237:         ('limitby_startdate');
 1238:     my $endtime = &Apache::lonhtmlcommon::get_date_from_form
 1239:         ('limitby_enddate');
 1240:     if (! defined($endtime)) {
 1241:         $endtime = time;
 1242:     }
 1243:     if (! defined($starttime)) {
 1244:         $starttime = $endtime - 60*60*24*7;
 1245:     }
 1246:     my $state;
 1247:     if (&limit_by_time()) {
 1248:         $state = '';
 1249:     } else {
 1250:         $state = 'disabled';
 1251:     }
 1252:     my $startdateform = &Apache::lonhtmlcommon::date_setter
 1253:         ('Statistics','limitby_startdate',$starttime,undef,undef,$state);
 1254:     my $enddateform = &Apache::lonhtmlcommon::date_setter
 1255:         ('Statistics','limitby_enddate',$endtime,undef,undef,$state);
 1256:     my $Str;
 1257:     $Str .= '<script language="Javascript" >';
 1258:     $Str .= 'function toggle_limitby_activity(state) {';
 1259:     $Str .= '    if (state) {';
 1260:     $Str .= '        limitby_startdate_enable();';
 1261:     $Str .= '        limitby_enddate_enable();';
 1262:     $Str .= '    } else {';
 1263:     $Str .= '        limitby_startdate_disable();';
 1264:     $Str .= '        limitby_enddate_disable();';
 1265:     $Str .= '    }';    
 1266:     $Str .= '}';
 1267:     $Str .= '</script>';
 1268:     $Str .= '<fieldset>';
 1269:     my $timecheckbox = '<input type="checkbox" name="limit_by_time" ';
 1270:     if (&limit_by_time()) {
 1271:         $timecheckbox .= ' checked ';
 1272:     } 
 1273:     $timecheckbox .= 'OnChange="javascript:toggle_limitby_activity(this.checked);" ';
 1274:     $timecheckbox .= ' />';
 1275:     $Str .= '<legend><label>'.&mt('[_1] Limit by time',$timecheckbox).'</label></legend>';
 1276:     $Str .= &mt('Start Time: [_1]',$startdateform).'<br />';
 1277:     $Str .= &mt('&nbsp;End Time: [_1]',$enddateform).'<br />';
 1278:     $Str .= '</fieldset>';
 1279:     return $Str;
 1280: }
 1281: 
 1282: sub limit_by_time {
 1283:     if (exists($env{'form.limit_by_time'}) &&
 1284:         $env{'form.limit_by_time'} ne '' ) {
 1285:         return 1;
 1286:     } else {
 1287:         return 0;
 1288:     }
 1289: }
 1290: 
 1291: sub get_time_limits {
 1292:     my $starttime = &Apache::lonhtmlcommon::get_date_from_form
 1293:         ('limitby_startdate');
 1294:     my $endtime = &Apache::lonhtmlcommon::get_date_from_form
 1295:         ('limitby_enddate');
 1296:     return ($starttime,$endtime);
 1297: }
 1298: 
 1299: ####################################################
 1300: ####################################################
 1301: 
 1302: =pod
 1303: 
 1304: =item &manage_caches
 1305: 
 1306: Inputs: $r, apache request object
 1307: 
 1308: Returns: An array of scalars containing html for buttons.
 1309: 
 1310: =cut
 1311: 
 1312: ####################################################
 1313: ####################################################
 1314: sub manage_caches {
 1315:     my ($r,$formname,$inputname,$update_message) = @_;
 1316:     &Apache::loncoursedata::clear_internal_caches();
 1317:     my $sectionkey = 
 1318:         join(',',
 1319:              map {
 1320:                      &escape($_);
 1321:                  } sort(&Apache::lonstatistics::get_selected_sections())
 1322:              );
 1323:     my $statuskey = $Apache::lonstatistics::enrollment_status;
 1324:     if (exists($env{'form.ClearCache'}) || 
 1325:         exists($env{'form.updatecaches'}) || 
 1326:         (exists($env{'form.firstrun'}) && $env{'form.firstrun'} ne 'no') ||
 1327:         (exists($env{'form.prevsection'}) &&
 1328:             $env{'form.prevsection'} ne $sectionkey) ||
 1329:         (exists($env{'form.prevenrollstatus'}) &&
 1330:             $env{'form.prevenrollstatus'} ne $statuskey)
 1331:         ) {
 1332:         if (defined($update_message)) {
 1333:             $r->print($update_message);
 1334:         }
 1335:         if (0) {
 1336:             &Apache::lonnet::logthis('Updating mysql student data caches');
 1337:         }
 1338:         &gather_full_student_data($r,$formname,$inputname);
 1339:     }
 1340:     #
 1341:     my @Buttons = 
 1342:         ('<input type="submit" name="ClearCache" '.
 1343:              'value="'.&mt('Clear Caches').'" />',
 1344:          '<input type="submit" name="updatecaches" '.
 1345:              'value="'.&mt('Update Caches').'" />'.
 1346:          &Apache::loncommon::help_open_topic('Statistics_Cache'),
 1347:          '<input type="hidden" name="prevsection" value="'.$sectionkey.'" />',
 1348:          '<input type="hidden" name="prevenrollstatus" value="'.$statuskey.'" />'
 1349:          );
 1350:     #
 1351:     if (! exists($env{'form.firstrun'})) {
 1352:         $r->print('<input type="hidden" name="firstrun" value="yes" />');
 1353:     } else {
 1354:         $r->print('<input type="hidden" name="firstrun" value="no" />');
 1355:     }
 1356:     #
 1357:     return @Buttons;
 1358: }
 1359: 
 1360: sub gather_full_student_data {
 1361:     my ($r,$formname,$inputname) = @_;
 1362:     my $status_type;
 1363:     if (defined($formname)) {
 1364:         $status_type = 'inline';
 1365:     } else {
 1366:         $status_type = 'popup';
 1367:     }
 1368:     my $c = $r->connection();
 1369:     #
 1370:     &Apache::loncoursedata::clear_internal_caches();
 1371:     #
 1372:     my @Students = @Apache::lonstatistics::Students;
 1373:     #
 1374:     # Open the progress window
 1375:     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
 1376:         ($r,&mt('Student Data Compilation Status'),
 1377:          &mt('Student Data Compilation Progress'), scalar(@Students),
 1378:          $status_type,undef,$formname,$inputname);
 1379:     #
 1380:     while (my $student = shift @Students) {
 1381:         return if ($c->aborted());
 1382:         my $status = &Apache::loncoursedata::ensure_current_full_data
 1383:             ($student->{'username'},$student->{'domain'},
 1384:              $env{'request.course.id'});
 1385:         &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
 1386:                                                  &mt('last student'));
 1387:     }
 1388:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
 1389:     $r->rflush();
 1390:     return;
 1391: }
 1392: 
 1393: ####################################################
 1394: ####################################################
 1395: 
 1396: =pod
 1397: 
 1398: =item &submission_report_form
 1399: 
 1400: Input: The originating reportSelected value for the current stats page.
 1401: 
 1402: Output: Scalar containing HTML with needed form elements and a link to 
 1403: the student submission reports page.
 1404: 
 1405: =cut
 1406: 
 1407: ####################################################
 1408: ####################################################
 1409: sub submission_report_form {
 1410:     my ($original_report) = @_;
 1411:     # Note: In the link below we change the reportSelected value.  If
 1412:     # the user hits the 'back' button on the browser after getting their
 1413:     # student submissions report, this value may still be around.  So we
 1414:     # output a script block to set it properly.  If the $original_report
 1415:     # value is unset, you are just asking for trouble.
 1416:     if (! defined($original_report)) {
 1417:         &Apache::lonnet::logthis
 1418:             ('someone called lonstathelpers::submission_report_form without '.
 1419:              ' enough input.');
 1420:     }
 1421:     my $html = $/.
 1422:         '<script type="Text/JavaScript">'.
 1423:         "document.Statistics.reportSelected.value='$original_report';".
 1424:         '</script>'.
 1425:         '<input type="hidden" name="correctans" value="true" />'.
 1426:         '<input type="hidden" name="prob_status" value="true" />'.
 1427:         '<input type="hidden" name="all_sub" value="true" />';
 1428:     my $output_selector = $/.'<select name="output">'.$/;
 1429:     foreach ('HTML','Excel','CSV') {
 1430:         $output_selector .= '    <option value="'.lc($_).'"';
 1431:         if ($env{'form.output'} eq lc($_)) {
 1432:             $output_selector .= ' selected ';
 1433:         }
 1434:         $output_selector .='>'.&mt($_).'</option>'.$/;
 1435:     } 
 1436:     $output_selector .= '</select>'.$/;
 1437:     my $link = '<a href="javascript:'.
 1438:        q{document.Statistics.reportSelected.value='student_submission_reports';}.
 1439:        'document.Statistics.submit();">';
 1440:     $html.= &mt('View data as [_1] [_2]go[_3]',$output_selector,
 1441:                 $link,'</a>').$/;
 1442:     return $html
 1443: }
 1444: 
 1445: ####################################################
 1446: ####################################################
 1447: 
 1448: =pod
 1449: 
 1450: =back
 1451: 
 1452: =cut
 1453: 
 1454: ####################################################
 1455: ####################################################
 1456: 
 1457: 1;
 1458: 
 1459: __END__

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