File:  [LON-CAPA] / loncom / interface / spreadsheet / studentcalc.pm
Revision 1.46: download - view: text, annotated - select for diffs
Wed Feb 12 16:55:36 2014 UTC (10 years, 3 months ago) by bisitz
Branches: MAIN
CVS tags: version_2_11_X, version_2_11_4_uiuc, version_2_11_4_msu, version_2_11_4, version_2_11_3_uiuc, version_2_11_3_msu, version_2_11_3, version_2_11_2_uiuc, version_2_11_2_msu, version_2_11_2_educog, version_2_11_2, version_2_11_1, version_2_11_0_RC3, version_2_11_0, HEAD
Internationalization: Added missing &mt() call

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

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