File:  [LON-CAPA] / loncom / interface / spreadsheet / studentcalc.pm
Revision 1.40: download - view: text, annotated - select for diffs
Tue May 30 12:46:26 2006 UTC (18 years, 1 month ago) by www
Branches: MAIN
CVS tags: version_2_2_1, version_2_2_0, version_2_1_99_3, version_2_1_99_2, version_2_1_99_1, version_2_1_99_0, HEAD
&Apache::lonnet::unescape -> &unescape
&Apache::lonnet::escape -> &escape

    1: #
    2: # $Id: studentcalc.pm,v 1.40 2006/05/30 12:46:26 www Exp $
    3: #
    4: # Copyright Michigan State University Board of Trustees
    5: #
    6: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    7: #
    8: # LON-CAPA is free software; you can redistribute it and/or modify
    9: # it under the terms of the GNU General Public License as published by
   10: # the Free Software Foundation; either version 2 of the License, or
   11: # (at your option) any later version.
   12: #
   13: # LON-CAPA is distributed in the hope that it will be useful,
   14: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   15: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   16: # GNU General Public License for more details.
   17: #
   18: # You should have received a copy of the GNU General Public License
   19: # along with LON-CAPA; if not, write to the Free Software
   20: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   21: #
   22: # /home/httpd/html/adm/gpl.txt
   23: #
   24: # http://www.lon-capa.org/
   25: #
   26: # The LearningOnline Network with CAPA
   27: # Spreadsheet/Grades Display Handler
   28: #
   29: # POD required stuff:
   30: 
   31: =head1 NAME
   32: 
   33: studentcalc
   34: 
   35: =head1 SYNOPSIS
   36: 
   37: =head1 DESCRIPTION
   38: 
   39: =over 4
   40: 
   41: =cut
   42: 
   43: ###################################################
   44: ###                 StudentSheet                ###
   45: ###################################################
   46: package Apache::studentcalc;
   47: 
   48: use warnings FATAL=>'all';
   49: no warnings 'uninitialized';
   50: 
   51: use strict;
   52: use Apache::Constants qw(:common :http);
   53: use Apache::lonnet;
   54: use Apache::loncommon();
   55: use Apache::loncoursedata();
   56: use Apache::lonnavmaps;
   57: use Apache::Spreadsheet();
   58: use Apache::assesscalc();
   59: use HTML::Entities();
   60: use Time::HiRes;
   61: use Apache::lonlocal;
   62: use lib '/home/httpd/lib/perl/';
   63: use LONCAPA;
   64:  
   65: 
   66: @Apache::studentcalc::ISA = ('Apache::Spreadsheet');
   67: 
   68: my @Sequences = ();
   69: my $navmap;
   70: my %Exportrows = ();
   71: 
   72: my $current_course;
   73: 
   74: sub initialize {
   75:     &initialize_sequence_cache();
   76:     &Apache::assesscalc::initialize($navmap);
   77: }
   78: 
   79: sub initialize_package {
   80:     $current_course = $env{'request.course.id'};
   81:     &initialize_sequence_cache();
   82:     &load_cached_export_rows();
   83: }
   84: 
   85: sub ensure_correct_sequence_data {
   86:     if ($current_course ne $env{'request.course.id'}) {
   87:         &initialize_sequence_cache();
   88:         $current_course = $env{'request.course.id'};
   89:     }
   90:     return;
   91: }
   92: 
   93: sub initialize_sequence_cache {
   94:     #
   95:     # Set up the sequences and assessments
   96:     undef(@Sequences);
   97:     undef($navmap);
   98:     $navmap = Apache::lonnavmaps::navmap->new();
   99:     if (!defined($navmap)) {
  100:         &Apache::lonnet::logthis('student spreadsheet:Can not open Coursemap');
  101:     }
  102:     my @all_sequences = $navmap->retrieveResources(undef,
  103:                                                sub { shift->is_map(); },1,0,1);
  104:     for my $sequence ($navmap->getById('0.0'), @all_sequences) {
  105: 	if ($navmap->hasResource($sequence,sub { shift->is_problem(); }, 0)){
  106:             push(@Sequences,$sequence);
  107: 	    &get_resources($sequence);
  108:         }
  109:     }
  110: }
  111: 
  112: my %res_memoize;
  113: sub get_resources {
  114:     my ($seq) = @_;
  115:     if (exists($res_memoize{$seq->symb()})) {
  116: 	return @{$res_memoize{$seq->symb()}};
  117:     }
  118:     return () if (! defined($navmap) || ! ref($navmap));
  119:     my @resources = $navmap->retrieveResources($seq,
  120:                                                sub { shift->is_problem(); },
  121:                                                0,0,0);
  122:     $res_memoize{$seq->symb()}=\@resources;
  123:     return @resources;
  124: }
  125: 
  126: sub clear_package {
  127:     undef(@Sequences);
  128:     undef(%Exportrows);
  129:     undef(%res_memoize);
  130:     undef($navmap);
  131:     &Apache::assesscalc::clear_package();
  132: }
  133: 
  134: sub get_title {
  135:     my $self = shift;
  136:     my @title = ();
  137:     #
  138:     # Determine the students name
  139:     my $name = &Apache::loncommon::plainname($self->{'name'},
  140: 					     $self->{'domain'});
  141:     push (@title,$name);
  142:     push (@title,$self->{'coursedesc'});
  143:     push (@title,&Apache::lonlocal::locallocaltime(time));
  144:     return @title;
  145: }
  146: 
  147: sub get_html_title {
  148:     my $self = shift;
  149:     my ($name,$desc,$time) = $self->get_title();
  150:     my $title = '<h1>'.$name;
  151:     if ($env{'user.name'} ne $self->{'name'} && 
  152:         $env{'user.domain'} ne $self->{'domain'}) {
  153:         $title .= ' '.&Apache::loncommon::aboutmewrapper
  154:                                     ($self->{'name'}.'@'.$self->{'domain'},
  155:                                      $self->{'name'},$self->{'domain'});
  156:     }
  157:     $title .= "</h1>\n";
  158:     $title .= '<h2>'.$desc."</h2>\n";
  159:     $title .= '<h3>'.$time.'</h3>';
  160:     return $title;
  161: }
  162: 
  163: sub parent_link {
  164:     my $self = shift;
  165:     return '<p><a href="/adm/classcalc">'.&mt('Course level sheet').'</a></p>'."\n";
  166: }
  167: 
  168: sub convenience_links {
  169:     my $self = shift;
  170:     my ($resource) = @_;
  171:     my $result=&Apache::loncommon::submlink('<img src="/adm/lonMisc/subm_button.gif" border="0" />',$self->{'name'},$self->{'domain'},$resource->symb,'LONcatInfo');
  172:     $result .= &Apache::loncommon::pgrdlink('<img src="/adm/lonMisc/pgrd_button.gif" border="0" />',$self->{'name'},$self->{'domain'},$resource->symb,'LONcatInfo');
  173:     $result .= &Apache::loncommon::pprmlink('<img src="/adm/lonMisc/pprm_button.gif" border="0" />',$self->{'name'},$self->{'domain'},$resource->symb,'LONcatInfo');
  174:     return $result;
  175: }
  176: 
  177: sub outsheet_html {
  178:     my $self = shift;
  179:     my ($r) = @_;
  180:     my $importcolor = '#FFFFAA';
  181:     my $exportcolor = '#88FF88';
  182:     ####################################
  183:     # Get the list of assessment files #
  184:     ####################################
  185:     my @AssessFileNames = $self->othersheets('assesscalc');
  186:     my $editing_is_allowed = &Apache::lonnet::allowed('mgr',
  187:                                                 $env{'request.course.id'});
  188:     ####################################
  189:     # Report any calculation errors    #
  190:     ####################################
  191:     $r->print($self->html_report_error());
  192:     ####################################
  193:     # Determine table structure        #
  194:     ####################################
  195:     my $num_uneditable = 26;
  196:     my $num_left = 52-$num_uneditable;
  197:     my %lt=&Apache::lonlocal::texthash(
  198: 				       'st' => 'Student',
  199: 				       'im' => 'Import',
  200: 				       'ca' => 'Calculations',
  201: 				       'as' => 'Assessment',
  202: 				       'ro' => 'Row',
  203: 				       );
  204:     my $tableheader =<<"END";
  205: <p>
  206: <table border="2">
  207: <tr>
  208:   <th colspan="2" rowspan="2"><font size="+2">$lt{'st'}</font></th>
  209:   <td bgcolor="$importcolor" colspan="$num_uneditable">
  210:       <b><font size="+1">$lt{'im'}</font></b></td>
  211:   <td colspan="$num_left">
  212:       <b><font size="+1">$lt{'ca'}</font></b></td>
  213: </tr><tr>
  214: END
  215:     my $label_num = 0;
  216:     foreach (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')){
  217:         if ($label_num<$num_uneditable) { 
  218:             $tableheader .='<td bgcolor="'.$importcolor.'">';
  219:         } else {
  220:             $tableheader .='<td>';
  221:         }
  222:         $tableheader .="<b><font size=+1>$_</font></b></td>";
  223:         $label_num++;
  224:     }
  225:     $tableheader .="</tr>\n";
  226:     if ($self->blackout()) {
  227:         $r->print('<font color="red" size="+2"><p>'.
  228:                   &mt('Some computations are not available at this time.').'<br />'.
  229:                   &mt('There are problems whose status you are not allowed to view.').
  230:                   '</font></p>'."\n");
  231:     } else {
  232:         $r->print($tableheader);
  233:         #
  234:         # Print out template row
  235:         if (exists($env{'request.role.adv'}) && $env{'request.role.adv'}) {
  236:             $r->print('<tr><td>Template</td><td>&nbsp;</td>'.
  237:                       $self->html_template_row($num_uneditable,
  238:                                                $importcolor)."</tr>\n");
  239:         }
  240:         #
  241:         # Print out summary/export row
  242:         $r->print('<tr><td>'.&mt('Summary').'</td><td>0</td>'.
  243:                   $self->html_export_row($exportcolor)."</tr>\n");
  244:     }
  245:     $r->print("</table>\n");
  246:     #
  247:     # Prepare to output rows
  248:     if (exists($env{'request.role.adv'}) && $env{'request.role.adv'}) {
  249:         $tableheader =<<"END";
  250: </p><p>
  251: <table border="2">
  252: <tr><th>$lt{'ro'}</th><th>&nbsp;</th><th>$lt{'as'}</th>
  253: END
  254:     } else {
  255:         $tableheader =<<"END";
  256: </p><p>
  257: <table border="2">
  258: <tr><th>&nbsp;</th><th>$lt{'as'}</th>
  259: END
  260:     }
  261:     foreach (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')){
  262: 	if ($label_num<$num_uneditable) { 
  263:             $tableheader.='<td bgcolor="#FFDDDD">';
  264:         } else {
  265:             $tableheader.='<td>';
  266:         }
  267:         $tableheader.="<b><font size=+1>$_</font></b></td>";
  268:     }
  269:     $tableheader.="\n";
  270:     #
  271:     my $num_output = 1;
  272:     if (scalar(@Sequences)< 1) {
  273:         &initialize_sequence_cache();
  274:     }
  275:     foreach my $Sequence (@Sequences) {
  276: 	$r->print("<h3>".$Sequence->compTitle."</h3>\n");
  277: 
  278:         my @resources = &get_resources($Sequence);
  279:         my $first_rownum =
  280:             $self->get_row_number_from_key($resources[0]->symb);
  281:         my $last_rownum = 
  282:             $self->get_row_number_from_key($resources[-1]->symb);
  283:         $r->print(&assess_file_selector([$first_rownum, $last_rownum],
  284:                                         undef, \@AssessFileNames));
  285: 
  286:  	$r->print($tableheader);
  287: 	foreach my $resource (@resources) {
  288: 	    my $rownum = $self->get_row_number_from_key($resource->symb);
  289:             my $assess_filename = $self->{'row_source'}->{$rownum};
  290:             my $row_output = '<tr>';
  291:             if ($editing_is_allowed) {
  292:                 $row_output .= '<td>'.$rownum.'</td>';
  293:                 $row_output .= '<td>'.$self->convenience_links($resource).'</td>';
  294:                 $row_output .= '<td>'.
  295:                     '<a href="/adm/assesscalc?sname='.$self->{'name'}.
  296:                     '&sdomain='.$self->{'domain'}.
  297:                     '&filename='.$assess_filename.
  298:                     '&usymb='.&escape($resource->symb).
  299:                     '">'.$resource->compTitle.'</a><br />';
  300:                 $row_output .= &assess_file_selector($rownum,
  301:                                                      $assess_filename,
  302:                                                      \@AssessFileNames).
  303:                                                          '</td>';
  304:             } else {
  305:                 $row_output .= '<td><a href="'.$resource->src.'?symb='.
  306:                     &escape($resource->symb).
  307:                     '">Go&nbsp;To</a>';
  308:                 $row_output .= '</td><td>'.$resource->compTitle.'</td>';
  309:             }
  310:             if ($self->blackout() && $self->{'blackout_rows'}->{$rownum}>0) {
  311:                 $row_output .= 
  312:                     '<td colspan="52">'.&mt('Unavailable at this time').'</td></tr>'."\n";
  313:             } else {
  314:                 $row_output .= $self->html_row($num_uneditable,$rownum,
  315:                                                $exportcolor,$importcolor).
  316:                     "</tr>\n";
  317:             }
  318:             $r->print($row_output);
  319: 	}
  320: 	$r->print("</table>\n");
  321:     }
  322:     $r->print("</p>\n");
  323:     return;
  324: }
  325: 
  326: ########################################################
  327: ########################################################
  328: 
  329: =pod
  330: 
  331: =item &assess_file_selector()
  332: 
  333: =cut
  334: 
  335: ########################################################
  336: ########################################################
  337: sub assess_file_selector {
  338:     my ($row,$default,$AssessFiles)=@_;
  339:     if (!defined($AssessFiles) || ! @$AssessFiles) {
  340:         return '';
  341:     }
  342:     return '' if (! &Apache::lonnet::allowed('mgr',$env{'request.course.id'}));
  343:     my $element_name;
  344:     my $source_row = $row;
  345:     if (ref($row)) {
  346:         my ($first_rownum, $last_rownum) = @$row;
  347:         $element_name = "FileSelect_${first_rownum}_${last_rownum}";
  348:         $source_row = "${first_rownum}:${last_rownum}";
  349:     } else {
  350:         $element_name = 'FileSelect_'.$row;
  351:     }
  352:     my $load_dialog = '<select size="1" name="'.$element_name.'" '.
  353:         'onchange="'.
  354:         "document.sheet.cell.value='source_${source_row}';".
  355:         "document.sheet.newformula.value=document.sheet.$element_name\.value;".
  356:         'document.sheet.submit()" '.'>'."\n";
  357:     if (ref($row)) {
  358:         $load_dialog .= '    <option name="" value="">' .
  359:             &mt("Select spreadsheet for entire sequence")
  360:             . "</option>\n";
  361:     }
  362:     foreach my $file (@{$AssessFiles}) {
  363:         $load_dialog .= '    <option name="'.$file.'"';
  364:         $load_dialog .= ' selected' if ($default eq $file);
  365:         $load_dialog .= '>'.$file."</option>\n";
  366:     }
  367:     $load_dialog .= "</select>\n";
  368:     return $load_dialog;
  369: }
  370: 
  371: sub modify_cell {
  372:     my $self = shift;
  373:     my ($cell,$formula) = @_;
  374: 
  375:     my $set_row = sub {
  376:         my $row = shift;
  377:         my $formula = shift;
  378:         my $cell = 'A' . $row;
  379:         $self->{'row_source'}->{$row} = $formula;
  380:         my $original_source = $self->formula($cell);
  381:         if ($original_source =~ /__&&&__/) {
  382:             ($original_source,undef) = split('__&&&__',$original_source);
  383:         }
  384:         $formula = $original_source.'__&&&__'.$formula;
  385:         $self->set_formula($cell,$formula);
  386:     };
  387: 
  388:     if ($cell =~ /^source_(\d+):(\d+)$/) {
  389:         my $first_row = $1;
  390:         my $last_row = $2;
  391:         for my $row ($first_row..$last_row) {
  392:             $set_row->($row, $formula);
  393:         }
  394:     } elsif ($cell =~ /^source_(\d+)$/) {
  395:         # Need to make sure $formula is a valid filename....
  396:         my $row = $1;
  397:         $set_row->($row, $formula);
  398:     } elsif ($cell =~ /([A-z])\-/) {
  399:         $cell = 'template_'.$1;
  400:         $self->set_formula($cell,$formula);
  401:     } elsif ($cell !~ /^([A-z](\d+)|template_[A-z])$/) {
  402:         return;
  403:     }
  404:     $self->rebuild_stats();
  405:     return;
  406: }
  407: 
  408: sub csv_rows {
  409:     # writes the meat of the spreadsheet to an excel worksheet.  Called
  410:     # by Spreadsheet::outsheet_excel;
  411:     my $self = shift;
  412:     my ($connection,$filehandle) = @_;
  413:     #
  414:     # Write a header row
  415:     $self->csv_output_row($filehandle,undef,
  416:                           (&mt('Sequence or Folder'),&mt('Assessment title')));
  417:     #
  418:     # Write each assessments row
  419:     if (scalar(@Sequences)< 1) {
  420:         &initialize_sequence_cache();
  421:     }
  422:     foreach my $Sequence (@Sequences) {
  423: 	foreach my $resource (&get_resources($Sequence)) {
  424: 	    my $rownum = $self->get_row_number_from_key($resource->symb);
  425:             my @assessdata = ($Sequence->compTitle,
  426:                               $resource->compTitle);
  427:             $self->csv_output_row($filehandle,$rownum,@assessdata);
  428:         }
  429:     }
  430:     return;
  431: }
  432: 
  433: sub excel_rows {
  434:     # writes the meat of the spreadsheet to an excel worksheet.  Called
  435:     # by Spreadsheet::outsheet_excel;
  436:     my $self = shift;
  437:     my ($connection,$worksheet,$cols_output,$rows_output,$format) = @_;
  438:     #
  439:     # Write a header row
  440:     $cols_output = 0;
  441:     foreach my $value ('Container','Assessment title') {
  442:         $worksheet->write($rows_output,$cols_output++,&mt($value),$format->{'h4'});
  443:     }
  444:     $rows_output++;    
  445:     #
  446:     # Write each assessments row
  447:     if (scalar(@Sequences)< 1) {
  448:         &initialize_sequence_cache();
  449:     }
  450:     foreach my $Sequence (@Sequences) {
  451: 	foreach my $resource (&get_resources($Sequence)) {
  452: 	    my $rownum = $self->get_row_number_from_key($resource->symb);
  453:             my @assessdata = ($Sequence->compTitle,
  454:                               $resource->compTitle);
  455:             $self->excel_output_row($worksheet,$rownum,$rows_output++,
  456:                                     @assessdata);
  457:         }
  458:     }
  459:     return;
  460: }
  461: 
  462: sub outsheet_recursive_excel {
  463:     my $self = shift;
  464:     my ($r) = @_;
  465: } 
  466: 
  467: ##
  468: ## Routines to deal with sequences in the safe space
  469: ##
  470: sub get_rows_in_sequence {
  471:     my $self = shift();
  472:     my ($sequence) = @_;
  473:     my @Rows;
  474:     my @resources = &get_resources($sequence);
  475:     foreach my $resource (@resources) {
  476:         my $rownum = $self->get_row_number_from_key($resource->symb);
  477:         push (@Rows,$rownum);
  478:     }
  479:     return @Rows;
  480: }
  481: 
  482: sub remove_sequence_data_from_safe_space {
  483:     my $self = shift();
  484:     my $command = 'undef(%Sequence_Rows);';
  485:     $self->{'safe'}->reval($command);
  486: }
  487: 
  488: sub put_sequence_data_in_safe_space {
  489:     my $self = shift();
  490:     my $data = 'undef(%Sequence_Rows);';
  491:     # Build up the %Sequence_Rows hash - each sequence title is associated with
  492:     # an array pointer, which holds the rows in the sequence.
  493:     foreach my $seq (@Sequences) {
  494:         my @Rows = $self->get_rows_in_sequence($seq);
  495:         # 
  496:         # Potential problems with sequence titles:
  497:         # 1. duplicate titles - they get the total for the titles
  498:         # 2. control characters in titles - use q{} around the string to
  499:         #    deal with it.  
  500:         my $title = &HTML::Entities::decode($seq->title());
  501:         $title =~ s/&\#058;/:/g;
  502:         if (@Rows) {
  503:             $data .= 'push(@{$Sequence_Rows{"'.quotemeta($title).'"}},'.
  504:                 '('.join(',',@Rows).'));'."\n";;
  505:         }
  506:     }
  507:     my $new_code = $data.<<'END';
  508: sub SUMSEQ {
  509:     my ($col,@titles) = @_;
  510:     return 'bad column: '.$col if ($col !~ /^[A-z]$/);
  511:     my $sum = 0;
  512:     foreach my $title (@titles) {
  513:         while (my ($seq_title,$rows) = each(%Sequence_Rows)) {
  514:             my $regexp;
  515:             if ($title =~ /^regexp:(.*)$/) {
  516:                 $regexp = $1;
  517:             } elsif (lc($title) eq 'all') {
  518:                 $regexp = '.';
  519:             }
  520:             if (defined($regexp)) {
  521:                 next if ($seq_title !~ /$regexp/);
  522:             } else {
  523:                 next if ($seq_title ne $title);
  524:             }
  525:             foreach my $rownum (@{$rows}) {
  526:                 my $cell = $col.$rownum;
  527:                 if (exists($sheet_values{$cell})) {
  528:                     $sum += $sheet_values{$cell};
  529:                 }
  530:             }
  531:         }
  532:     }
  533:     return $sum;
  534: }
  535: END
  536:     $self->{'safe'}->reval($new_code);
  537:     return;
  538: }
  539: 
  540: ##
  541: ## Main computation method
  542: ##
  543: sub compute {
  544:     my $self = shift;
  545:     my ($r) = @_;
  546:     if (! defined($current_course) ||
  547:         $current_course ne $env{'request.course.id'} ||
  548:         ! @Sequences ) {
  549:         $current_course = $env{'request.course.id'};
  550:         &clear_package();
  551:         &initialize_sequence_cache();
  552:     }
  553:     $self->initialize_safe_space();
  554:     &Apache::assesscalc::initialize_package($self->{'name'},$self->{'domain'},
  555:                                             $navmap);
  556:     my %f = $self->formulas();
  557:     #
  558:     # Process the formulas list - 
  559:     #   the formula for the A column of a row is symb__&&__filename
  560:     my %c = $self->constants();
  561:     foreach my $seq (@Sequences) {
  562:         foreach my $resource (&get_resources($seq)) {
  563:             my $rownum = $self->get_row_number_from_key($resource->symb);
  564:             my $cell = 'A'.$rownum;
  565:             my $assess_filename = 'Default';
  566:             if (exists($self->{'row_source'}->{$rownum})) {
  567:                 $assess_filename = $self->{'row_source'}->{$rownum};
  568:             } else {
  569:                 $self->{'row_source'}->{$rownum} = $assess_filename;
  570:             }
  571:             $f{$cell} = $resource->symb.'__&&&__'.$assess_filename;
  572:             my $assessSheet;
  573:                 $assessSheet = Apache::assesscalc->new($self->{'name'},
  574:                                                        $self->{'domain'},
  575:                                                        $assess_filename,
  576:                                                        $resource->symb,
  577: 						       $self->{'section'},
  578:                                                        $self->{'groups'});
  579:             my @exportdata = $assessSheet->export_data($r);
  580:             #
  581:             if ($assessSheet->badcalc()) {
  582:                 $self->set_calcerror(
  583:             &mt('Error computing row for assessment "[_1]" (row [_2]):[_3]',
  584:                 $assessSheet->get_title(),$rownum,$assessSheet->calcerror()));
  585:             }
  586:             #
  587:             if ($assessSheet->blackout()) {
  588:                 $self->blackout(1);
  589:                 $self->{'blackout_rows'}->{$rownum} = 1;
  590:             }
  591:             #
  592:             # Be sure not to disturb the formulas in the 'A' column
  593:             my $data = shift(@exportdata);
  594:             $c{$cell} = $data if (defined($data));
  595:             #
  596:             # Deal with the remaining columns
  597:             my $i=0;
  598:             foreach (split(//,'BCDEFGHIJKLMNOPQRSTUVWXYZ')) {
  599:                 my $cell = $_.$rownum;
  600:                 my $data = shift(@exportdata);
  601:                 if (defined($data)) {
  602:                     $f{$cell} = 'import';
  603:                     $c{$cell} = $data;
  604:                 }
  605:                 $i++;
  606:             }
  607:         }
  608:     }
  609:     $self->constants(\%c);
  610:     $self->formulas(\%f);
  611:     $self->put_sequence_data_in_safe_space();
  612:     $self->calcsheet();
  613:     $self->remove_sequence_data_from_safe_space();
  614:     #
  615:     # Store export row in cache
  616:     my @exportarray=$self->exportrow();
  617:     my $student = $self->{'name'}.':'.$self->{'domain'};
  618:     $Exportrows{$student}->{'time'} = time;
  619:     $Exportrows{$student}->{'data'} = \@exportarray;
  620:     # save export row
  621:     $self->save_export_data();
  622:     #
  623:     $self->save() if ($self->need_to_save());
  624:     return;
  625: }
  626: 
  627: sub set_row_sources {
  628:     my $self = shift;
  629:     $self->check_formulas_loaded();
  630:     while (my ($cell,$value) = each(%{$self->{'formulas'}})) {
  631:         next if ($cell !~ /^A(\d+)$/ || $1 < 1);
  632:         my $row = $1;
  633:         (undef,$value) = split('__&&&__',$value);
  634:         $value = 'Default' if (! defined($value));
  635:         $self->{'row_source'}->{$row} = $value;
  636:     }
  637:     return;
  638: }
  639: 
  640: sub set_row_numbers {
  641:     my $self = shift;
  642:     $self->check_formulas_loaded();
  643:     while (my ($cell,$formula) = each(%{$self->{'formulas'}})) {
  644:         next if ($cell !~ /^A(\d+)/);
  645:         my $row = $1;
  646:         next if ($row == 0);
  647:         my ($symb,undef) = split('__&&&__',$formula);
  648:         $self->{'row_numbers'}->{$symb} = $row;
  649:         $self->{'maxrow'} = $row if ($row > $self->{'maxrow'});
  650:     }
  651: }
  652: 
  653: sub get_row_number_from_symb {
  654:     my $self = shift;
  655:     my ($key) = @_;
  656:     ($key,undef) = split('__&&&__',$key) if ($key =~ /__&&&__/);
  657:     return $self->get_row_number_from_key($key);
  658: }
  659: 
  660: #############################################
  661: #############################################
  662: 
  663: =pod
  664: 
  665: =item &load_cached_export_rows
  666: 
  667: Retrieves and parsers the export rows of the student spreadsheets.
  668: These rows are saved in the courses directory in the format:
  669: 
  670:  sname:sdom:studentcalc:.time => time
  671: 
  672:  sname:sdom:studentcalc => ___=___Adata___;___Bdata___;___Cdata___;___ .....
  673: 
  674: =cut
  675: 
  676: #############################################
  677: #############################################
  678: sub load_cached_export_rows {
  679:     undef(%Exportrows);
  680:     my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets',
  681: 		     $env{'course.'.$env{'request.course.id'}.'.domain'},
  682: 		     $env{'course.'.$env{'request.course.id'}.'.num'},undef);
  683:     my %Selected_Assess_Sheet;
  684:     if ($tmp[0] =~ /^error/) {
  685:         &Apache::lonnet::logthis('unable to read cached student export rows '.
  686:                                  'for course '.$env{'request.course.id'});
  687:         return;
  688:     }
  689:     my %tmp = @tmp;
  690:     while (my ($key,$sheetdata) = each(%tmp)) {
  691:         my ($sname,$sdom,$sheettype,$remainder) = split(':',$key);
  692:         my $student = $sname.':'.$sdom;
  693:         if ($remainder =~ /\.time/) {
  694:             $Exportrows{$student}->{'time'} = $sheetdata;
  695:         } else {
  696:             $sheetdata =~ s/^___=___//;
  697:             my @Data = split('___;___',$sheetdata);
  698:             $Exportrows{$student}->{'data'} = \@Data;
  699:         }
  700:     }
  701: }
  702: 
  703: #############################################
  704: #############################################
  705: 
  706: =pod
  707: 
  708: =item &save_export_data()
  709: 
  710: Writes the export data for this student to the course cache.
  711: 
  712: =cut
  713: 
  714: #############################################
  715: #############################################
  716: sub save_export_data {
  717:     my $self = shift;
  718:     my $student = $self->{'name'}.':'.$self->{'domain'};
  719:     return if ($self->temporary());
  720:     if ($self->badcalc()){
  721:         # do not save data away when calculations have not been done properly.
  722:         delete($Exportrows{$student});
  723:         return;
  724:     }
  725:     return if (! exists($Exportrows{$student}));
  726:     &Apache::assesscalc::save_cached_export_rows($self->{'name'},
  727:                                                  $self->{'domain'});
  728:     return if (! $self->is_default());
  729:     my $key = join(':',($self->{'name'},$self->{'domain'},'studentcalc')).':';
  730:     my $timekey = $key.'.time';
  731:     my $newstore = join('___;___',
  732:                         @{$Exportrows{$student}->{'data'}});
  733:     $newstore = '___=___'.$newstore;
  734:     my $result= &Apache::lonnet::put('nohist_calculatedsheets',
  735:                          { $key     => $newstore,
  736:                            $timekey => $Exportrows{$student}->{'time'} },
  737:                          $self->{'cdom'},
  738:                          $self->{'cnum'});
  739:     return;
  740: }
  741: 
  742: #############################################
  743: #############################################
  744: 
  745: =pod
  746: 
  747: =item &export_data()
  748: 
  749: Returns the export data associated with the spreadsheet.  Computes the
  750: spreadsheet only if necessary.
  751: 
  752: =cut
  753: 
  754: #############################################
  755: #############################################
  756: sub export_data {
  757:     my $self = shift;
  758:     my ($r) = @_;
  759:     my $connection = $r->connection();
  760:     my $student = $self->{'name'}.':'.$self->{'domain'};
  761:     if (! exists($Exportrows{$student}) ||
  762:         ! defined($Exportrows{$student}) ||
  763:         ! exists($Exportrows{$student}->{'data'}) ||
  764:         ! defined($Exportrows{$student}->{'data'}) ||
  765:         ! exists($Exportrows{$student}->{'time'}) ||
  766:         ! defined($Exportrows{$student}->{'time'}) ||
  767:         ! $self->check_expiration_time($Exportrows{$student}->{'time'})) {
  768:         $self->compute($r);
  769:     }
  770:     if ($connection->aborted()) { $self->cleanup(); return; }
  771:     my @Data;
  772:     if ($self->badcalc()) {
  773:         @Data = ();
  774:     } else {
  775:         @Data = @{$Exportrows{$student}->{'data'}};
  776:         for (my $i=0; $i<=$#Data;$i++) {
  777:             if ($Data[$i]=~/\D/ && defined($Data[$i])) {
  778:                 $Data[$i]="'".$Data[$i]."'";
  779:             }
  780:         }
  781:     }
  782:     return @Data;
  783: }
  784: 
  785: 1;
  786: 
  787: __END__

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