Diff for /loncom/interface/Attic/lonspreadsheet.pm between versions 1.108 and 1.179

version 1.108, 2002/09/09 17:18:33 version 1.179, 2003/05/27 15:50:30
Line 26 Line 26
 # The LearningOnline Network with CAPA  # The LearningOnline Network with CAPA
 # Spreadsheet/Grades Display Handler  # Spreadsheet/Grades Display Handler
 #  #
 # 11/11,11/15,11/27,12/04,12/05,12/06,12/07,  
 # 12/08,12/09,12/11,12/12,12/15,12/16,12/18,12/19,12/30,  
 # 01/01/01,02/01,03/01,19/01,20/01,22/01,  
 # 03/05,03/08,03/10,03/12,03/13,03/15,03/17,  
 # 03/19,03/20,03/21,03/27,04/05,04/09,  
 # 07/09,07/14,07/21,09/01,09/10,9/11,9/12,9/13,9/14,9/17,  
 # 10/16,10/17,10/20,11/05,11/28,12/27 Gerd Kortemeyer  
 # 01/14/02 Matthew  
 # 02/04/02 Matthew  
   
 # POD required stuff:  # POD required stuff:
   
 =head1 NAME  =head1 NAME
Line 54  not the grades of their peers.  The spre Line 44  not the grades of their peers.  The spre
 offering the ability to use Perl code to manipulate data, as well as many  offering the ability to use Perl code to manipulate data, as well as many
 built-in functions.  built-in functions.
   
   
 =head2 Functions available to user of lonspreadsheet  =head2 Functions available to user of lonspreadsheet
   
 =over 4  =over 4
   
 =cut  =cut
   
   
 package Apache::lonspreadsheet;  package Apache::lonspreadsheet;
                           
 use strict;  use strict;
   use Apache::Constants qw(:common :http);
   use Apache::lonnet;
   use Apache::lonhtmlcommon;
   use Apache::lonmenu;
   use HTML::Entities();
   
   # --------------------------------------------------------- Various form fields
   
   sub textfield {
       my ($title,$name,$value)=@_;
       return "\n<p><b>$title:</b><br>".
           '<input type=text name="'.$name.'" size=80 value="'.$value.'">';
   }
   
   sub hiddenfield {
       my ($name,$value)=@_;
       return "\n".'<input type=hidden name="'.$name.'" value="'.$value.'">';
   }
   
   sub selectbox {
       my ($title,$name,$value,%options)=@_;
       my $selout="\n<p><b>$title:</b><br>".'<select name="'.$name.'">';
       foreach (sort keys(%options)) {
           $selout.='<option value="'.$_.'"';
           if ($_ eq $value) { $selout.=' selected'; }
           $selout.='>'.$options{$_}.'</option>';
       }
       return $selout.'</select>';
   }
   
   my %oldsheets;
   my %loadedcaches;
   
   # ================================================================ Main handler
   #
   # Interactive call to screen
   #
   #
   sub handler {
       my $r=shift;
   
       my ($sheettype) = ($r->uri=~/\/(\w+)$/);
   
       if (! exists($ENV{'form.Status'})) {
           $ENV{'form.Status'} = 'Active';
       }
       if ( ! exists($ENV{'form.output'}) || 
                ($sheettype ne 'classcalc' && 
                 lc($ENV{'form.output'}) eq 'recursive excel')) {
           $ENV{'form.output'} = 'HTML';
       }
       #
       # Overload checking
       #
       # Check this server
       my $loaderror=&Apache::lonnet::overloaderror($r);
       if ($loaderror) { return $loaderror; }
       # Check the course homeserver
       $loaderror= &Apache::lonnet::overloaderror($r,
                         $ENV{'course.'.$ENV{'request.course.id'}.'.home'});
       if ($loaderror) { return $loaderror; } 
       #
       # HTML Header
       #
       if ($r->header_only) {
           $r->content_type('text/html');
           $r->send_http_header;
           return OK;
       }
       #
       # Roles Checking
       #
       # Needs to be in a course
       if (! $ENV{'request.course.fn'}) { 
           # Not in a course, or not allowed to modify parms
           $ENV{'user.error.msg'}=
               $r->uri.":opa:0:0:Cannot modify spreadsheet";
           return HTTP_NOT_ACCEPTABLE; 
       }
       #
       # Get query string for limited number of parameters
       #
       &Apache::loncommon::get_unprocessed_cgi
           ($ENV{'QUERY_STRING'},['uname','udom','usymb','ufn','mapid','resid']);
       #
       # Deal with restricted student permissions 
       #
       if ($ENV{'request.role'} =~ /^st\./) {
           delete $ENV{'form.unewfield'}   if (exists($ENV{'form.unewfield'}));
           delete $ENV{'form.unewformula'} if (exists($ENV{'form.unewformula'}));
       }
       #
       # Look for special assessment spreadsheets - '_feedback', etc.
       #
       if (($ENV{'form.usymb'}=~/^\_(\w+)/) && (!$ENV{'form.ufn'} || 
                                                $ENV{'form.ufn'} eq '' || 
                                                $ENV{'form.ufn'} eq 'default')) {
           $ENV{'form.ufn'}='default_'.$1;
       }
   #    if (!$ENV{'form.ufn'} || $ENV{'form.ufn'} eq 'default') {
   #        $ENV{'form.ufn'}='course_default_'.$sheettype;
   #    }
       #
       # Interactive loading of specific sheet?
       #
       if (($ENV{'form.load'}) && ($ENV{'form.loadthissheet'} ne 'Default')) {
           $ENV{'form.ufn'}=$ENV{'form.loadthissheet'};
       }
       #
       # Determine the user name and domain for the sheet.
       my $aname;
       my $adom;
       unless ($ENV{'form.uname'}) {
           $aname=$ENV{'user.name'};
           $adom=$ENV{'user.domain'};
       } else {
           $aname=$ENV{'form.uname'};
           $adom=$ENV{'form.udom'};
       }
       #
       # Open page, try to prevent browser cache.
       #
       $r->content_type('text/html');
       $r->header_out('Cache-control','no-cache');
       $r->header_out('Pragma','no-cache');
       $r->send_http_header;
       #
       # Header....
       #
       $r->print('<html><head><title>LON-CAPA Spreadsheet</title>');
       my $nothing = &Apache::lonhtmlcommon::javascript_nothing();
   
       if ($ENV{'request.role'} !~ /^st\./) {
           $r->print(<<ENDSCRIPT);
   <script language="JavaScript">
   
       var editwin;
   
       function celledit(cellname,cellformula) {
           var edit_text = '';
           // cellformula may contain less-than and greater-than symbols, so
           // we need to escape them?  
           edit_text +='<html><head><title>Cell Edit Window</title></head><body>';
           edit_text += '<form name="editwinform">';
           edit_text += '<center><h3>Cell '+cellname+'</h3>';
           edit_text += '<textarea name="newformula" cols="40" rows="6"';
           edit_text += ' wrap="off" >'+cellformula+'</textarea>';
           edit_text += '</br>';
           edit_text += '<input type="button" name="accept" value="Accept"';
           edit_text += ' onClick=\\\'javascript:';
           edit_text += 'opener.document.sheet.unewfield.value=';
           edit_text +=     '"'+cellname+'";';
           edit_text += 'opener.document.sheet.unewformula.value=';
           edit_text +=     'document.editwinform.newformula.value;';
           edit_text += 'opener.document.sheet.submit();';
           edit_text += 'self.close()\\\' />';
           edit_text += '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
           edit_text += '<input type="button" name="abort" ';
           edit_text +=     'value="Discard Changes"';
           edit_text += ' onClick="javascript:self.close()" />';
           edit_text += '</center></body></html>';
   
           if (editwin != null && !(editwin.closed) ) {
               editwin.close();
           }
   
           editwin = window.open($nothing,'CellEditWin','height=200,width=350,scrollbars=no,resizeable=yes,alwaysRaised=yes,dependent=yes',true);
           editwin.document.write(edit_text);
       }
   
       function changesheet(cn) {
    document.sheet.unewfield.value=cn;
           document.sheet.unewformula.value='changesheet';
           document.sheet.submit();
       }
   
       function insertrow(cn) {
    document.sheet.unewfield.value='insertrow';
           document.sheet.unewformula.value=cn;
           document.sheet.submit();
       }
   
   </script>
   ENDSCRIPT
       }
       $r->print('</head>'.&Apache::loncommon::bodytag('Grades Spreadsheet').
                 '<form action="'.$r->uri.'" name="sheet" method="post">');
       $r->print(&hiddenfield('uname',$ENV{'form.uname'}).
                 &hiddenfield('udom',$ENV{'form.udom'}).
                 &hiddenfield('usymb',$ENV{'form.usymb'}).
                 &hiddenfield('unewfield','').
                 &hiddenfield('unewformula','').
                 &Apache::lonmenu::regflush());
       $r->rflush();
       #
       # Full recalc?
       #
       # Read new sheet or modified worksheet
       my $sheet=Apache::lonspreadsheet::Spreadsheet->new($aname,$adom,$sheettype,$ENV{'form.usymb'});
       if ($ENV{'form.forcerecalc'}) {
           $r->print('<h4>Completely Recalculating Sheet ...</h4>');
           $sheet->complete_recalc();
       }
       #
       # Global directory configs
       #
       $sheet->includedir($r->dir_config('lonIncludes'));
       #
       # Check user permissions
       if (($sheet->{'type'}  eq 'classcalc'       ) || 
           ($sheet->{'uname'} ne $ENV{'user.name'} ) ||
           ($sheet->{'udom'}  ne $ENV{'user.domain'})) {
           unless (&Apache::lonnet::allowed('vgr',$sheet->{'cid'})) {
               $r->print('<h1>Access Permission Denied</h1>'.
                         '</form></body></html>');
               return OK;
           }
       }
       # Print out user information
       $r->print('<h2>'.$sheet->{'coursedesc'}.'</h2>');
       if ($sheet->{'type'} ne 'classcalc') {
           $r->print('<h2>'.$sheet->gettitle().'</h2><p>');
       }
       if ($sheet->{'type'} eq 'assesscalc') {
           $r->print('<b>User:</b> '.$sheet->{'uname'}.
                     '<br /><b>Domain:</b> '.$sheet->{'udom'}.'<br />');
       }
       if ($sheet->{'type'} eq 'studentcalc' || 
           $sheet->{'type'} eq 'assesscalc') {
           $r->print('<b>Section/Group:</b>'.$sheet->{'csec'}.'</p>');
       } 
       #
       # If a new formula had been entered, go from work copy
       if ($ENV{'form.unewfield'}) {
           $r->print('<h2>Modified Workcopy</h2>');
           #$ENV{'form.unewformula'}=~s/\'/\"/g;
           $r->print('<p>Cell '.$ENV{'form.unewfield'}.' = <pre>');
           $r->print(&HTML::Entities::encode($ENV{'form.unewformula'}).
                     '</pre></p>');
           $sheet->{'filename'} = $ENV{'form.ufn'};
           $sheet->tmpread($ENV{'form.unewfield'},$ENV{'form.unewformula'});
       } elsif ($ENV{'form.saveas'}) {
           $sheet->{'filename'} = $ENV{'form.ufn'};
           $sheet->tmpread();
       } else {
           $sheet->readsheet($ENV{'form.ufn'});
       }
       # Additional options
       if ($sheet->{'type'} eq 'assesscalc') {
           $r->print('<p><font size=+2>'.
                     '<a href="/adm/studentcalc?'.
                     'uname='.$sheet->{'uname'}.
                     '&udom='.$sheet->{'udom'}.'">'.
                     'Level up: Student Sheet</a></font></p>');
       }
       if (($sheet->{'type'} eq 'studentcalc') && 
           (&Apache::lonnet::allowed('vgr',$sheet->{'cid'}))) {
           $r->print ('<p><font size=+2><a href="/adm/classcalc">'.
                      'Level up: Course Sheet</a></font></p>');
       }
       # Recalc button
       $r->print('<br />'.
                 '<input type="submit" name="forcerecalc" '.
                 'value="Completely Recalculate Sheet"></p>');
       # Save dialog
       if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
           my $fname=$ENV{'form.ufn'};
           $fname=~s/\_[^\_]+$//;
           if ($fname eq 'default') { $fname='course_default'; }
           $r->print('<input type=submit name=saveas value="Save as ...">'.
                     '<input type=text size=20 name=newfn value="'.$fname.'">'.
                     'make default: <input type=checkbox name="makedefufn"><p>');
       }
       $r->print(&hiddenfield('ufn',$sheet->{'filename'}));
       # Load dialog
       if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
           $r->print('<p><input type=submit name=load value="Load ...">'.
                     '<select name="loadthissheet">'.
                     '<option name="default">Default</option>');
           foreach ($sheet->othersheets()) {
               $r->print('<option name="'.$_.'"');
               if ($ENV{'form.ufn'} eq $_) {
                   $r->print(' selected');
               }
               $r->print('>'.$_.'</option>');
           } 
           $r->print('</select><p>');
           if ($sheet->{'type'} eq 'studentcalc') {
               $sheet->setothersheets($sheet->othersheets('assesscalc'));
           }
       }
       #
       # Set up caching mechanisms
       #
       &Apache::lonspreadsheet::Spreadsheet::load_spreadsheet_expirationdates();
       # Clear out old caches if we have not seen this class before.
       if (exists($oldsheets{'course'}) &&
           $oldsheets{'course'} ne $sheet->{'cid'}) {
           undef %oldsheets;
           undef %loadedcaches;
           &Apache::lonspreadsheet::Spreadsheet::clear_package_variables();
       }
       $oldsheets{'course'} = $sheet->{'cid'};
       #
       if ($sheet->{'type'} eq 'classcalc') {
           $r->print("Loading previously calculated student sheets ...\n");
           $r->rflush();
           &Apache::lonspreadsheet::Spreadsheet::cachedcsheets();
       } elsif ($sheet->{'type'} eq 'studentcalc') {
           $r->print("Loading previously calculated assessment sheets ...\n");
           $r->rflush();
           $sheet->cachedssheets();
       }
       # Update sheet, load rows
       $r->print("Loaded sheet(s), updating rows ...<br>\n");
       $r->rflush();
       #
       $sheet->updatesheet();
       $r->print("Updated rows, loading row data ...\n");
       $r->rflush();
       #
       $sheet->loadrows($r);
       $r->print("Loaded row data, calculating sheet ...<br>\n");
       $r->rflush();
       #
       my $calcoutput=$sheet->calcsheet();
       $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>');
       # See if something to save
       if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
           my $fname='';
           if ($ENV{'form.saveas'} && ($fname=$ENV{'form.newfn'})) {
               $fname=~s/\W/\_/g;
               if ($fname eq 'default') { $fname='course_default'; }
               $fname.='_'.$sheet->{'type'};
               $sheet->{'filename'} = $fname;
               $ENV{'form.ufn'}=$fname;
               $r->print('<p>Saving spreadsheet: '.
                         $sheet->writesheet($ENV{'form.makedefufn'}).
                         '<p>');
           }
       }
       #
       # Write the modified worksheet
       $r->print('<b>Current sheet:</b> '.$sheet->{'filename'}.'</p>');
       $sheet->tmpwrite();
       if ($sheet->{'type'} eq 'assesscalc') {
           $r->print('<p>Show rows with empty A column: ');
       } else {
           $r->print('<p>Show empty rows: ');
       }
       #
       $r->print(&hiddenfield('userselhidden','true').
                 '<input type="checkbox" name="showall" onClick="submit()"');
       #
       if ($ENV{'form.showall'}) { 
           $r->print(' checked'); 
       } else {
           unless ($ENV{'form.userselhidden'}) {
               unless 
                   ($ENV{'course.'.$sheet->{'cid'}.'.hideemptyrows'} eq 'yes') {
                       $r->print(' checked');
                       $ENV{'form.showall'}=1;
                   }
           }
       }
       $r->print('>');
       #
       # output format select box 
       $r->print(' Output as <select name="output" size="1" onChange="submit()">'.
                 "\n");
       foreach my $mode (qw/HTML CSV Excel/) {
           $r->print('<option value="'.$mode.'"');
           if ($ENV{'form.output'} eq $mode) {
               $r->print(' selected ');
           } 
           $r->print('>'.$mode.'</option>'."\n");
       }
   #
   #    Mulit-sheet excel takes too long and does not work at all for large
   #    classes.  Future inclusion of this option may be possible with the
   #    Spreadsheet::WriteExcel::Big and speed improvements.
   #
   #    if ($sheet->{'type'} eq 'classcalc') {
   #        $r->print('<option value="recursive excel"');
   #        if ($ENV{'form.output'} eq 'recursive excel') {
   #            $r->print(' selected ');
   #        } 
   #        $r->print(">Multi-Sheet Excel</option>\n");
   #    }
       $r->print("</select>\n");
       #
       if ($sheet->{'type'} eq 'classcalc') {
           $r->print('&nbsp;Student Status: '.
                     &Apache::lonhtmlcommon::StatusOptions
                     ($ENV{'form.Status'},'sheet'));
       }
       #
       # Buttons to insert rows
   #    $r->print(<<ENDINSERTBUTTONS);
   #<br>
   #<input type='button' onClick='insertrow("top");' 
   #value='Insert Row Top'>
   #<input type='button' onClick='insertrow("bottom");' 
   #value='Insert Row Bottom'><br>
   #ENDINSERTBUTTONS
       # Print out sheet
       $sheet->outsheet($r);
       $r->print('</form></body></html>');
       #  Done
       return OK;
   }
   
   1;
   
   #############################################################
   #############################################################
   #############################################################
   
   package Apache::lonspreadsheet::Spreadsheet;
               
   use strict;
   use Apache::Constants qw(:common :http);
   use Apache::lonnet;
   use Apache::loncoursedata;
   use Apache::File();
 use Safe;  use Safe;
 use Safe::Hole;  use Safe::Hole;
 use Opcode;  use Opcode;
 use Apache::lonnet;  
 use Apache::Constants qw(:common :http);  
 use GDBM_File;  use GDBM_File;
   use HTML::Entities();
 use HTML::TokeParser;  use HTML::TokeParser;
 use Apache::lonhtmlcommon;  use Spreadsheet::WriteExcel;
   use Time::HiRes;
   
 #  #
 # Caches for previously calculated spreadsheets  # These global hashes are dependent on user, course and resource, 
   # and need to be initialized every time when a sheet is calculated
 #  #
   my %courseopt;
   my %useropt;
   my %parmhash;
   
 my %oldsheets;  #
 my %loadedcaches;  # Caches for coursewide information 
   #
   my %Section;
   
   #
   # Caches for previously calculated spreadsheets
   #
 my %expiredates;  my %expiredates;
   
 #  #
 # Cache for stores of an individual user  # Cache for stores of an individual user
 #  #
   
 my $cachedassess;  my $cachedassess;
 my %cachedstores;  my %cachedstores;
   
 #  #
 # These cache hashes need to be independent of user, resource and course  # Some hashes for stats on timing and performance
 # (user and course can/should be in the keys)  
 #  #
   my %starttimes;
   my %usedtimes;
   my %numbertimes;
   
   #
   # Directories
   #
   my $includedir;
   
   sub includedir {
       my $self = shift;
       $includedir = shift;
   }
   
 my %spreadsheets;  my %spreadsheets;
   #my %loadedcaches;
 my %courserdatas;  my %courserdatas;
 my %userrdatas;  my %userrdatas;
 my %defaultsheets;  my %defaultsheets;
 my %updatedata;  my %rowlabel_cache;
   #my %oldsheets;
   
   sub clear_package_variables {
       undef %courseopt;
       undef %useropt;
       undef %parmhash;
       undef %Section;
       undef %expiredates;
       undef $cachedassess;
       undef %cachedstores;
       undef %starttimes;
       undef %usedtimes;
       undef %numbertimes;
       undef $includedir;
       undef %spreadsheets;
       undef %courserdatas;
       undef %userrdatas;
       undef %defaultsheets;
       undef %rowlabel_cache;
   }
   
   sub complete_recalc {
       my $self = shift;
       undef %spreadsheets;
       undef %courserdatas;
       undef %userrdatas;
       undef %defaultsheets;
       undef %rowlabel_cache;
   }
   
   sub get_sheet {
       my $self = shift;
       my $sheet_id = shift;
       my $formulas;
       # if we already have the file loaded and parsed, return the formulas
       if (exists($self->{'sheets'}->{$sheet_id})) {
           $formulas = $self->{'sheets'}->{$sheet_id};
           $self->debug('retrieved '.$sheet_id);
       } else {
           # load the file
           #     set $error and return undef if there is an error loading
           # parse it
           #     set $error and return undef if there is an error parsing
       }
       return $formulas;
   }
   
 #  #
 # These global hashes are dependent on user, course and resource,   # Load previously cached student spreadsheets for this course
 # and need to be initialized every time when a sheet is calculated  
 #  #
 my %courseopt;  sub load_spreadsheet_expirationdates {
 my %useropt;      undef %expiredates;
 my %parmhash;      my $cid=$ENV{'request.course.id'};
       my @tmp = &Apache::lonnet::dump('nohist_expirationdates',
                                       $ENV{'course.'.$cid.'.domain'},
                                       $ENV{'course.'.$cid.'.num'});
       if (lc($tmp[0]) !~ /^error/){
           %expiredates = @tmp;
       }
   }
   
   # ===================================================== Calculated sheets cache
 #  #
 # Some hashes for stats on timing and performance  # Load previously cached student spreadsheets for this course
 #  #
   sub cachedcsheets {
       my $cid=$ENV{'request.course.id'};
       my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets',
                                       $ENV{'course.'.$cid.'.domain'},
                                       $ENV{'course.'.$cid.'.num'});
       if ($tmp[0] !~ /^error/) {
           my %StupidTempHash = @tmp;
           while (my ($key,$value) = each %StupidTempHash) {
               $Apache::lonspreadsheet::oldsheets{$key} = $value;
           }
       }
   }
   
 my %starttimes;  #
 my %usedtimes;  # Load previously cached assessment spreadsheets for this student
 my %numbertimes;  #
   sub cachedssheets {
       my $self = shift;
       my ($uname,$udom) = @_;
       $uname = $uname || $self->{'uname'};
       $udom  = $udom  || $self->{'udom'};
       if (! exists($Apache::lonspreadsheet::loadedcaches{$uname.'_'.$udom})) {
           my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets_'.
                                           $ENV{'request.course.id'},
                                           $self->{'udom'},
                                           $self->{'uname'});
           if ($tmp[0] !~ /^error/) {
               my %TempHash = @tmp;
               my $count = 0;
               while (my ($key,$value) = each %TempHash) {
                   $Apache::lonspreadsheet::oldsheets{$key} = $value;
                   $count++;
               }
               $Apache::lonspreadsheet::loadedcaches{$self->{'uname'}.'_'.$self->{'udom'}}=1;
           }
       }    
   }
   
 # Stuff that only the screen handler can know  # ======================================================= Forced recalculation?
   sub checkthis {
       my ($keyname,$time)=@_;
       if (! exists($expiredates{$keyname})) {
           return 0;
       } else {
           return ($time<$expiredates{$keyname});
       }
   }
   
 my $includedir;  sub forcedrecalc {
 my $tmpdir;      my ($uname,$udom,$stype,$usymb)=@_;
       my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;
       my $time=$Apache::lonspreadsheet::oldsheets{$key.'.time'};
       if ($ENV{'form.forcerecalc'}) { return 1; }
       unless ($time) { return 1; }
       if ($stype eq 'assesscalc') {
           my $map=(split(/___/,$usymb))[0];
           if (&checkthis('::assesscalc:',$time) ||
               &checkthis('::assesscalc:'.$map,$time) ||
               &checkthis('::assesscalc:'.$usymb,$time) ||
               &checkthis($uname.':'.$udom.':assesscalc:',$time) ||
               &checkthis($uname.':'.$udom.':assesscalc:'.$map,$time) ||
               &checkthis($uname.':'.$udom.':assesscalc:'.$usymb,$time)) {
               return 1;
           }
       } else {
           if (&checkthis('::studentcalc:',$time) || 
               &checkthis($uname.':'.$udom.':studentcalc:',$time)) {
       return 1;
           }
       }
       return 0; 
   }
   
 # =============================================================================  
 # ===================================== Implements an instance of a spreadsheet  
   
 sub initsheet {  ##################################################
     my $safeeval = new Safe(shift);  ##################################################
     my $safehole = new Safe::Hole;  
     $safeeval->permit("entereval");  
     $safeeval->permit(":base_math");  
     $safeeval->permit("sort");  
     $safeeval->deny(":base_io");  
     $safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&EXT');  
     $safeeval->share('$@');  
     my $code=<<'ENDDEFS';  
 # ---------------------------------------------------- Inside of the safe space  
   
 #  =pod
 # f: formulas  
 # t: intermediate format (variable references expanded)  
 # v: output values  
 # c: preloaded constants (A-column)  
 # rl: row label  
 # os: other spreadsheets (for student spreadsheet only)  
   
 undef %sheet_values;   =item &parmval()
 undef %t;  
 undef %f;  
 undef %c;  
 undef %rowlabel;  
 undef @os;  
   
 $maxrow = 0;  Determine the value of a parameter.
 $sheettype = '';  
   
 # filename/reference of the sheet  Inputs: $what, the parameter needed, $symb, $uname, $udom, $csec 
 $filename = '';  
   
 # user data  Returns: The value of a parameter, or '' if none.
 $uname = '';  
 $uhome = '';  
 $udom  = '';  
   
 # course data  This function cascades through the possible levels searching for a value for
   a parameter.  The levels are checked in the following order:
   user, course (at section level and course level), map, and lonnet::metadata.
   This function uses %parmhash, which must be tied prior to calling it.
   This function also requires %courseopt and %useropt to be initialized for
   this user and course.
   
 $csec = '';  =cut
 $chome= '';  
 $cnum = '';  
 $cdom = '';  
 $cid  = '';  
 $cfn  = '';  
   
 # symb  ##################################################
   ##################################################
   sub parmval {
       my ($what,$symb,$uname,$udom,$csec)=@_;
       return '' if (!$symb);
       #
       my $cid   = $ENV{'request.course.id'};
       my $result='';
       #
       my ($mapname,$id,$fn)=split(/\_\_\_/,$symb);
       # Cascading lookup scheme
       my $rwhat=$what;
       $what =~ s/^parameter\_//;
       $what =~ s/\_([^\_]+)$/\.$1/;
       #
       my $symbparm = $symb.'.'.$what;
       my $mapparm  = $mapname.'___(all).'.$what;
       my $usercourseprefix = $uname.'_'.$udom.'_'.$cid;
       #
       my $seclevel  = $usercourseprefix.'.['.$csec.'].'.$what;
       my $seclevelr = $usercourseprefix.'.['.$csec.'].'.$symbparm;
       my $seclevelm = $usercourseprefix.'.['.$csec.'].'.$mapparm;
       #
       my $courselevel  = $usercourseprefix.'.'.$what;
       my $courselevelr = $usercourseprefix.'.'.$symbparm;
       my $courselevelm = $usercourseprefix.'.'.$mapparm;
       # fourth, check user
       if (defined($uname)) {
           return $useropt{$courselevelr} if (defined($useropt{$courselevelr}));
           return $useropt{$courselevelm} if (defined($useropt{$courselevelm}));
           return $useropt{$courselevel}  if (defined($useropt{$courselevel}));
       }
       # third, check course
       if (defined($csec)) {
           return $courseopt{$seclevelr} if (defined($courseopt{$seclevelr}));
           return $courseopt{$seclevelm} if (defined($courseopt{$seclevelm}));
           return $courseopt{$seclevel}  if (defined($courseopt{$seclevel}));
       }
       #
       return $courseopt{$courselevelr} if (defined($courseopt{$courselevelr}));
       return $courseopt{$courselevelm} if (defined($courseopt{$courselevelm}));
       return $courseopt{$courselevel}  if (defined($courseopt{$courselevel}));
       # second, check map parms
       my $thisparm = $parmhash{$symbparm};
       return $thisparm if (defined($thisparm));
   
 $usymb = '';      # first, check default
       $thisparm = &Apache::lonnet::metadata($fn,$rwhat.'.default');
       return $thisparm if (defined($thisparm));
   
 # error messages      #Cascade Up
 $errormsg = '';      my $space=$what;
       $space=~s/\.\w+$//;
       if ($space ne '0') {
    my @parts=split(/_/,$space);
    my $id=pop(@parts);
    my $part=join('_',@parts);
    if ($part eq '') { $part='0'; }
    my $newwhat=$rwhat;
    $newwhat=~s/\Q$space\E/$part/;
    my $partgeneral=&parmval($newwhat,$symb,$uname,$udom,$csec);
    if (defined($partgeneral)) { return $partgeneral; }
       }
   
       #nothing defined
       return '';
   }
   
   #
   # new: Make a new spreadsheet
   #
   sub new {
       my $this = shift;
       my $class = ref($this) || $this;
       #
       my ($uname,$udom,$stype,$usymb)=@_;
       #
       if (! exists($Section{$uname.':'.$udom})) {
           my $classlist = &Apache::loncoursedata::get_classlist();
           #
           foreach my $student (keys(%$classlist)) {
               my ($studentDomain,$studentName,undef,undef,undef,$studentSection,
                   undef,undef) = @{$classlist->{$student}};
               $Section{$studentName.':'.$studentDomain} = $studentSection;
           }
       }
       my $self = {
           uname => $uname,
           udom  => $udom,
           type  => $stype,
           usymb => $usymb,
           errorlog => '',
           maxrow   => '',
           mapid => $ENV{'form.mapid'},
           resid => $ENV{'form.resid'},
           cid   => $ENV{'request.course.id'},
           csec  => $Section{$uname.':'.$udom},
           cnum  => $ENV{'course.'.$ENV{'request.course.id'}.'.num'},
           cdom  => $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
           chome => $ENV{'course.'.$ENV{'request.course.id'}.'.home'},
           coursefilename => $ENV{'request.course.fn'},
           coursedesc => $ENV{'course.'.$ENV{'request.course.id'}.'.description'},
           rows       => [],
           template_cells => [],
           };
       $self->{'uhome'} = &Apache::lonnet::homeserver($uname,$udom);
       #
       #
       $self->{'formulas'} = {};
       $self->{'constants'} = {};
       $self->{'othersheets'} = [];
       $self->{'rowlabel'} = {};
       #
       #
       $self->{'safe'} = &initsheet($self->{'type'});
       $self->{'root'} = $self->{'safe'}->root();
       #
       # Place some of the %$self  items into the safe space except the safe space
       # itself
       my $initstring = '';
       foreach (qw/uname udom type usymb cid csec coursefilename
                cnum cdom chome uhome/) {
           $initstring.= qq{\$$_="$self->{$_}";};
       }
       $self->{'safe'}->reval($initstring);
       bless($self,$class);
       return $self;
   }
   
   ##
   ## mask - used to reside in the safe space.  
   ##
   {
   
   my %memoizer;
   
 sub mask {  sub mask {
     my ($lower,$upper)=@_;      my ($lower,$upper)=@_;
       my $key = $lower.'_'.$upper;
     $lower=~/([A-Za-z]|\*)(\d+|\*)/;      if (exists($memoizer{$key})) {
     my $la=$1;          return $memoizer{$key};
     my $ld=$2;      }
       $upper = $lower if (! defined($upper));
     $upper=~/([A-Za-z]|\*)(\d+|\*)/;      #
     my $ua=$1;      my ($la,$ld) = ($lower=~/([A-Za-z]|\*)(\d+|\*)/);
     my $ud=$2;      my ($ua,$ud) = ($upper=~/([A-Za-z]|\*)(\d+|\*)/);
       #
     my $alpha='';      my $alpha='';
     my $num='';      my $num='';
       #
     if (($la eq '*') || ($ua eq '*')) {      if (($la eq '*') || ($ua eq '*')) {
        $alpha='[A-Za-z]';          $alpha='[A-Za-z]';
     } else {      } else {
        if (($la=~/[A-Z]/) && ($ua=~/[A-Z]/) ||         if (($la=~/[A-Z]/) && ($ua=~/[A-Z]/) ||
            ($la=~/[a-z]/) && ($ua=~/[a-z]/)) {             ($la=~/[a-z]/) && ($ua=~/[a-z]/)) {
Line 199  sub mask { Line 856  sub mask {
           $alpha='['.$la.'-Za-'.$ua.']';            $alpha='['.$la.'-Za-'.$ua.']';
        }         }
     }         }   
   
     if (($ld eq '*') || ($ud eq '*')) {      if (($ld eq '*') || ($ud eq '*')) {
  $num='\d+';   $num='\d+';
     } else {      } else {
Line 219  sub mask { Line 875  sub mask {
        } else {         } else {
            my @lda=($ld=~m/\d/g);             my @lda=($ld=~m/\d/g);
            my @uda=($ud=~m/\d/g);             my @uda=($ud=~m/\d/g);
            my $i; $j=0; $notdone=1;             my $i; 
              my $j=0; 
              my $notdone=1;
            for ($i=0;($i<=$#lda)&&($notdone);$i++) {             for ($i=0;($i<=$#lda)&&($notdone);$i++) {
                if ($lda[$i]==$uda[$i]) {                 if ($lda[$i]==$uda[$i]) {
    $num.=$lda[$i];     $num.=$lda[$i];
Line 243  sub mask { Line 901  sub mask {
                }                 }
                $num.=')';                 $num.=')';
            } else {             } else {
                if ($lda[$#lda]!=$uda[$#uda]) {                 if ($lda[-1]!=$uda[-1]) {
                   $num.='['.$lda[$#lda].'-'.$uda[$#uda].']';                    $num.='['.$lda[-1].'-'.$uda[-1].']';
        }         }
            }             }
        }         }
     }      }
     return '^'.$alpha.$num."\$";      my $expression ='^'.$alpha.$num."\$";
       $memoizer{$key} = $expression;
       return $expression;
 }  }
   
   }
   
   sub add_hash_to_safe {
       my $self = shift;
       my $code = <<'END';
 #-------------------------------------------------------  #-------------------------------------------------------
   
 =item UWCALC(hashname,modules,units,date)   =item UWCALC(hashname,modules,units,date) 
Line 518  sub HASH { Line 1183  sub HASH {
     }      }
     return $Values[-1];      return $Values[-1];
 }  }
   END
       $self->{'safe'}->reval($code);
       return;
   }
   
   sub initsheet {
       my $safeeval = new Safe(shift);
       my $safehole = new Safe::Hole;
       $safeeval->permit("entereval");
       $safeeval->permit(":base_math");
       $safeeval->permit("sort");
       $safeeval->deny(":base_io");
       $safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&EXT');
       $safehole->wrap(\&mask,$safeeval,'&mask');
       $safeeval->share('$@');
       my $code=<<'ENDDEFS';
   # ---------------------------------------------------- Inside of the safe space
   #
   # f: formulas
   # t: intermediate format (variable references expanded)
   # v: output values
   # c: preloaded constants (A-column)
   # rl: row label
   # os: other spreadsheets (for student spreadsheet only)
   
   undef %sheet_values;   # Holds the (computed, final) values for the sheet
       # This is only written to by &calc, the spreadsheet computation routine.
       # It is read by many functions
   undef %t; # Holds the values of the spreadsheet temporarily. Set in &sett, 
       # which does the translation of strings like C5 into the value in C5.
       # Used in &calc - %t holds the values that are actually eval'd.
   undef %f;    # Holds the formulas for each cell.  This is the users
       # (spreadsheet authors) data for each cell.
   undef %c; # Holds the constants for a sheet.  In the assessment
       # sheets, this is the A column.  Used in &MINPARM, &MAXPARM, &expandnamed,
       # &sett, and &constants.  There is no &getconstants.
       # &constants is called by &loadstudent, &loadcourse, &load assessment,
   undef @os;  # Holds the names of other spreadsheets - this is used to specify
       # the spreadsheets that are available for the assessment sheet.
       # Set by &setothersheets.  &setothersheets is called by &handler.  A
       # related subroutine is &othersheets.
   $errorlog = '';
   
   $maxrow = 0;
   $type = '';
   
   # filename/reference of the sheet
   $filename = '';
   
   # user data
   $uname = '';
   $uhome = '';
   $udom  = '';
   
   # course data
   
   $csec = '';
   $chome= '';
   $cnum = '';
   $cdom = '';
   $cid  = '';
   $coursefilename  = '';
   
   # symb
   
   $usymb = '';
   
   # error messages
   $errormsg = '';
   
 #-------------------------------------------------------  #-------------------------------------------------------
   
Line 529  returns the number of items in the range Line 1263  returns the number of items in the range
   
 #-------------------------------------------------------  #-------------------------------------------------------
 sub NUM {  sub NUM {
     my $mask=mask(@_);      my $mask=&mask(@_);
     my $num= $#{@{grep(/$mask/,keys(%sheet_values))}}+1;      my $num= $#{@{grep(/$mask/,keys(%sheet_values))}}+1;
     return $num;         return $num;   
 }  }
   
 sub BIN {  sub BIN {
     my ($low,$high,$lower,$upper)=@_;      my ($low,$high,$lower,$upper)=@_;
     my $mask=mask($lower,$upper);      my $mask=&mask($lower,$upper);
     my $num=0;      my $num=0;
     foreach (grep /$mask/,keys(%sheet_values)) {      foreach (grep /$mask/,keys(%sheet_values)) {
         if (($sheet_values{$_}>=$low) && ($sheet_values{$_}<=$high)) {          if (($sheet_values{$_}>=$low) && ($sheet_values{$_}<=$high)) {
Line 557  returns the sum of items in the range. Line 1291  returns the sum of items in the range.
   
 #-------------------------------------------------------  #-------------------------------------------------------
 sub SUM {  sub SUM {
     my $mask=mask(@_);      my $mask=&mask(@_);
     my $sum=0;      my $sum=0;
     foreach (grep /$mask/,keys(%sheet_values)) {      foreach (grep /$mask/,keys(%sheet_values)) {
         $sum+=$sheet_values{$_};          $sum+=$sheet_values{$_};
Line 575  compute the average of the items in the Line 1309  compute the average of the items in the
   
 #-------------------------------------------------------  #-------------------------------------------------------
 sub MEAN {  sub MEAN {
     my $mask=mask(@_);      my $mask=&mask(@_);
     my $sum=0; my $num=0;  #    $errorlog.='(mask = '.$mask.' )';
       my $sum=0; 
       my $num=0;
     foreach (grep /$mask/,keys(%sheet_values)) {      foreach (grep /$mask/,keys(%sheet_values)) {
         $sum+=$sheet_values{$_};          $sum+=$sheet_values{$_};
         $num++;          $num++;
Line 598  compute the standard deviation of the it Line 1334  compute the standard deviation of the it
   
 #-------------------------------------------------------  #-------------------------------------------------------
 sub STDDEV {  sub STDDEV {
     my $mask=mask(@_);      my $mask=&mask(@_);
   #    $errorlog.='(mask = '.$mask.' )';
     my $sum=0; my $num=0;      my $sum=0; my $num=0;
     foreach (grep /$mask/,keys(%sheet_values)) {      foreach (grep /$mask/,keys(%sheet_values)) {
         $sum+=$sheet_values{$_};          $sum+=$sheet_values{$_};
Line 623  compute the product of the items in the Line 1360  compute the product of the items in the
   
 #-------------------------------------------------------  #-------------------------------------------------------
 sub PROD {  sub PROD {
     my $mask=mask(@_);      my $mask=&mask(@_);
     my $prod=1;      my $prod=1;
     foreach (grep /$mask/,keys(%sheet_values)) {      foreach (grep /$mask/,keys(%sheet_values)) {
         $prod*=$sheet_values{$_};          $prod*=$sheet_values{$_};
Line 641  compute the maximum of the items in the Line 1378  compute the maximum of the items in the
   
 #-------------------------------------------------------  #-------------------------------------------------------
 sub MAX {  sub MAX {
     my $mask=mask(@_);      my $mask=&mask(@_);
     my $max='-';      my $max='-';
     foreach (grep /$mask/,keys(%sheet_values)) {      foreach (grep /$mask/,keys(%sheet_values)) {
         unless ($max) { $max=$sheet_values{$_}; }          unless ($max) { $max=$sheet_values{$_}; }
Line 660  compute the minimum of the items in the Line 1397  compute the minimum of the items in the
   
 #-------------------------------------------------------  #-------------------------------------------------------
 sub MIN {  sub MIN {
     my $mask=mask(@_);      my $mask=&mask(@_);
     my $min='-';      my $min='-';
     foreach (grep /$mask/,keys(%sheet_values)) {      foreach (grep /$mask/,keys(%sheet_values)) {
         unless ($max) { $max=$sheet_values{$_}; }          unless ($max) { $max=$sheet_values{$_}; }
Line 683  compute the sum of the largest 'num' ite Line 1420  compute the sum of the largest 'num' ite
 #-------------------------------------------------------  #-------------------------------------------------------
 sub SUMMAX {  sub SUMMAX {
     my ($num,$lower,$upper)=@_;      my ($num,$lower,$upper)=@_;
     my $mask=mask($lower,$upper);      my $mask=&mask($lower,$upper);
     my @inside=();      my @inside=();
     foreach (grep /$mask/,keys(%sheet_values)) {      foreach (grep /$mask/,keys(%sheet_values)) {
  push (@inside,$sheet_values{$_});   push (@inside,$sheet_values{$_});
Line 708  compute the sum of the smallest 'num' it Line 1445  compute the sum of the smallest 'num' it
 #-------------------------------------------------------  #-------------------------------------------------------
 sub SUMMIN {  sub SUMMIN {
     my ($num,$lower,$upper)=@_;      my ($num,$lower,$upper)=@_;
     my $mask=mask($lower,$upper);      my $mask=&mask($lower,$upper);
     my @inside=();      my @inside=();
     foreach (grep /$mask/,keys(%sheet_values)) {      foreach (grep /$mask/,keys(%sheet_values)) {
  $inside[$#inside+1]=$sheet_values{$_};   $inside[$#inside+1]=$sheet_values{$_};
Line 767  sub MAXPARM { Line 1504  sub MAXPARM {
     return $max;      return $max;
 }  }
   
 #--------------------------------------------------------  sub calc {
   #    $errorlog .= "\%t has ".(keys(%t))." keys\n";
       %sheet_values = %t; # copy %t into %sheet_values.
   #    $errorlog .= "\%sheet_values has ".(keys(%sheet_values))." keys\n";
       my $notfinished=1;
       my $lastcalc='';
       my $depth=0;
       while ($notfinished) {
    $notfinished=0;
           while (my ($cell,$value) = each(%t)) {
               my $old=$sheet_values{$cell};
               $sheet_values{$cell}=eval $value;
       if ($@) {
    undef %sheet_values;
                   return $cell.': '.$@;
               }
       if ($sheet_values{$cell} ne $old) { 
                   $notfinished=1; 
                   $lastcalc=$cell; 
               }
           }
           $depth++;
           if ($depth>100) {
       undef %sheet_values;
               return $lastcalc.': Maximum calculation depth exceeded';
           }
       }
       return '';
   }
   
   # ------------------------------------------- End of "Inside of the safe space"
   ENDDEFS
       $safeeval->reval($code);
       return $safeeval;
   }
   
   
   #
   # expandnamed used to reside in the safe space
   #
 sub expandnamed {  sub expandnamed {
       my $self = shift;
     my $expression=shift;      my $expression=shift;
     if ($expression=~/^\&/) {      if ($expression=~/^\&/) {
  my ($func,$var,$formula)=($expression=~/^\&(\w+)\(([^\;]+)\;(.*)\)/);   my ($func,$var,$formula)=($expression=~/^\&(\w+)\(([^\;]+)\;(.*)\)/);
  my @vars=split(/\W+/,$formula);   my @vars=split(/\W+/,$formula);
         my %values=();          my %values=();
         undef %values;   foreach my $varname ( @vars ) {
  foreach ( @vars ) {  
             my $varname=$_;  
             if ($varname=~/\D/) {              if ($varname=~/\D/) {
                $formula=~s/$varname/'$c{\''.$varname.'\'}'/ge;                 $formula=~s/$varname/'$c{\''.$varname.'\'}'/ge;
                $varname=~s/$var/\(\\w\+\)/g;                 $varname=~s/$var/\([\\w:\\- ]\+\)/g;
        foreach (keys(%c)) {         foreach (keys(%{$self->{'constants'}})) {
   if ($_=~/$varname/) {    if ($_=~/$varname/) {
       $values{$1}=1;        $values{$1}=1;
                   }                    }
Line 810  sub expandnamed { Line 1585  sub expandnamed {
         my @matches = ();          my @matches = ();
         $#matches = -1;          $#matches = -1;
         study $expression;          study $expression;
         foreach $parameter (keys(%c)) {          my $parameter;
           foreach $parameter (keys(%{$self->{'constants'}})) {
             push @matches,$parameter if ($parameter =~ /$expression/);              push @matches,$parameter if ($parameter =~ /$expression/);
         }          }
         if ($#matches == 0) {          if (scalar(@matches) == 0) {
               $returnvalue = 'unmatched parameter: '.$parameter;
           } elsif (scalar(@matches) == 1) {
               # why do we not do this lookup here, instead of delaying it?
             $returnvalue = '$c{\''.$matches[0].'\'}';              $returnvalue = '$c{\''.$matches[0].'\'}';
         } elsif ($#matches > 0) {          } elsif (scalar(@matches) > 0) {
             # more than one match.  Look for a concise one              # more than one match.  Look for a concise one
             $returnvalue =  "'non-unique parameter name : $expression'";              $returnvalue =  "'non-unique parameter name : $expression'";
             foreach (@matches) {              foreach (@matches) {
                 if (/^$expression$/) {                  if (/^$expression$/) {
                       # why do we not do this lookup here?
                     $returnvalue = '$c{\''.$_.'\'}';                      $returnvalue = '$c{\''.$_.'\'}';
                 }                  }
             }              }
         } else {          } else {
             $returnvalue =  "'bad parameter name : $expression'";              # There was a negative number of matches, which indicates 
               # something is wrong with reality.  Better warn the user.
               $returnvalue = 'bizzare parameter: '.$parameter;
         }          }
         return $returnvalue;          return $returnvalue;
     }      }
 }  }
   
   #
   # sett used to reside in the safe space
   #
 sub sett {  sub sett {
     %t=();      my $self = shift;
       my %t=();
     my $pattern='';      my $pattern='';
     if ($sheettype eq 'assesscalc') {      if ($self->{'type'} eq 'assesscalc') {
  $pattern='A';   $pattern='A';
     } else {      } else {
         $pattern='[A-Z]';          $pattern='[A-Z]';
     }      }
     # Deal with the template row      # Deal with the template row
     foreach (keys(%f)) {      foreach my $col ($self->template_cells()) {
  next if ($_!~/template\_(\w)/);  
         my $col=$1;  
         next if ($col=~/^$pattern/);          next if ($col=~/^$pattern/);
         foreach (keys(%f)) {          foreach my $trow ($self->rows()) {
             next if ($_!~/A(\d+)/);  
             my $trow=$1;  
             next if (! $trow);  
             # Get the name of this cell              # Get the name of this cell
             my $lb=$col.$trow;              my $lb=$col.$trow;
             # Grab the template declaration              # Grab the template declaration
             $t{$lb}=$f{'template_'.$col};              $t{$lb}=$self->formula('template_'.$col);
             # Replace '#' with the row number              # Replace '#' with the row number
             $t{$lb}=~s/\#/$trow/g;              $t{$lb}=~s/\#/$trow/g;
             # Replace '....' with ','              # Replace '....' with ','
Line 858  sub sett { Line 1639  sub sett {
             # Replace 'A0' with the value from 'A0'              # Replace 'A0' with the value from 'A0'
             $t{$lb}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;              $t{$lb}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
             # Replace parameters              # Replace parameters
             $t{$lb}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge;              $t{$lb}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge;
         }          }
     }      }
     # Deal with the normal cells      # Deal with the normal cells
     foreach (keys(%f)) {      foreach ($self->formulas_keys()) {
  if (($f{$_}) && ($_!~/template\_/)) {   next if ($_=~/template\_/);
             my $matches=($_=~/^$pattern(\d+)/);          if  (($_=~/^$pattern(\d+)/) && ($1)) {
             if  (($matches) && ($1)) {              if ($self->formula($_) !~ /^\!/) {
         unless ($f{$_}=~/^\!/) {                  $t{$_}=$self->{'constants'}->{$_};
     $t{$_}=$c{$_};  
                 }  
             } else {  
        $t{$_}=$f{$_};  
                $t{$_}=~s/\.\.+/\,/g;  
                $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;  
                $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge;  
             }              }
           } else {
               $t{$_}=$self->formula($_);
               $t{$_}=~s/\.\.+/\,/g;
               $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
               $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge;
         }          }
     }      }
     # For inserted lines, [B-Z] is also valid      # For inserted lines, [B-Z] is also valid
     unless ($sheettype eq 'assesscalc') {      if ($self->{'type'} ne 'assesscalc') {
        foreach (keys(%f)) {          foreach ($self->formulas_keys()) {
    if ($_=~/[B-Z](\d+)/) {              next if ($_ !~ /[B-Z](\d+)/);
        if ($f{'A'.$1}=~/^[\~\-]/) {              next if ($self->formula('A'.$1) !~ /^[\~\-]/);
            $t{$_}=$f{$_};              $t{$_}=$self->formula($_);
                   $t{$_}=~s/\.\.+/\,/g;              $t{$_}=~s/\.\.+/\,/g;
                   $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;              $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
                   $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge;              $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge;
                }          }
            }  
        }  
     }      }
     # For some reason 'A0' gets special treatment...  This seems superfluous      # For some reason 'A0' gets special treatment...  This seems superfluous
     # but I imagine it is here for a reason.      # but I imagine it is here for a reason.
     $t{'A0'}=$f{'A0'};      $t{'A0'}=$self->formula('A0');
     $t{'A0'}=~s/\.\.+/\,/g;      $t{'A0'}=~s/\.\.+/\,/g;
     $t{'A0'}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;      $t{'A0'}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
     $t{'A0'}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge;      $t{'A0'}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge;
       # Put %t into the safe space
       %{$self->{'safe'}->varglob('t')}=%t;
 }  }
   
 sub calc {  
     undef %sheet_values;  ###########################################
     &sett();  ###          Row output routines        ###
     my $notfinished=1;  ###########################################
     my $lastcalc='';  #
     my $depth=0;  # get_row: Produce output row n from sheet by calling the appropriate routine
     while ($notfinished) {  #
  $notfinished=0;  sub get_row {
         foreach (keys(%t)) {      my $self = shift;
             my $old=$sheet_values{$_};      my ($n) = @_;
             $sheet_values{$_}=eval $t{$_};      my ($rowlabel,@rowdata);
     if ($@) {      if ($n eq '-') { 
  undef %sheet_values;          ($rowlabel,@rowdata) = $self->templaterow();
                 return $_.': '.$@;      } elsif ($self->{'type'} eq 'studentcalc') {
             }          ($rowlabel,@rowdata) = $self->outrowassess($n);
     if ($sheet_values{$_} ne $old) { $notfinished=1; $lastcalc=$_; }      } else {
         }          ($rowlabel,@rowdata) = $self->outrow($n);
         $depth++;  
         if ($depth>100) {  
     undef %sheet_values;  
             return $lastcalc.': Maximum calculation depth exceeded';  
         }  
     }      }
     return '';      return ($rowlabel,@rowdata);
 }  }
   
 sub templaterow {  sub templaterow {
       my $self = shift;
     my @cols=();      my @cols=();
     $cols[0]='<b><font size=+1>Template</font></b>';      my $rowlabel = 'Template</td><td>&nbsp;';
     foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',      foreach my $n ('A','B','C','D','E','F','G','H','I','J','K','L','M',
      'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',                     'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
      'a','b','c','d','e','f','g','h','i','j','k','l','m',                     'a','b','c','d','e','f','g','h','i','j','k','l','m',
      'n','o','p','q','r','s','t','u','v','w','x','y','z') {                     'n','o','p','q','r','s','t','u','v','w','x','y','z') {
         my $fm=$f{'template_'.$_};          push(@cols,{ name    => 'template_'.$_,
         $fm=~s/[\'\"]/\&\#34;/g;                       formula => $self->formula('template_'.$n),
         push(@cols,"'template_$_','$fm'".'___eq___'.$fm);                       value   => $self->value('template_'.$n) });
     }      }
     return @cols;      return ($rowlabel,@cols);
 }  }
   
 #  
 # This is actually used for the student spreadsheet, not the assessment sheet  
 # Do not be fooled by the name!  
 #  
 sub outrowassess {  sub outrowassess {
       my $self = shift;
     # $n is the current row number      # $n is the current row number
     my $n=shift;      my ($n) = @_;
     my @cols=();      my @cols=();
       my $rowlabel='';
     if ($n) {      if ($n) {
         my ($usy,$ufn)=split(/__&&&\__/,$f{'A'.$n});          my ($usy,$ufn)=split(/__&&&\__/,$self->formula('A'.$n));
         if ($rowlabel{$usy}) {          if (exists($self->{'rowlabel'}->{$usy})) {
             $cols[0]=$rowlabel{$usy}.'<br>'.              # This is dumb, but we need the information when we output
                 '<select name="sel_'.$n.'" onChange="changesheet('.$n.')">'.              # the html version of the studentcalc spreadsheet for the
                     '<option name="default">Default</option>';              # links to the assesscalc sheets.
               $rowlabel = $self->{'rowlabel'}->{$usy}.':'.
                   &Apache::lonnet::escape($ufn);
         } else {           } else { 
             $cols[0]='';               $rowlabel = '';
         }  
         foreach (@os) {  
             $cols[0].='<option name="'.$_.'"';  
             if ($ufn eq $_) {  
                 $cols[0].=' selected';  
             }  
             $cols[0].='>'.$_.'</option>';  
         }          }
         $cols[0].='</select>';      } elsif ($ENV{'request.role'} =~ /^st\./) {
           $rowlabel = 'Summary</td><td>0';
     } else {      } else {
         $cols[0]='<b><font size=+1>Export</font></b>';          $rowlabel = 'Export</td><td>0';
     }      }
     foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',      foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',
      'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',       'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
      'a','b','c','d','e','f','g','h','i','j','k','l','m',       'a','b','c','d','e','f','g','h','i','j','k','l','m',
      'n','o','p','q','r','s','t','u','v','w','x','y','z') {       'n','o','p','q','r','s','t','u','v','w','x','y','z') {
         my $fm=$f{$_.$n};          push(@cols,{ name    => $_.$n,
         $fm=~s/[\'\"]/\&\#34;/g;                       formula => $self->formula($_.$n),
         push(@cols,"'$_$n','$fm'".'___eq___'.$sheet_values{$_.$n});                       value   => $self->value($_.$n)});
     }      }
     return @cols;      return ($rowlabel,@cols);
 }  }
   
 sub outrow {  sub outrow {
     my $n=shift;      my $self = shift;
       my ($n)=@_;
     my @cols=();      my @cols=();
       my $rowlabel;
     if ($n) {      if ($n) {
        $cols[0]=$rowlabel{$f{'A'.$n}};          $rowlabel = $self->{'rowlabel'}->{$self->formula('A'.$n)};
     } else {      } else {
        $cols[0]='<b><font size=+1>Export</font></b>';          if ($self->{'type'} eq 'classcalc') {
               $rowlabel = 'Summary</td><td>0';
           } else {
               $rowlabel = 'Export</td><td>0';
           }
     }      }
     foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',      foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',
      'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',       'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
      'a','b','c','d','e','f','g','h','i','j','k','l','m',       'a','b','c','d','e','f','g','h','i','j','k','l','m',
      'n','o','p','q','r','s','t','u','v','w','x','y','z') {       'n','o','p','q','r','s','t','u','v','w','x','y','z') {
         my $fm=$f{$_.$n};          push(@cols,{ name    => $_.$n,
         $fm=~s/[\'\"]/\&\#34;/g;                       formula => $self->formula($_.$n),
         push(@cols,"'$_$n','$fm'".'___eq___'.$sheet_values{$_.$n});                       value   => $self->value($_.$n)});
     }      }
     return @cols;      return ($rowlabel,@cols);
 }  }
   
 sub exportrowa {  ########################################################
     my @exportarray=();  ####         Spreadsheet calculation methods       #####
     foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',  ########################################################
      'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') {  #
  push(@exportarray,$sheet_values{$_.'0'});  # calcsheet: makes all the calls to compute the spreadsheet.
     }   #
     return @exportarray;  
 }  
   
 # ------------------------------------------- End of "Inside of the safe space"  
 ENDDEFS  
     $safeeval->reval($code);  
     return $safeeval;  
 }  
   
 # ------------------------------------------------ Add or change formula values  
   
 sub setformulas {  
     my ($safeeval,%f)=@_;  
     %{$safeeval->varglob('f')}=%f;  
 }  
   
 # ------------------------------------------------ Add or change formula values  
   
 sub setconstants {  
     my ($safeeval,%c)=@_;  
     %{$safeeval->varglob('c')}=%c;  
 }  
   
 # --------------------------------------------- Set names of other spreadsheets  
   
 sub setothersheets {  
     my ($safeeval,@os)=@_;  
     @{$safeeval->varglob('os')}=@os;  
 }  
   
 # ------------------------------------------------ Add or change formula values  
   
 sub setrowlabels {  
     my ($safeeval,%rowlabel)=@_;  
     %{$safeeval->varglob('rowlabel')}=%rowlabel;  
 }  
   
 # ------------------------------------------------------- Calculate spreadsheet  
   
 sub calcsheet {  sub calcsheet {
     my $safeeval=shift;      my $self = shift;
     return $safeeval->reval('&calc();');      $self->sync_safe_space();
       $self->clear_errorlog();
       $self->sett();
       my $result =  $self->{'safe'}->reval('&calc();');
       %{$self->{'values'}} = %{$self->{'safe'}->varglob('sheet_values')};
   #    $self->logthis($self->get_errorlog());
       return $result;
   }
   
   ##
   ## sync_safe_space:  Called by calcsheet to make sure all the data we 
   #  need to calculate is placed into the safe space
   ##
   sub sync_safe_space {
       my $self = shift;
       # Inside the safe space 'formulas' has a diabolical alter-ego named 'f'.
       %{$self->{'safe'}->varglob('f')}=%{$self->{'formulas'}};
       # 'constants' leads a peaceful hidden life of 'c'.
       %{$self->{'safe'}->varglob('c')}=%{$self->{'constants'}};
       # 'othersheets' hides as 'os', a disguise few can penetrate.
       @{$self->{'safe'}->varglob('os')}=@{$self->{'othersheets'}};
   }
   
   ##
   ## Retrieve the error log from the safe space (used for debugging)
   ##
   sub get_errorlog {
       my $self = shift;
       $self->{'errorlog'} = ${$self->{'safe'}->varglob('errorlog')};
       return $self->{'errorlog'};
   }
   
   ##
   ## Clear the error log inside the safe space
   ##
   sub clear_errorlog {
       my $self = shift;
       ${$self->{'safe'}->varglob('errorlog')} = '';
       $self->{'errorlog'} = '';
 }  }
   
 # ------------------------------------------------------------------ Get values  
   
 sub getvalues {  ########################################################
     my $safeeval=shift;  #### Spreadsheet content retrieval/setting methods #####
     return $safeeval->reval('%sheet_values');  ########################################################
 }  ##
   ## constants:  either set or get the constants
 # ---------------------------------------------------------------- Get formulas  ##
   ##
 sub getformulas {  sub constants {
     my $safeeval=shift;      my $self=shift;
     return %{$safeeval->varglob('f')};      my ($constants) = @_;
 }      if (defined($constants)) {
           if (! ref($constants)) {
 # ----------------------------------------------------- Get value of $f{'A'.$n}              my %tmp = @_;
               $constants = \%tmp;
 sub getfa {          }
     my ($safeeval,$n)=@_;          $self->{'constants'} = $constants;
     return $safeeval->reval('$f{"A'.$n.'"}');          return;
 }      } else {
           return %{$self->{'constants'}};
 # -------------------------------------------------------------------- Get type      }
   
 sub gettype {  
     my $safeeval=shift;  
     return $safeeval->reval('$sheettype');  
 }  
   
 # ------------------------------------------------------------------ Set maxrow  
   
 sub setmaxrow {  
     my ($safeeval,$row)=@_;  
     $safeeval->reval('$maxrow='.$row.';');  
 }  
   
 # ------------------------------------------------------------------ Get maxrow  
   
 sub getmaxrow {  
     my $safeeval=shift;  
     return $safeeval->reval('$maxrow');  
 }  
   
 # ---------------------------------------------------------------- Set filename  
   
 sub setfilename {  
     my ($safeeval,$fn)=@_;  
     $safeeval->reval('$filename="'.$fn.'";');  
 }  
   
 # ---------------------------------------------------------------- Get filename  
   
 sub getfilename {  
     my $safeeval=shift;  
     return $safeeval->reval('$filename');  
 }  
   
 # --------------------------------------------------------------- Get course ID  
   
 sub getcid {  
     my $safeeval=shift;  
     return $safeeval->reval('$cid');  
 }  
   
 # --------------------------------------------------------- Get course filename  
   
 sub getcfn {  
     my $safeeval=shift;  
     return $safeeval->reval('$cfn');  
 }  
   
 # ----------------------------------------------------------- Get course number  
   
 sub getcnum {  
     my $safeeval=shift;  
     return $safeeval->reval('$cnum');  
 }  
   
 # ------------------------------------------------------------- Get course home  
   
 sub getchome {  
     my $safeeval=shift;  
     return $safeeval->reval('$chome');  
 }  
   
 # ----------------------------------------------------------- Get course domain  
   
 sub getcdom {  
     my $safeeval=shift;  
     return $safeeval->reval('$cdom');  
 }  }
       
 # ---------------------------------------------------------- Get course section  ##
   ## formulas: either set or get the formulas
 sub getcsec {  ##
     my $safeeval=shift;  sub formulas {
     return $safeeval->reval('$csec');      my $self=shift;
       my ($formulas) = @_;
       if (defined($formulas)) {
           if (! ref($formulas)) {
               my %tmp = @_;
               $formulas = \%tmp;
           }
           $self->{'formulas'} = $formulas;
           $self->{'rows'} = [];
           $self->{'template_cells'} = [];
           return;
       } else {
           return %{$self->{'formulas'}};
       }
 }  }
   
 # --------------------------------------------------------------- Get user name  ##
   ## formulas_keys:  Return the keys to the formulas hash.
 sub getuname {  ##
     my $safeeval=shift;  sub formulas_keys {
     return $safeeval->reval('$uname');      my $self = shift;
       my @keys = keys(%{$self->{'formulas'}});
       return keys(%{$self->{'formulas'}});
   }
   
   ##
   ## formula:  Return the formula for a given cell in the spreadsheet
   ## returns '' if the cell does not have a formula or does not exist
   ##
   sub formula {
       my $self = shift;
       my $cell = shift;
       if (defined($cell) && exists($self->{'formulas'}->{$cell})) {
           return $self->{'formulas'}->{$cell};
       }
       return '';
 }  }
   
 # ------------------------------------------------------------- Get user domain  ##
   ## logthis: write the input to lonnet.log
 sub getudom {  ##
     my $safeeval=shift;  sub logthis {
     return $safeeval->reval('$udom');      my $self = shift;
       my $message = shift;
       &Apache::lonnet::logthis($self->{'type'}.':'.
                                $self->{'uname'}.':'.$self->{'udom'}.':'.
                                $message);
       return;
   }
   
   ##
   ## dump_formulas_to_log: makes lonnet.log huge...
   ##
   sub dump_formulas_to_log {
       my $self =shift;
       $self->logthis("Spreadsheet formulas");
       $self->logthis("--------------------------------------------------------");
       while (my ($cell, $formula) = each(%{$self->{'formulas'}})) {
           $self->logthis('    '.$cell.' = '.$formula);
       }
       $self->logthis("--------------------------------------------------------");}
   
   ##
   ## value: returns the computed value of a particular cell
   ##
   sub value {
       my $self = shift;
       my $cell = shift;
       if (defined($cell) && exists($self->{'values'}->{$cell})) {
           return $self->{'values'}->{$cell};
       }
       return '';
 }  }
   
 # --------------------------------------------------------------- Get user home  ##
   ## dump_values_to_log: makes lonnet.log huge...
 sub getuhome {  ##
     my $safeeval=shift;  sub dump_values_to_log {
     return $safeeval->reval('$uhome');      my $self =shift;
       $self->logthis("Spreadsheet Values");
       $self->logthis("--------------------------------------------------------");
       while (my ($cell, $value) = each(%{$self->{'values'}})) {
           $self->logthis('    '.$cell.' = '.$value);
       }
       $self->logthis("--------------------------------------------------------");}
   
   ##
   ## Yet another debugging function
   ##
   sub dump_hash_to_log {
       my $self= shift();
       my %tmp = @_;
       if (@_<2) {
           %tmp = %{$_[0]};
       }
       $self->logthis('---------------------------- (entries end with ":"');
       while (my ($key,$val) = each (%tmp)) {
           $self->logthis($key.' = '.$val.':');
       }
       $self->logthis('---------------------------- (entries end with ":"');
   }
   
   ################################
   ##      Helper functions      ##
   ################################
   ##
   ## rebuild_stats: rebuilds the rows and template_cells arrays
   ##
   sub rebuild_stats {
       my $self = shift;
       $self->{'rows'}=[];
       $self->{'template_cells'}=[];
       foreach my $cell($self->formulas_keys()) {
           push(@{$self->{'rows'}},$1) if ($cell =~ /^A(\d+)/ && $1 != 0);
           push(@{$self->{'template_cells'}},$1) if ($cell =~ /^template_(\w+)/);
       }
       return;
   }
   
   ##
   ## template_cells returns a list of the cells defined in the template row
   ##
   sub template_cells {
       my $self = shift;
       $self->rebuild_stats() if (!@{$self->{'template_cells'}});
       return @{$self->{'template_cells'}};
   }
   
   ##
   ## rows returns a list of the names of cells defined in the A column
   ##
   sub rows {
       my $self = shift;
       $self->rebuild_stats() if (!@{$self->{'rows'}});
       return @{$self->{'rows'}};
   }
   
   ##
   ## Sigh.... 
   ##
   sub setothersheets {
       my $self = shift;
       my @othersheets = @_;
       $self->{'othersheets'} = \@othersheets;
   }
   
   ##
   ## rowlabels: get or set the rowlabels hash from the spreadsheet.
   ##
   sub rowlabels {
       my $self = shift;
       my ($rowlabel) = @_;
       if (defined($rowlabel)) {
           if (! ref($rowlabel)) {
               my %tmp = @_;
               $rowlabel = \%tmp;
           }
           $self->{'rowlabel'}=$rowlabel;
           return;
       } else {
           return %{$self->{'rowlabel'}} if (defined($self->{'rowlabel'}));
       }
 }  }
   
 # -------------------------------------------------------------------- Get symb  ##
   ## gettitle: returns a title for the spreadsheet.
 sub getusymb {  ##
     my $safeeval=shift;  sub gettitle {
     return $safeeval->reval('$usymb');      my $self = shift;
       if ($self->{'type'} eq 'classcalc') {
           return $self->{'coursedesc'};
       } elsif ($self->{'type'} eq 'studentcalc') {
           return 'Grades for '.$self->{'uname'}.'@'.$self->{'udom'};
       } elsif ($self->{'type'} eq 'assesscalc') {
           if (($self->{'usymb'} eq '_feedback') ||
               ($self->{'usymb'} eq '_evaluation') ||
               ($self->{'usymb'} eq '_discussion') ||
               ($self->{'usymb'} eq '_tutoring')) {
               my $title = $self->{'usymb'};
               $title =~ s/^_//;
               $title = ucfirst($title);
               return $title;
           }
           return if (! defined($self->{'mapid'}) || 
                      $self->{'mapid'} !~ /^\d+$/);
           my $mapid = $self->{'mapid'};
           return if (! defined($self->{'resid'}) || 
                      $self->{'resid'} !~ /^\d+$/);
           my $resid = $self->{'resid'};
           my %course_db;
           tie(%course_db,'GDBM_File',$self->{'coursefilename'}.'.db',
               &GDBM_READER(),0640);
           return if (! tied(%course_db));
           my $key = 'title_'.$mapid.'.'.$resid;
           my $title = '';
           if (exists($course_db{$key})) {
               $title = $course_db{$key};
           } else {
               $title = $self->{'usymb'};
           }
           untie (%course_db);
           return $title;
       }
 }  }
   
 # ------------------------------------------------------------- Export of A-row  #
   # Export of A-row
   #
 sub exportdata {  sub exportdata {
     my $safeeval=shift;      my $self=shift;
     return $safeeval->reval('&exportrowa()');      my @exportarray=();
       foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',
        'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') {
           push(@exportarray,$self->value($_.'0'));
       } 
       return @exportarray;
 }  }
   
   ##
 # ========================================================== End of Spreadsheet  ## update_student_sheet: experimental function
 # =============================================================================  ##
   sub update_student_sheet{
       my $self = shift;
       my ($r,$c) = @_;
       # Load in the studentcalc sheet
       $self->readsheet('default_studentcalc');
       # Determine the structure (contained assessments, etc) of the sheet
       $self->updatesheet();
       # Load in the cached sheets for this student
       $self->cachedssheets();
       # Load in the (possibly cached) data from the assessment sheets        
       $self->loadstudent($r,$c);
       # Compute the sheet
       $self->calcsheet();
   }
   
 #  #
 # Procedures for screen output  # sort_indicies: returns an ordered list of the rows of the spreadsheet
 #  #
 # --------------------------------------------- Produce output row n from sheet  sub sort_indicies {
       my $self = shift;
 sub rown {      my @sortidx=();
     my ($safeeval,$n)=@_;      #
     my $defaultbg;      if ($self->{'type'} eq 'classcalc') {
     my $rowdata='';          my @sortby=(undef);
     my $dataflag=0;          # Skip row 0
     unless ($n eq '-') {          for (my $row=1;$row<=$self->{'maxrow'};$row++) {
         $defaultbg=((($n-1)/5)==int(($n-1)/5))?'#E0E0':'#FFFF';              my (undef,$sname,$sdom,$fullname,$section,$id) = 
     } else {                  split(':',$self->{'rowlabel'}->{$self->formula('A'.$row)});
         $defaultbg='#E0FF';              push (@sortby, lc($fullname));
     }              push (@sortidx, $row);
     unless ($ENV{'form.showcsv'}) {          }
         $rowdata.="\n<tr><td><b><font size=+1>$n</font></b></td>";          @sortidx = sort { $sortby[$a] cmp $sortby[$b]; } @sortidx;
     } else {      } elsif ($self->{'type'} eq 'studentcalc') {
         $rowdata.="\n".'"'.$n.'"';          my @sortby1=(undef);
     }          my @sortby2=(undef);
     my $showf=0;          # Skip row 0
     my $proc;          for (my $row=1;$row<=$self->{'maxrow'};$row++) {
     my $maxred=1;              my ($key,undef) = split(/__&&&\__/,$self->formula('A'.$row));
     my $sheettype=&gettype($safeeval);              my $rowlabel = $self->{'rowlabel'}->{$key};
     if ($sheettype eq 'studentcalc') {              my (undef,$symb,$mapid,$resid,$title,$ufn) = 
         $proc='&outrowassess';                  split(':',$rowlabel);
         $maxred=26;              $ufn   = &Apache::lonnet::unescape($ufn);
     } else {              $symb  = &Apache::lonnet::unescape($symb);
         $proc='&outrow';              $title = &Apache::lonnet::unescape($title);
     }              my ($sequence) = ($symb =~ /\/([^\/]*\.sequence)/);
     if ($sheettype eq 'assesscalc') {              if ($sequence eq '') {
         $maxred=1;                  $sequence = $symb;
     } else {              }
         $maxred=26;              push (@sortby1, $sequence);
     }              push (@sortby2, $title);
     if (&getfa($safeeval,$n)=~/^[\~\-]/) { $maxred=1; }              push (@sortidx, $row);
     if ($n eq '-') {           }
         $proc='&templaterow';           @sortidx = sort { $sortby1[$a] cmp $sortby1[$b] || 
         $n=-1;                                 $sortby2[$a] cmp $sortby2[$b] } @sortidx;
         $dataflag=1;       } else {
     }          my @sortby=(undef);
     foreach ($safeeval->reval($proc.'('.$n.')')) {          # Skip row 0
         my $bgcolor=$defaultbg.((($showf-1)/5==int(($showf-1)/5))?'99':'DD');          $self->sync_safe_space();
         my ($fm,$vl)=split(/\_\_\_eq\_\_\_/,$_);          for (my $row=1;$row<=$self->{'maxrow'};$row++) {
         if ((($vl ne '') || ($vl eq '0')) &&              push (@sortby, $self->{'safe'}->reval('$f{"A'.$row.'"}'));
             (($showf==1) || ($sheettype ne 'studentcalc'))) { $dataflag=1; }              push (@sortidx, $row);
         if ($showf==0) { $vl=$_; }          }
         unless ($ENV{'form.showcsv'}) {          @sortidx = sort { $sortby[$a] cmp $sortby[$b]; } @sortidx;
             if ($showf<=$maxred) { $bgcolor='#FFDDDD'; }      }
             if (($n==0) && ($showf<=26)) { $bgcolor='#CCCCFF'; }       return @sortidx;
             if (($showf>$maxred) || ((!$n) && ($showf>0))) {  }
                 if ($vl eq '') {  
                     $vl='<font size=+2 color='.$bgcolor.'>&#35;</font>';  #############################################################
                 }  ###                                                       ###
                 $rowdata.='<td bgcolor='.$bgcolor.'>'.  ###              Spreadsheet Output Routines              ###
                     '<a href="javascript:celledit('.$fm.');">'.$vl.'</a></td>';  ###                                                       ###
             } else {  #############################################################
                 $rowdata.='<td bgcolor='.$bgcolor.'>&nbsp;'.$vl.'&nbsp;</td>';  
             }  ############################################
   ##         HTML output routines           ##
   ############################################
   sub html_editable_cell {
       my ($cell,$bgcolor) = @_;
       my $result;
       my ($name,$formula,$value);
       if (defined($cell)) {
           $name    = $cell->{'name'};
           $formula = $cell->{'formula'};
           $value   = $cell->{'value'};
       }
       $name    = '' if (! defined($name));
       $formula = '' if (! defined($formula));
       if (! defined($value)) {
           $value = '<font color="'.$bgcolor.'">#</font>';
           if ($formula ne '') {
               $value = '<i>undefined value</i>';
           }
       } elsif ($value =~ /^\s*$/ ) {
           $value = '<font color="'.$bgcolor.'">#</font>';
       } else {
           $value = &HTML::Entities::encode($value) if ($value !~/&nbsp;/);
       }
       # Make the formula safe for outputting
       $formula =~ s/\'/\"/g;
       # The formula will be parsed by the browser *twice* before being 
       # displayed to the user for editing.
       $formula = &HTML::Entities::encode(&HTML::Entities::encode($formula));
       # Escape newlines so they make it into the edit window
       $formula =~ s/\n/\\n/gs;
       # Glue everything together
       $result .= "<a href=\"javascript:celledit(\'".
           $name."','".$formula."');\">".$value."</a>";
       return $result;
   }
   
   sub html_uneditable_cell {
       my ($cell,$bgcolor) = @_;
       my $value = (defined($cell) ? $cell->{'value'} : '');
       $value = &HTML::Entities::encode($value) if ($value !~/&nbsp;/);
       return '&nbsp;'.$value.'&nbsp;';
   }
   
   sub outsheet_html  {
       my $self = shift;
       my ($r) = @_;
       my ($num_uneditable,$realm,$row_type);
       my $requester_is_student = ($ENV{'request.role'} =~ /^st\./);
       if ($self->{'type'} eq 'assesscalc') {
           $num_uneditable = 1;
           $realm = 'Assessment';
           $row_type = 'Item';
       } elsif ($self->{'type'} eq 'studentcalc') {
           $num_uneditable = 26;
           $realm = 'User';
           $row_type = 'Assessment';
       } elsif ($self->{'type'} eq 'classcalc') {
           $num_uneditable = 26;
           $realm = 'Course';
           $row_type = 'Student';
       } else {
           return;  # error
       }
       ####################################
       # Print out header table
       ####################################
       my $num_left = 52-$num_uneditable;
       my $tabledata =<<"END";
   <table border="2">
   <tr>
     <th colspan="2" rowspan="2"><font size="+2">$realm</font></th>
     <td bgcolor="#FFDDDD" colspan="$num_uneditable">
         <b><font size="+1">Import</font></b></td>
     <td colspan="$num_left">
         <b><font size="+1">Calculations</font></b></td>
   </tr><tr>
   END
       my $label_num = 0;
       foreach (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')){
           if ($label_num<$num_uneditable) { 
               $tabledata.='<td bgcolor="#FFDDDD">';
         } else {          } else {
             $rowdata.=',"'.$vl.'"';              $tabledata.='<td>';
         }          }
         $showf++;          $tabledata.="<b><font size=+1>$_</font></b></td>";
     }  # End of foreach($safeval...)          $label_num++;
     if ($ENV{'form.showall'} || ($dataflag)) {  
         return $rowdata.($ENV{'form.showcsv'}?'':'</tr>');  
     } else {  
         return '';  
     }      }
 }      $tabledata.="</tr>\n";
       $r->print($tabledata);
 # ------------------------------------------------------------- Print out sheet      ####################################
       # Print out template row
 sub outsheet {      ####################################
     my ($r,$safeeval,$sheetdata)=@_;      my ($num_cols_output,$row_html,$rowlabel,@rowdata);
     my $maxred = 26;    # The maximum number of cells to show as       
                         # red (uneditable)       if (! $requester_is_student) {
                         # To make student sheets uneditable could we           ($rowlabel,@rowdata) = $self->get_row('-');
                         # set $maxred = 52?          $row_html = '<tr><td>'.$self->format_html_rowlabel($rowlabel).'</td>';
                         #          $num_cols_output = 0;
     my $realm='Course'; # 'assessment', 'user', or 'course' sheet          foreach my $cell (@rowdata) {
     if ($sheetdata->{'sheettype'} eq 'assesscalc') {              if ($requester_is_student || 
         $maxred=1;                  $num_cols_output++ < $num_uneditable) {
         $realm='Assessment';                  $row_html .= '<td bgcolor="#FFDDDD">';
     } elsif ($sheetdata->{'sheettype'} eq 'studentcalc') {                  $row_html .= &html_uneditable_cell($cell,'#FFDDDD');
         $maxred=26;  
         $realm='User';  
     }  
     #  
     # Column label  
     my $tabledata;  
     if ($ENV{'form.showcsv'}) {  
         $tabledata='<pre>';  
     } else {   
         $tabledata='<table border=2><tr><th colspan=2 rowspan=2>'.  
             '<font size=+2>'.$realm.'</font></th>'.  
                   '<td bgcolor=#FFDDDD colspan='.$maxred.  
                   '><b><font size=+1>Import</font></b></td>'.  
                   '<td colspan='.(52-$maxred).  
   '><b><font size=+1>Calculations</font></b></td></tr><tr>';  
         my $showf=0;  
         foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',  
                  'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',  
                  'a','b','c','d','e','f','g','h','i','j','k','l','m',  
                  'n','o','p','q','r','s','t','u','v','w','x','y','z') {  
             $showf++;  
             if ($showf<=$maxred) {   
                 $tabledata.='<td bgcolor="#FFDDDD">';   
             } else {              } else {
                 $tabledata.='<td>';                  $row_html .= '<td bgcolor="#EOFFDD">';
                   $row_html .= &html_editable_cell($cell,'#E0FFDD');
             }              }
             $tabledata.="<b><font size=+1>$_</font></b></td>";              $row_html .= '</td>';
         }          }
         $tabledata.='</tr>'.&rown($safeeval,'-').&rown($safeeval,0);          $row_html.= "</tr>\n";
           $r->print($row_html);
     }      }
     $r->print($tabledata);      ####################################
     #      # Print out summary/export row
     # Prepare to output rows      ####################################
     my $row;      ($rowlabel,@rowdata) = $self->get_row('0');
     my $maxrow=&getmaxrow($safeeval);      $row_html = '<tr><td>'.$self->format_html_rowlabel($rowlabel).'</td>';
     #      $num_cols_output = 0;
     my @sortby=();      foreach my $cell (@rowdata) {
     my @sortidx=();          if ($num_cols_output++ < 26 && ! $requester_is_student) {
     for ($row=1;$row<=$maxrow;$row++) {              $row_html .= '<td bgcolor="#CCCCFF">';
         push (@sortby, $safeeval->reval('$f{"A'.$row.'"}'));              $row_html .= &html_editable_cell($cell,'#CCCCFF');
         push (@sortidx, $row-1);          } else {
     }              $row_html .= '<td bgcolor="#DDCCFF">';
     @sortidx=sort { $sortby[$a] cmp $sortby[$b]; } @sortidx;              $row_html .= &html_uneditable_cell($cell,'#CCCCFF');
     #          }
     # Determine the type of child spreadsheets          $row_html .= '</td>';
     my $what='Student';  
     if ($sheetdata->{'sheettype'} eq 'assesscalc') {  
         $what='Item';  
     } elsif ($sheetdata->{'sheettype'} eq 'studentcalc') {  
         $what='Assessment';  
     }      }
       $row_html.= "</tr>\n";
       $r->print($row_html);
       $r->print('</table>');
       ####################################
       # Prepare to output rows
       ####################################
       my @Rows = $self->sort_indicies();
     #      #
     # Loop through the rows and output them one at a time      # Loop through the rows and output them one at a time
     my $n=0;      my $rows_output=0;
     for ($row=0;$row<$maxrow;$row++) {      foreach my $rownum (@Rows) {
         my $thisrow=&rown($safeeval,$sortidx[$row]+1);          my ($rowlabel,@rowdata) = $self->get_row($rownum);
         if ($thisrow) {          next if ($rowlabel =~ /^\s*$/);
             if (($n/25==int($n/25)) && (!$ENV{'form.showcsv'})) {          next if (($self->{'type'} eq 'assesscalc') && 
                    (! $ENV{'form.showall'})                &&
                    ($rowdata[0]->{'value'} =~ /^\s*$/));
           if (! $ENV{'form.showall'} &&
               $self->{'type'} =~ /^(studentcalc|classcalc)$/) {
               my $row_is_empty = 1;
               foreach my $cell (@rowdata) {
                   if ($cell->{'value'} !~  /^\s*$/) {
                       $row_is_empty = 0;
                       last;
                   }
               }
               next if ($row_is_empty);
           }
           #
           my $defaultbg='#E0FF';
           #
           my $row_html ="\n".'<tr><td><b><font size=+1>'.$rownum.
               '</font></b></td>';
           #
           if ($self->{'type'} eq 'classcalc') {
               $row_html.='<td>'.$self->format_html_rowlabel($rowlabel).'</td>';
               # Output links for each student?
               # Nope, that is already done for us in format_html_rowlabel 
               # (for now)
           } elsif ($self->{'type'} eq 'studentcalc') {
               my $ufn = (split(/:/,$rowlabel))[5];
               $row_html.='<td>'.$self->format_html_rowlabel($rowlabel);
               $row_html.= '<br>'.
                   '<select name="sel_'.$rownum.'" '.
                       'onChange="changesheet('.$rownum.')">'.
                           '<option name="default">Default</option>';
               foreach (@{$self->{'othersheets'}}) {
                   $row_html.='<option name="'.$_.'"';
                   if ($ufn eq $_) {
                       $row_html.=' selected';
                   }
                   $row_html.='>'.$_.'</option>';
               }
               $row_html.='</select></td>';
           } elsif ($self->{'type'} eq 'assesscalc') {
               $row_html.='<td>'.$self->format_html_rowlabel($rowlabel).'</td>';
           }
           #
           my $shown_cells = 0;
           foreach my $cell (@rowdata) {
               my $value    = $cell->{'value'};
               my $formula  = $cell->{'formula'};
               my $cellname = $cell->{'name'};
               #
               my $bgcolor;
               if ($shown_cells && ($shown_cells/5 == int($shown_cells/5))) {
                   $bgcolor = $defaultbg.'99';
               } else {
                   $bgcolor = $defaultbg.'DD';
               }
               $bgcolor='#FFDDDD' if ($shown_cells < $num_uneditable);
               #
               $row_html.='<td bgcolor='.$bgcolor.'>';
               if ($requester_is_student || $shown_cells < $num_uneditable) {
                   $row_html .= &html_uneditable_cell($cell,$bgcolor);
               } else {
                   $row_html .= &html_editable_cell($cell,$bgcolor);
               }
               $row_html.='</td>';
               $shown_cells++;
           }
           if ($row_html) {
               if ($rows_output % 25 == 0) {
                 $r->print("</table>\n<br>\n");                  $r->print("</table>\n<br>\n");
                 $r->rflush();                  $r->rflush();
                 $r->print('<table border=2><tr><td>&nbsp;<td>'.$what.'</td>');                  $r->print('<table border=2>'.
                 $r->print('<td>'.                            '<tr><td>&nbsp;<td>'.$row_type.'</td>'.
                             '<td>'.
                           join('</td><td>',                            join('</td><td>',
                                (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.                                 (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
                                       'abcdefghijklmnopqrstuvwxyz'))).                                        'abcdefghijklmnopqrstuvwxyz'))).
                           "</td></tr>\n");                            "</td></tr>\n");
             }              }
             $n++;              $rows_output++;
             $r->print($thisrow);              $r->print($row_html);
         }          }
     }      }
     $r->print($ENV{'form.showcsv'}?'</pre>':'</table>');      #
       $r->print('</table>');
       #
       # Debugging code (be sure to uncomment errorlog code in safe space):
       #
       # $r->print("\n<pre>");
       # $r->print(&geterrorlog($self));
       # $r->print("\n</pre>");
       return 1;
   }
   
   ############################################
   ##         csv output routines            ##
   ############################################
   sub outsheet_csv   {
       my $self = shift;
       my ($r) = @_;
       my $csvdata = '';
       my @Values;
       ####################################
       # Prepare to output rows
       ####################################
       my @Rows = $self->sort_indicies();
       #
       # Loop through the rows and output them one at a time
       my $rows_output=0;
       foreach my $rownum (@Rows) {
           my ($rowlabel,@rowdata) = $self->get_row($rownum);
           next if ($rowlabel =~ /^\s*$/);
           push (@Values,$self->format_csv_rowlabel($rowlabel));
           foreach my $cell (@rowdata) {
               push (@Values,'"'.$cell->{'value'}.'"');
           }
           $csvdata.= join(',',@Values)."\n";
           @Values = ();
       }
       #
       # Write the CSV data to a file and serve up a link
       #
       my $filename = '/prtspool/'.
           $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
           time.'_'.rand(1000000000).'.csv';
       my $file;
       unless ($file = Apache::File->new('>'.'/home/httpd'.$filename)) {
           $r->log_error("Couldn't open $filename for output $!");
           $r->print("Problems occured in writing the csv file.  ".
                     "This error has been logged.  ".
                     "Please alert your LON-CAPA administrator.");
           $r->print("<pre>\n".$csvdata."</pre>\n");
           return 0;
       }
       print $file $csvdata;
       close($file);
       $r->print('<br /><br />'.
                 '<a href="'.$filename.'">Your CSV spreadsheet.</a>'."\n");
       #
       return 1;
   }
   
   ############################################
   ##        Excel output routines           ##
   ############################################
   sub outsheet_recursive_excel {
       my $self = shift;
       my ($r) = @_;
       my $c = $r->connection;
       return undef if ($self->{'type'} ne 'classcalc');
       my ($workbook,$filename) = $self->create_excel_spreadsheet($r);
       return undef if (! defined($workbook));
       #
       # Create main worksheet
       my $main_worksheet = $workbook->addworksheet('main');
       #
       # Figure out who the students are
       my %f=$self->formulas();
       my $count = 0;
       $r->print(<<END);
   <p>
   Compiling Excel Workbook with a worksheet for each student.
   </p><p>
   This operation may take longer than a complete recalculation of the
   spreadsheet. 
   </p><p>
   To abort this operation, hit the stop button on your browser.
   </p><p>
   A link to the spreadsheet will be available at the end of this process.
   </p>
   <p>
   END
       $r->rflush();
       my $starttime = time;
       foreach my $rownum ($self->sort_indicies()) {
           $count++;
           my ($sname,$sdom) = split(':',$f{'A'.$rownum});
           my $student_excel_worksheet=$workbook->addworksheet($sname.'@'.$sdom);
           # Create a new spreadsheet
           my $studentsheet = &Apache::lonspreadsheet::Spreadsheet->new
                                      ($sname,$sdom,'studentcalc',undef);
           # Read in the spreadsheet definition
           $studentsheet->update_student_sheet($r,$c);
           # Stuff the sheet into excel
           $studentsheet->export_sheet_as_excel($student_excel_worksheet);
           my $totaltime = int((time - $starttime) / $count * $self->{'maxrow'});
           my $timeleft = int((time - $starttime) / $count * ($self->{'maxrow'} - $count));
           if ($count % 5 == 0) {
               $r->print($count.' students completed.'.
                         '  Time remaining: '.$timeleft.' sec. '.
                         '  Estimated total time: '.$totaltime." sec <br />\n");
               $r->rflush();
           }
           if(defined($c) && ($c->aborted())) {
               last;
           }
       }
       #
       if(! $c->aborted() ) {
           $r->print('All students spreadsheets completed!<br />');
           $r->rflush();
           #
           # &export_sheet_as_excel fills $worksheet with the data from $sheet
           $self->export_sheet_as_excel($main_worksheet);
           #
           $workbook->close();
           # Okay, the spreadsheet is taken care of, so give the user a link.
           $r->print('<br /><br />'.
                     '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n");
       } else {
           $workbook->close();  # Not sure how necessary this is.
           #unlink('/home/httpd'.$filename); # No need to keep this around?
       }
       return 1;
   }
   
   sub outsheet_excel {
       my $self = shift;
       my ($r) = @_;
       my ($workbook,$filename) = $self->create_excel_spreadsheet($r);
       return undef if (! defined($workbook));
       my $sheetname;
       if ($self->{'type'} eq 'classcalc') {
           $sheetname = 'Main';
       } elsif ($self->{'type'} eq 'studentcalc') {
           $sheetname = $self->{'uname'}.'@'.$self->{'udom'};
       } elsif ($self->{'type'} eq 'assesscalc') {
           $sheetname = $self->{'uname'}.'@'.$self->{'udom'}.' assessment';
       }
       my $worksheet = $workbook->addworksheet($sheetname);
       #
       # &export_sheet_as_excel fills $worksheet with the data from $sheet
       $self->export_sheet_as_excel($worksheet);
       #
       $workbook->close();
       # Okay, the spreadsheet is taken care of, so give the user a link.
       $r->print('<br /><br />'.
                 '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n");
       return 1;
   }
   
   sub create_excel_spreadsheet {
       my $self = shift;
       my ($r) = @_;
       my $filename = '/prtspool/'.
           $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
           time.'_'.rand(1000000000).'.xls';
       my $workbook  = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
       if (! defined($workbook)) {
           $r->log_error("Error creating excel spreadsheet $filename: $!");
           $r->print("Problems creating new Excel file.  ".
                     "This error has been logged.  ".
                     "Please alert your LON-CAPA administrator");
           return undef;
       }
       #
       # The excel spreadsheet stores temporary data in files, then put them
       # together.  If needed we should be able to disable this (memory only).
       # The temporary directory must be specified before calling 'addworksheet'.
       # File::Temp is used to determine the temporary directory.
       $workbook->set_tempdir('/home/httpd/perl/tmp');
       #
       # Determine the name to give the worksheet
       return ($workbook,$filename);
   }
   
   sub export_sheet_as_excel {
       my $self = shift;
       my $worksheet = shift;
       #
       my $rows_output = 0;
       my $cols_output = 0;
       ####################################
       #    Write an identifying row      #
       ####################################
       my @Headerinfo = ($self->{'coursedesc'});
       my $title = $self->gettitle();
       $cols_output = 0;    
       if (defined($title)) {
           $worksheet->write($rows_output++,$cols_output++,$title);
       }
       ####################################
       #   Write the summary/export row   #
       ####################################
       my ($rowlabel,@rowdata) = &get_row($self,'0');
       my $label = &format_excel_rowlabel($self,$rowlabel);
       $cols_output = 0;
       $worksheet->write($rows_output,$cols_output++,$label);
       foreach my $cell (@rowdata) {
           $worksheet->write($rows_output,$cols_output++,$cell->{'value'});
       }
       $rows_output+= 2;   # Skip a row, just for fun
       ####################################
       # Prepare to output rows
       ####################################
       my @Rows = &sort_indicies($self);
       #
       # Loop through the rows and output them one at a time
       foreach my $rownum (@Rows) {
           my ($rowlabel,@rowdata) = &get_row($self,$rownum);
           next if ($rowlabel =~ /^[\s]*$/);
           $cols_output = 0;
           my $label = &format_excel_rowlabel($self,$rowlabel);
           if ( ! $ENV{'form.showall'} &&
                $self->{'type'} =~ /^(studentcalc|classcalc)$/) {
               my $row_is_empty = 1;
               foreach my $cell (@rowdata) {
                   if ($cell->{'value'} !~  /^\s*$/) {
                       $row_is_empty = 0;
                       last;
                   }
               }
               next if ($row_is_empty);
           }
           $worksheet->write($rows_output,$cols_output++,$rownum);
           $worksheet->write($rows_output,$cols_output++,$label);
           if (ref($label)) {
               $cols_output = (scalar(@$label));
           }
           foreach my $cell (@rowdata) {
               $worksheet->write($rows_output,$cols_output++,$cell->{'value'});
           }
           $rows_output++;
       }
       return;
   }
   
   ############################################
   ##          XML output routines           ##
   ############################################
   sub outsheet_xml   {
       my $self = shift;
       my ($r) = @_;
       ## Someday XML
       ## Will be rendered for the user
       ## But not on this day
   }
   
   ##
   ## Outsheet - calls other outsheet_* functions
   ##
   sub outsheet {
       my $self = shift;
       my ($r)=@_;
       if (! exists($ENV{'form.output'})) {
           $ENV{'form.output'} = 'HTML';
       }
       if (lc($ENV{'form.output'}) eq 'csv') {
           $self->outsheet_csv($r);
       } elsif (lc($ENV{'form.output'}) eq 'excel') {
           $self->outsheet_excel($r);
       } elsif (lc($ENV{'form.output'}) eq 'recursive excel') {
           $self->outsheet_recursive_excel($r);
   #    } elsif (lc($ENV{'form.output'}) eq 'xml' ) {
   #        $self->outsheet_xml($r);
       } else {
           $self->outsheet_html($r);
       }
 }  }
   
 #  #
 # ----------------------------------------------- Read list of available sheets  # othersheets: Returns the list of other spreadsheets available 
 #   #
 sub othersheets {  sub othersheets {
     my ($safeeval,$stype)=@_;      my $self = shift;
     #      my ($stype)=@_;
     my $cnum  = &getcnum($safeeval);      $stype = $self->{'type'} if (! defined($stype));
     my $cdom  = &getcdom($safeeval);      #
     my $chome = &getchome($safeeval);      my $cnum  = $self->{'cnum'};
       my $cdom  = $self->{'cdom'};
       my $chome = $self->{'chome'};
     #      #
     my @alternatives=();      my @alternatives=();
     my %results=&Apache::lonnet::dump($stype.'_spreadsheets',$cdom,$cnum);      my %results=&Apache::lonnet::dump($stype.'_spreadsheets',$cdom,$cnum);
Line 1361  sub othersheets { Line 2664  sub othersheets {
     return @alternatives;       return @alternatives; 
 }  }
   
   
 #  #
 # -------------------------------------- Parse a spreadsheet  # Parse a spreadsheet
 #   # 
 sub parse_sheet {  sub parse_sheet {
     # $sheetxml is a scalar reference or a scalar      # $sheetxml is a scalar reference or a scalar
Line 1390  sub parse_sheet { Line 2692  sub parse_sheet {
     return \%f;      return \%f;
 }  }
   
 #  
 # -------------------------------------- Read spreadsheet formulas for a course  
 #  
   
 sub readsheet {  sub readsheet {
     my ($safeeval,$sheetdata,$fn)=@_;      my $self = shift;
       my ($fn)=@_;
     #      #
     my $stype = $sheetdata->{'sheettype'};      my $stype = $self->{'type'};
     my $cnum  = $sheetdata->{'cnum'};      my $cnum  = $self->{'cnum'};
     my $cdom  = $sheetdata->{'cdom'};      my $cdom  = $self->{'cdom'};
     my $chome = $sheetdata->{'chome'};      my $chome = $self->{'chome'};
     #      #
     if (! defined($fn)) {      if (! defined($fn)) {
         # There is no filename. Look for defaults in course and global, cache          # There is no filename. Look for defaults in course and global, cache
Line 1421  sub readsheet { Line 2720  sub readsheet {
         }          }
     }      }
     # $fn now has a value      # $fn now has a value
     &setfilename($safeeval,$fn);      $self->{'filename'} = $fn;
     # see if sheet is cached      # see if sheet is cached
     my $fstring='';      if (exists($spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn})) {
     if ($fstring=$spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}) {          
         &setformulas($safeeval,split(/\_\_\_\;\_\_\_/,$fstring));          my %tmp = split(/___;___/,
                           $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn});
           $self->formulas(\%tmp);
     } else {      } else {
         # Not cached, need to read          # Not cached, need to read
         my %f=();          my %f=();
Line 1437  sub readsheet { Line 2738  sub readsheet {
             if ($fh=Apache::File->new($includedir.'/'.$dfn)) {              if ($fh=Apache::File->new($includedir.'/'.$dfn)) {
                 $sheetxml=join('',<$fh>);                  $sheetxml=join('',<$fh>);
             } else {              } else {
                 $sheetxml='<field row="0" col="A">"Error"</field>';                  # $sheetxml='<field row="0" col="A">"Error"</field>';
                   $sheetxml='<field row="0" col="A"></field>';
             }              }
             %f=%{&parse_sheet(\$sheetxml)};              %f=%{&parse_sheet(\$sheetxml)};
         } elsif($fn=~/\/*\.spreadsheet$/) {          } elsif($fn=~/\/*\.spreadsheet$/) {
Line 1449  sub readsheet { Line 2751  sub readsheet {
             }              }
             %f=%{&parse_sheet(\$sheetxml)};              %f=%{&parse_sheet(\$sheetxml)};
         } else {          } else {
             my $sheet='';  
             my %tmphash = &Apache::lonnet::dump($fn,$cdom,$cnum);              my %tmphash = &Apache::lonnet::dump($fn,$cdom,$cnum);
             my ($tmp) = keys(%tmphash);              my ($tmp) = keys(%tmphash);
             unless ($tmp =~ /^(con_lost|error|no_such_host)/i) {              if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
                 foreach (keys(%tmphash)) {                  foreach (keys(%tmphash)) {
                     $f{$_}=$tmphash{$_};                      $f{$_}=$tmphash{$_};
                 }                  }
               } else {
                   # Unable to grab the specified spreadsheet,
                   # so we get the default ones instead.
                   $fn = 'default_'.$stype;
                   $self->{'filename'} = $fn;
                   my $dfn = $fn;
                   $dfn =~ s/\_/\./g;
                   my $sheetxml;
                   if (my $fh=Apache::File->new($includedir.'/'.$dfn)) {
                       $sheetxml = join('',<$fh>);
                   } else {
                       $sheetxml='<field row="0" col="A">'.
                           '"Unable to load spreadsheet"</field>';
                   }
                   %f=%{&parse_sheet(\$sheetxml)};
             }              }
         }          }
         # Cache and set          # Cache and set
         $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f);            $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f);  
         &setformulas($safeeval,%f);          $self->formulas(\%f);
     }      }
 }  }
   
 # -------------------------------------------------------- Make new spreadsheet  
   
 sub makenewsheet {  
     my ($uname,$udom,$stype,$usymb)=@_;  
     my %sheetdata=();  
     $sheetdata{'uname'} = $uname;  
     $sheetdata{'udom'}  = $udom;  
     $sheetdata{'sheettype'} = $stype;  
     $sheetdata{'usymb'} = $usymb;  
     $sheetdata{'cid'}   = $ENV{'request.course.id'};  
     $sheetdata{'csec'}  = &Apache::lonnet::usection  
                                ($udom,$uname,$ENV{'request.course.id'});  
     $sheetdata{'cfn'}   = $ENV{'request.course.fn'};  
     $sheetdata{'cnum'}  = $ENV{'course.'.$ENV{'request.course.id'}.'.num'};  
     $sheetdata{'cdom'}  = $ENV{'course.'.$ENV{'request.course.id'}.'.domain'};  
     $sheetdata{'chome'} = $ENV{'course.'.$ENV{'request.course.id'}.'.home'};  
     $sheetdata{'uhome'} = &Apache::lonnet::homeserver($uname,$udom);  
       
     my $safeeval=initsheet($stype);  
     my $initstring = '';  
     foreach (keys(%sheetdata)) {  
         $initstring.= qq{\$$_="$sheetdata{$_}";};  
     }  
     $safeeval->reval($initstring);  
     return $safeeval,\%sheetdata;  
 }  
   
 # ------------------------------------------------------------ Save spreadsheet  # ------------------------------------------------------------ Save spreadsheet
   
 sub writesheet {  sub writesheet {
     my ($safeeval,$makedef)=@_;      my $self = shift;
     my $cid=&getcid($safeeval);      my ($makedef)=@_;
       my $cid=$self->{'cid'};
     if (&Apache::lonnet::allowed('opa',$cid)) {      if (&Apache::lonnet::allowed('opa',$cid)) {
         my %f=&getformulas($safeeval);          my %f=$self->formulas();
         my $stype=&gettype($safeeval);          my $stype= $self->{'type'};
         my $cnum=&getcnum($safeeval);          my $cnum = $self->{'cnum'};
         my $cdom=&getcdom($safeeval);          my $cdom = $self->{'cdom'};
         my $chome=&getchome($safeeval);          my $chome= $self->{'chome'};
         my $fn=&getfilename($safeeval);          my $fn   = $self->{'filename'};
         # Cache new sheet          # Cache new sheet
         $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f);          $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f);
         # Write sheet          # Write sheet
         my $sheetdata='';  
         foreach (keys(%f)) {          foreach (keys(%f)) {
             unless ($f{$_} eq 'import') {              delete($f{$_}) if ($f{$_} eq 'import');
                 $sheetdata.=&Apache::lonnet::escape($_).'='.  
                     &Apache::lonnet::escape($f{$_}).'&';  
             }  
         }          }
         $sheetdata=~s/\&$//;          my $reply = &Apache::lonnet::put($fn,\%f,$cdom,$cnum);
         my $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'.$fn.':'.  
                                          $sheetdata,$chome);  
         if ($reply eq 'ok') {          if ($reply eq 'ok') {
             $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'.              $reply = &Apache::lonnet::put($stype.'_spreadsheets',
                                           $stype.'_spreadsheets:'.                              {$fn => $ENV{'user.name'}.'@'.$ENV{'user.domain'}},
                                           &Apache::lonnet::escape($fn).                                            $cdom,$cnum);
                                           '='.$ENV{'user.name'}.'@'.  
                                           $ENV{'user.domain'},  
                                           $chome);  
             if ($reply eq 'ok') {              if ($reply eq 'ok') {
                 if ($makedef) {                   if ($makedef) { 
                     return &Apache::lonnet::reply('put:'.$cdom.':'.$cnum.                      $reply = &Apache::lonnet::put('environment',
                                                   ':environment:'.                                      {'spreadsheet_default_'.$stype => $fn },
                                                   'spreadsheet_default_'.                                                    $cdom,$cnum);
                                                   $stype.'='.                      if ($reply eq 'ok' && 
                                                   &Apache::lonnet::escape($fn),                          ($self->{'type'} eq 'studentcalc' ||
                                                   $chome);                           $self->{'type'} eq 'assesscalc')) {
                           # Expire the spreadsheets of the other students.
                           &Apache::lonnet::expirespread('','','studentcalc','');
                       }
                       return $reply;
                 }                   } 
                 return $reply;                  return $reply;
             }               } 
Line 1544  sub writesheet { Line 2828  sub writesheet {
 # ----------------------------------------------- Make a temp copy of the sheet  # ----------------------------------------------- Make a temp copy of the sheet
 # "Modified workcopy" - interactive only  # "Modified workcopy" - interactive only
 #  #
   
 sub tmpwrite {  sub tmpwrite {
     my $safeeval=shift;      my $self = shift;
     my $fn=$ENV{'user.name'}.'_'.      my $fn=$ENV{'user.name'}.'_'.
         $ENV{'user.domain'}.'_spreadsheet_'.&getusymb($safeeval).'_'.          $ENV{'user.domain'}.'_spreadsheet_'.$self->{'usymb'}.'_'.
            &getfilename($safeeval);             $self->{'filename'};
     $fn=~s/\W/\_/g;      $fn=~s/\W/\_/g;
     $fn=$tmpdir.$fn.'.tmp';      $fn=$Apache::lonnet::tmpdir.$fn.'.tmp';
     my $fh;      my $fh;
     if ($fh=Apache::File->new('>'.$fn)) {      if ($fh=Apache::File->new('>'.$fn)) {
  print $fh join("\n",&getformulas($safeeval));          my %f = $self->formulas();
           while( my ($cell,$formula) = each(%f)) {
               print $fh &Apache::lonnet::escape($cell)."=".&Apache::lonnet::escape($formula)."\n";
           }
     }      }
 }  }
   
 # ---------------------------------------------------------- Read the temp copy  
   
   # ---------------------------------------------------------- Read the temp copy
 sub tmpread {  sub tmpread {
     my ($safeeval,$nfield,$nform)=@_;      my $self = shift;
       my ($nfield,$nform)=@_;
     my $fn=$ENV{'user.name'}.'_'.      my $fn=$ENV{'user.name'}.'_'.
            $ENV{'user.domain'}.'_spreadsheet_'.&getusymb($safeeval).'_'.             $ENV{'user.domain'}.'_spreadsheet_'.$self->{'usymb'}.'_'.
            &getfilename($safeeval);             $self->{'filename'};
     $fn=~s/\W/\_/g;      $fn=~s/\W/\_/g;
     $fn=$tmpdir.$fn.'.tmp';      $fn=$Apache::lonnet::tmpdir.$fn.'.tmp';
     my $fh;      my $fh;
     my %fo=();      my %fo=();
     my $countrows=0;      my $countrows=0;
     if ($fh=Apache::File->new($fn)) {      if ($fh=Apache::File->new($fn)) {
         my $name;          while (<$fh>) {
         while ($name=<$fh>) {      chomp;
     chomp($name);              my ($cell,$formula) = split(/=/);
             my $value=<$fh>;              $cell    = &Apache::lonnet::unescape($cell);
             chomp($value);              $formula = &Apache::lonnet::unescape($formula);
             $fo{$name}=$value;              $fo{$cell} = $formula;
             if ($name=~/^A(\d+)$/) {  
  if ($1>$countrows) {  
     $countrows=$1;  
                 }  
             }  
         }          }
     }      }
     if ($nform eq 'changesheet') {      if ($nform eq 'changesheet') {
         $fo{'A'.$nfield}=(split(/\_\_\&\&\&\_\_/,$fo{'A'.$nfield}))[0];          $fo{'A'.$nfield}=(split(/__&&&\__/,$fo{'A'.$nfield}))[0];
         unless ($ENV{'form.sel_'.$nfield} eq 'Default') {          unless ($ENV{'form.sel_'.$nfield} eq 'Default') {
     $fo{'A'.$nfield}.='__&&&__'.$ENV{'form.sel_'.$nfield};      $fo{'A'.$nfield}.='__&&&__'.$ENV{'form.sel_'.$nfield};
         }          }
     } elsif ($nfield eq 'insertrow') {  
         $countrows++;  
         my $newrow=substr('000000'.$countrows,-7);  
         if ($nform eq 'top') {  
     $fo{'A'.$countrows}='--- '.$newrow;  
         } else {  
             $fo{'A'.$countrows}='~~~ '.$newrow;  
         }  
     } else {      } else {
        if ($nfield) { $fo{$nfield}=$nform; }         if ($nfield) { $fo{$nfield}=$nform; }
     }      }
     &setformulas($safeeval,%fo);      $self->formulas(\%fo);
 }  }
   
 ##################################################  ##################################################################
 ##################################################  ##                  Row label formatting routines               ##
   ##################################################################
 =pod  sub format_html_rowlabel {
       my $self = shift;
 =item &parmval()      my $rowlabel = shift;
       return '' if ($rowlabel eq '');
 Determine the value of a parameter.      my ($type,$labeldata) = split(':',$rowlabel,2);
       my $result = '';
 Inputs: $what, the parameter needed, $safeeval, the safe space      if ($type eq 'symb') {
           my ($symb,$mapid,$resid,$title,$ufn) = split(':',$labeldata);
 Returns: The value of a parameter, or '' if none.          $ufn   = 'default' if (!defined($ufn) || $ufn eq '');
           $ufn   = &Apache::lonnet::unescape($ufn);
 This function cascades through the possible levels searching for a value for          $symb  = &Apache::lonnet::unescape($symb);
 a parameter.  The levels are checked in the following order:          $title = &Apache::lonnet::unescape($title);
 user, course (at section level and course level), map, and lonnet::metadata.          $result = '<a href="/adm/assesscalc?usymb='.$symb.
 This function uses %parmhash, which must be tied prior to calling it.              '&uname='.$self->{'uname'}.'&udom='.$self->{'udom'}.
 This function also requires %courseopt and %useropt to be initialized for                  '&ufn='.$ufn.
 this user and course.                      '&mapid='.$mapid.'&resid='.$resid.'">'.$title.'</a>';
       } elsif ($type eq 'student') {
 =cut          my ($sname,$sdom,$fullname,$section,$id) = split(':',$labeldata);
           if ($fullname =~ /^\s*$/) {
 ##################################################              $fullname = $sname.'@'.$sdom;
 ##################################################          }
 sub parmval {          $result ='<a href="/adm/studentcalc?uname='.$sname.
     my ($what,$safeeval,$sheetdata)=@_;              '&udom='.$sdom.'">';
     my $symb  = $sheetdata->{'usymb'};          $result.=$section.'&nbsp;'.$id."&nbsp;".$fullname.'</a>';
     unless ($symb) { return ''; }      } elsif ($type eq 'parameter') {
     #          $result = $labeldata;
     my $cid   = $sheetdata->{'cid'};      } else {
     my $csec  = $sheetdata->{'csec'};          $result = '<b><font size=+1>'.$rowlabel.'</font></b>';
     my $uname = $sheetdata->{'uname'};      }
     my $udom  = $sheetdata->{'udom'};      return $result;
     my $result='';  }
     #  
     my ($mapname,$id,$fn)=split(/\_\_\_/,$symb);  sub format_csv_rowlabel {
     # Cascading lookup scheme      my $self = shift;
     my $rwhat=$what;      my $rowlabel = shift;
     $what =~ s/^parameter\_//;      return '' if ($rowlabel eq '');
     $what =~ s/\_([^\_]+)$/\.$1/;      my ($type,$labeldata) = split(':',$rowlabel,2);
     #      my $result = '';
     my $symbparm = $symb.'.'.$what;      if ($type eq 'symb') {
     my $mapparm  = $mapname.'___(all).'.$what;          my ($symb,$mapid,$resid,$title,$ufn) = split(':',$labeldata);
     my $usercourseprefix = $uname.'_'.$udom.'_'.$cid;          $ufn   = &Apache::lonnet::unescape($ufn);
     #          $symb  = &Apache::lonnet::unescape($symb);
     my $seclevel  = $usercourseprefix.'.['.$csec.'].'.$what;          $title = &Apache::lonnet::unescape($title);
     my $seclevelr = $usercourseprefix.'.['.$csec.'].'.$symbparm;          $result = $title;
     my $seclevelm = $usercourseprefix.'.['.$csec.'].'.$mapparm;      } elsif ($type eq 'student') {
     #          my ($sname,$sdom,$fullname,$section,$id) = split(':',$labeldata);
     my $courselevel  = $usercourseprefix.'.'.$what;          $result = join('","',($sname,$sdom,$fullname,$section,$id));
     my $courselevelr = $usercourseprefix.'.'.$symbparm;      } elsif ($type eq 'parameter') {
     my $courselevelm = $usercourseprefix.'.'.$mapparm;          $labeldata =~ s/<br>/ /g;
     # fourth, check user          $result = $labeldata;
     if ($uname) {       } else {
         return $useropt{$courselevelr} if ($useropt{$courselevelr});          $result = $rowlabel;
         return $useropt{$courselevelm} if ($useropt{$courselevelm});      }
         return $useropt{$courselevel}  if ($useropt{$courselevel});      return '"'.$result.'"';
     }  }
     # third, check course  
     if ($csec) {  sub format_excel_rowlabel {
         return $courseopt{$seclevelr} if ($courseopt{$seclevelr});      my $self = shift;
         return $courseopt{$seclevelm} if ($courseopt{$seclevelm});      my $rowlabel = shift;
         return $courseopt{$seclevel}  if ($courseopt{$seclevel});      return '' if ($rowlabel eq '');
       my ($type,$labeldata) = split(':',$rowlabel,2);
       my $result = '';
       if ($type eq 'symb') {
           my ($symb,$mapid,$resid,$title,$ufn) = split(':',$labeldata);
           $ufn   = &Apache::lonnet::unescape($ufn);
           $symb  = &Apache::lonnet::unescape($symb);
           $title = &Apache::lonnet::unescape($title);
           $result = $title;
       } elsif ($type eq 'student') {
           my ($sname,$sdom,$fullname,$section,$id) = split(':',$labeldata);
           $section = '' if (! defined($section));
           $id      = '' if (! defined($id));
           my @Data = ($sname,$sdom,$fullname,$section,$id);
           $result = \@Data;
       } elsif ($type eq 'parameter') {
           $labeldata =~ s/<br>/ /g;
           $result = $labeldata;
       } else {
           $result = $rowlabel;
     }      }
     #      return $result;
     return $courseopt{$courselevelr} if ($courseopt{$courselevelr});  
     return $courseopt{$courselevelm} if ($courseopt{$courselevelm});  
     return $courseopt{$courselevel}  if ($courseopt{$courselevel});  
     # second, check map parms  
     my $thisparm = $parmhash{$symbparm};  
     return $thisparm if ($thisparm);  
     # first, check default  
     return &Apache::lonnet::metadata($fn,$rwhat.'.default');  
 }  }
   
 # ---------------------------------------------- Update rows for course listing  # ---------------------------------------------- Update rows for course listing
 sub updateclasssheet {  sub updateclasssheet {
     my ($safeeval,$sheetdata) = @_;      my $self = shift;
     my $cnum  =$sheetdata->{'cnum'};      my $cnum  =$self->{'cnum'};
     my $cdom  =$sheetdata->{'cdom'};      my $cdom  =$self->{'cdom'};
     my $cid   =$sheetdata->{'cid'};      my $cid   =$self->{'cid'};
     my $chome =$sheetdata->{'chome'};      my $chome =$self->{'chome'};
       #
       %Section = ();
     #      #
     # Read class list and row labels      # Read class list and row labels
     my %classlist;      my $classlist = &Apache::loncoursedata::get_classlist();
     my @tmp = &Apache::lonnet::dump('classlist',$cdom,$cnum);      if (! defined($classlist)) {
     if ($tmp[0] !~ /^error/) {          return 'Could not access course classlist';
         %classlist = @tmp;      } 
     } else {  
         return 'Could not access course data';  
     }  
     undef @tmp;  
     #      #
     my %currentlist=();      my %currentlist=();
     my $now=time;      foreach my $student (keys(%$classlist)) {
     foreach my $student (keys(%classlist)) {          my ($studentDomain,$studentName,$end,$start,$id,$studentSection,
         my ($end,$start)=split(/\:/,$classlist{$student});              $fullname,$status)   =   @{$classlist->{$student}};
         my $active=1;          $Section{$studentName.':'.$studentDomain} = $studentSection;
         $active = 0 if (($end) && ($now>$end));          if ($ENV{'form.Status'} eq $status || $ENV{'form.Status'} eq 'Any') {
         $active = 1 if ($ENV{'form.Status'} eq 'Any');              $currentlist{$student}=join(':',('student',$studentName,
         $active = !$active if ($ENV{'form.Status'} eq 'Expired');                                               $studentDomain,$fullname,
         if ($active) {                                               $studentSection,$id));
             my $rowlabel='';          }
             my ($studentName,$studentDomain)=split(/\:/,$student);      }
             my $studentSection=&Apache::lonnet::usection($studentDomain,  
                                                          $studentName,$cid);  
             if ($studentSection==-1) {  
                 unless ($ENV{'form.showcsv'}) {  
                     $rowlabel='<font color=red>Data not available: '.  
                         $studentName.'</font>';  
                 } else {  
                     $rowlabel='ERROR","'.$studentName.  
                         '","Data not available","","","';  
                 }  
             } else {  
                 my %reply=&Apache::lonnet::idrget($studentDomain,$studentName);  
                 my %studentInformation=&Apache::lonnet::get  
                     ('environment',  
                      ['lastname','generation','firstname','middlename','id'],  
                      $studentDomain,$studentName);  
                 if (! $ENV{'form.showcsv'}) {  
                     $rowlabel='<a href="/adm/studentcalc?uname='.$studentName.  
                         '&udom='.$studentDomain.'">'.  
                             $studentSection.'&nbsp;';  
                     foreach ('id','firstname','middlename',  
                              'lastname','generation'){  
                         $rowlabel.=$studentInformation{$_}."&nbsp;";  
                     }  
                     $rowlabel.='</a>';  
                 } else {  
                     $rowlabel= '"'.join('","',  
                                         ($studentSection,  
                                          $studentInformation{'id'},  
                                          $studentInformation{'firstname'},  
                                          $studentInformation{'middlename'},  
                                          $studentInformation{'lastname'},  
                                          $studentInformation{'generation'})  
                                         ).'"';  
                 }  
             }  
             $currentlist{$student}=$rowlabel;  
         } # end of if ($active)  
     } # end of foreach my $student (keys(%classlist))  
     #      #
     # Find discrepancies between the course row table and this      # Find discrepancies between the course row table and this
     #      #
     my %f=&getformulas($safeeval);      my %f=$self->formulas();
     my $changed=0;      my $changed=0;
     #      #
     my $maxrow=0;      $self->{'maxrow'}=0;
     my %existing=();      my %existing=();
     #      #
     # Now obsolete rows      # Now obsolete rows
     foreach (keys(%f)) {      foreach my $rownum ($self->rows()) {
         if ($_=~/^A(\d+)/) {          my $cell = 'A'.$rownum;
             $maxrow=($1>$maxrow)?$1:$maxrow;          if ($rownum > $self->{'maxrow'}) {
             $existing{$f{$_}}=1;              $self->{'maxrow'}= $rownum;
             unless ((defined($currentlist{$f{$_}})) || (!$1) ||          }
                     ($f{$_}=~/^(\~\~\~|\-\-\-)/)) {          $existing{$f{$cell}}=1;
                 $f{$_}='!!! Obsolete';          if (! defined($currentlist{$f{$cell}}) && ($f{$cell}=~/^(~~~|---)/)) {
                 $changed=1;              $f{$cell}='!!! Obsolete';
             }              $changed=1;
         }          }
     }      }
     #      #
     # New and unknown keys      # New and unknown keys
     foreach (sort keys(%currentlist)) {      foreach my $student (sort keys(%currentlist)) {
         unless ($existing{$_}) {          next if ($existing{$student});
             $changed=1;          $changed=1;
             $maxrow++;          $self->{'maxrow'}++;
             $f{'A'.$maxrow}=$_;          $f{'A'.$self->{'maxrow'}}=$student;
         }  
     }      }
     if ($changed) { &setformulas($safeeval,%f); }      $self->formulas(\%f) if ($changed);
     #      #
     &setmaxrow($safeeval,$maxrow);      $self->rowlabels(\%currentlist);
     &setrowlabels($safeeval,%currentlist);  
 }  }
   
 # ----------------------------------- Update rows for student and assess sheets  # ----------------------------------- Update rows for student and assess sheets
 sub updatestudentassesssheet {  sub get_student_rowlabels {
     my ($safeeval,$sheetdata) = @_;      my $self = shift;
     my %bighash;      #
     my $stype=$sheetdata->{'sheettype'};      my %course_db;
     my $uname=$sheetdata->{'uname'};      #
     my $udom =$sheetdata->{'udom'};      my $stype = $self->{'type'};
     my %current=();      my $uname = $self->{'uname'};
     if  ($updatedata      my $udom  = $self->{'udom'};
          {$ENV{'request.course.fn'}.'_'.$stype.'_'.$uname.'_'.$udom}) {      #
         %current=split(/\_\_\_\;\_\_\_/,      $self->{'rowlabel'} = {};
                        $updatedata{$ENV{'request.course.fn'}.      #
                                        '_'.$stype.'_'.$uname.'_'.$udom});      my $identifier =$self->{'coursefilename'}.'_'.$stype;
       if  (exists($rowlabel_cache{$identifier})) {
           my %tmp = split(/___;___/,$rowlabel_cache{$identifier});
           $self->rowlabels(\%tmp);
     } else {      } else {
           # Get the data and store it in the cache
         # Tie hash          # Tie hash
         tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db',          tie(%course_db,'GDBM_File',$self->{'coursefilename'}.'.db',
             &GDBM_READER(),0640);              &GDBM_READER(),0640);
         if (! tied(%bighash)) {          if (! tied(%course_db)) {
             return 'Could not access course data';              return 'Could not access course data';
         }          }
         # Get all assessments          #
         my %allkeys=('timestamp' =>           my %assesslist = ();
                      'Timestamp of Last Transaction<br>timestamp',          foreach ('Feedback','Evaluation','Tutoring','Discussion') {
                      'subnumber' =>              my $symb = '_'.lc($_);
                      'Number of Submissions<br>subnumber',              $assesslist{$symb} = join(':',('symb',$symb,0,0,
                      'tutornumber' =>                                             &Apache::lonnet::escape($_)));
                      'Number of Tutor Responses<br>tutornumber',          }
                      'totalpoints' =>          #
                      'Total Points Granted<br>totalpoints');          while (my ($key,$srcf) = each(%course_db)) {
         my $adduserstr='';              next if ($key !~ /^src_(\d+)\.(\d+)$/);
         if (($uname ne $ENV{'user.name'}) || ($udom ne $ENV{'user.domain'})){              my $mapid = $1;
             $adduserstr='&uname='.$uname.'&udom='.$udom;              my $resid = $2;
         }              my $id   = $mapid.'.'.$resid;
         my %allassess =  
             ('_feedback' =>'<a href="/adm/assesscalc?usymb=_feedback'.  
              $adduserstr.'">Feedback</a>',  
              '_evaluation' =>'<a href="/adm/assesscalc?usymb=_evaluation'.  
              $adduserstr.'">Evaluation</a>',  
              '_tutoring' =>'<a href="/adm/assesscalc?usymb=_tutoring'.  
              $adduserstr.'">Tutoring</a>',  
              '_discussion' =>'<a href="/adm/assesscalc?usymb=_discussion'.  
              $adduserstr.'">Discussion</a>'  
              );  
         while (($_,undef) = each(%bighash)) {  
             next if ($_!~/^src\_(\d+)\.(\d+)$/);  
             my $mapid=$1;  
             my $resid=$2;  
             my $id=$mapid.'.'.$resid;  
             my $srcf=$bighash{$_};  
             if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) {              if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) {
                 my $symb=                  my $symb=
                     &Apache::lonnet::declutter($bighash{'map_id_'.$mapid}).                      &Apache::lonnet::declutter($course_db{'map_id_'.$mapid}).
                         '___'.$resid.'___'.&Apache::lonnet::declutter($srcf);                          '___'.$resid.'___'.&Apache::lonnet::declutter($srcf);
                 $allassess{$symb}=                  $assesslist{$symb} ='symb:'.&Apache::lonnet::escape($symb).':'
                     '<a href="/adm/assesscalc?usymb='.$symb.$adduserstr.'">'.                      .$mapid.':'.$resid.':'.
                         $bighash{'title_'.$id}.'</a>';                          &Apache::lonnet::escape($course_db{'title_'.$id});
                 next if ($stype ne 'assesscalc');              }
                 foreach my $key (split(/\,/,          }
                                        &Apache::lonnet::metadata($srcf,'keys')          untie(%course_db);
                                        )) {          # Store away the data
           $self->{'rowlabel'} = \%assesslist;
           $rowlabel_cache{$identifier}=join('___;___',%{$self->{'rowlabel'}});
       }
       
   }
   
   sub get_assess_rowlabels {
       my $self = shift;
       #
       my %course_db;
       #
       my $stype = $self->{'type'};
       my $uname = $self->{'uname'};
       my $udom  = $self->{'udom'};
       my $usymb = $self->{'usymb'};
       #
       $self->rowlabels({});
       my $identifier =$self->{'coursefilename'}.'_'.$stype.'_'.$usymb;
       #
       if (exists($rowlabel_cache{$identifier})) {
           my %tmp = split('___;___',$rowlabel_cache{$identifier});
           $self->rowlabels(\%tmp);
       } else {
           # Get the data and store it in the cache
           # Tie hash
           tie(%course_db,'GDBM_File',$self->{'coursefilename'}.'.db',
               &GDBM_READER(),0640);
           if (! tied(%course_db)) {
               return 'Could not access course data';
           }
           #
           my %parameter_labels=
               ('timestamp' => 
                    'parameter:Timestamp of Last Transaction<br>timestamp',
                'subnumber' =>
                    'parameter:Number of Submissions<br>subnumber',
                'tutornumber' =>
                    'parameter:Number of Tutor Responses<br>tutornumber',
                'totalpoints' =>
                    'parameter:Total Points Granted<br>totalpoints');
           while (my ($key,$srcf) = each(%course_db)) {
               next if ($key !~ /^src_(\d+)\.(\d+)$/);
               my $mapid = $1;
               my $resid = $2;
               my $id   = $mapid.'.'.$resid;
               if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) {
                   # Loop through the metadata for this key
                   my @Metadata = split(/,/,
                                        &Apache::lonnet::metadata($srcf,'keys'));
                   foreach my $key (@Metadata) {
                     next if ($key !~ /^(stores|parameter)_/);                      next if ($key !~ /^(stores|parameter)_/);
                     my $display=                      my $display=
                         &Apache::lonnet::metadata($srcf,$key.'.display');                          &Apache::lonnet::metadata($srcf,$key.'.display');
Line 1850  sub updatestudentassesssheet { Line 3130  sub updatestudentassesssheet {
                             &Apache::lonnet::metadata($srcf,$key.'.name');                              &Apache::lonnet::metadata($srcf,$key.'.name');
                     }                      }
                     $display.='<br>'.$key;                      $display.='<br>'.$key;
                     $allkeys{$key}=$display;                      $parameter_labels{$key}='parameter:'.$display;
                 } # end of foreach                  } # end of foreach
             }              }
         } # end of foreach (keys(%bighash))          }
         untie(%bighash);          untie(%course_db);
         #          # Store away the results
         # %allkeys has a list of storage and parameter displays by unikey          $self->rowlabels(\%parameter_labels);
         # %allassess has a list of all resource displays by symb          $rowlabel_cache{$identifier}=join('___;___',%parameter_labels);
         #  
         if ($stype eq 'assesscalc') {  
             %current=%allkeys;  
         } elsif ($stype eq 'studentcalc') {  
             %current=%allassess;  
         }  
         $updatedata{$ENV{'request.course.fn'}.'_'.$stype.'_'.$uname.'_'.$udom}=  
             join('___;___',%current);  
         # Get current from cache  
     }      }
     # Find discrepancies between the course row table and this  }
     #  
     my %f=&getformulas($safeeval);  sub updatestudentassesssheet {
       my $self = shift;
       if ($self->{'type'} eq 'studentcalc') {
           $self->get_student_rowlabels();
       } else {
           $self->get_assess_rowlabels();
       }
       # Determine if any of the information has changed
       my %f=$self->formulas();
     my $changed=0;      my $changed=0;
           $self->{'maxrow'} = 0;
     my $maxrow=0;  
     my %existing=();      my %existing=();
     # Now obsolete rows      # Now obsolete rows
     foreach (keys(%f)) {      foreach my $rownum ($self->rows()) {
         next if ($_!~/^A(\d+)/);          my $cell = 'A'.$rownum;
         $maxrow=($1>$maxrow)?$1:$maxrow;          my $formula = $f{$cell};
         my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{$_});          $self->{'maxrow'} = $rownum if ($rownum > $self->{'maxrow'});
           my ($usy,$ufn)=split(/__&&&\__/,$formula);
         $existing{$usy}=1;          $existing{$usy}=1;
         unless ((defined($current{$usy})) || (!$1) ||          if ( ! exists($self->{'rowlabel'}->{$usy})  ||
                 ($f{$_}=~/^(\~\~\~|\-\-\-)/)){               ! defined($self->{'rowlabel'}->{$usy}) ||
             $f{$_}='!!! Obsolete';               ($formula =~ /^(~~~|---)/) ||
                ($formula =~ /^\s*$/)) {
               $f{$cell}='!!! Obsolete';
             $changed=1;              $changed=1;
         } elsif ($ufn) {  
             $current{$usy}  
             =~s/assesscalc\?usymb\=/assesscalc\?ufn\=$ufn\&usymb\=/;  
         }          }
     }      }
     # New and unknown keys      # New and unknown keys
     foreach (keys(%current)) {      my %keys_hates_me = $self->rowlabels();
       foreach (keys(%keys_hates_me)) {
         unless ($existing{$_}) {          unless ($existing{$_}) {
             $changed=1;              $changed=1;
             $maxrow++;              $self->{'maxrow'}++;
             $f{'A'.$maxrow}=$_;              $f{'A'.$self->{'maxrow'}}=$_;
         }          }
     }      }
     if ($changed) { &setformulas($safeeval,%f); }      $self->formulas(\%f) if ($changed);
     &setmaxrow($safeeval,$maxrow);  #    $self->dump_formulas_to_log();    
     &setrowlabels($safeeval,%current);  
     #  
     undef %current;  
     undef %existing;  
 }  }
   
 # ------------------------------------------------ Load data for one assessment  # ------------------------------------------------ Load data for one assessment
   sub loadstudent{
 sub loadstudent {      my $self = shift;
     my ($safeeval,$sheetdata)=@_;      my ($r,$c)=@_;
     my %c=();      my %constants = ();
     my %f=&getformulas($safeeval);      my %formulas  = $self->formulas();
     $cachedassess=$sheetdata->{'uname'}.':'.$sheetdata->{'udom'};      $cachedassess = $self->{'uname'}.':'.$self->{'udom'};
     # Get ALL the student preformance data      # Get ALL the student preformance data
     my @tmp = &Apache::lonnet::dump($sheetdata->{'cid'},      my @tmp = &Apache::loncoursedata::get_current_state($self->{'uname'},
                                     $sheetdata->{'udom'},                                                          $self->{'udom'},
                                     $sheetdata->{'uname'},                                                          undef,
                                     undef);                                                          $self->{'cid'});
     if ($tmp[0] !~ /^error:/) {      if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
         %cachedstores = @tmp;          %cachedstores = @tmp;
     }      }
     undef @tmp;      undef @tmp;
     #       # debugging code
       # $self->dump_hash_to_log(\%cachedstores);
       #
     my @assessdata=();      my @assessdata=();
     foreach (keys(%f)) {      foreach my $row ($self->rows()) {
  next if ($_!~/^A(\d+)/);          my $cell = 'A'.$row;
         my $row=$1;          my $value = $formulas{$cell};
         next if (($f{$_}=~/^[\!\~\-]/) || ($row==0));          if(defined($c) && ($c->aborted())) {
         my ($usy,$ufn)=split(/__&&&\__/,$f{$_});              last;
         @assessdata=&exportsheet($sheetdata->{'uname'},          }
                                  $sheetdata->{'udom'},          next if ($value =~ /^[!~-]/);
                                  'assesscalc',$usy,$ufn);          my ($usy,$ufn)=split(/__&&&\__/,$value);
           @assessdata=$self->exportsheet($self->{'uname'},
                                           $self->{'udom'},
                                           'assesscalc',$usy,$ufn,$r);
         my $index=0;          my $index=0;
         foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',          foreach my $col ('A','B','C','D','E','F','G','H','I','J','K','L','M',
                  'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') {                           'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') {
             if ($assessdata[$index]) {              if (defined($assessdata[$index])) {
                 my $col=$_;  
                 if ($assessdata[$index]=~/\D/) {                  if ($assessdata[$index]=~/\D/) {
                     $c{$col.$row}="'".$assessdata[$index]."'";                      $constants{$col.$row}="'".$assessdata[$index]."'";
                 } else {                  } else {
                     $c{$col.$row}=$assessdata[$index];                      $constants{$col.$row}=$assessdata[$index];
                 }  
                 unless ($col eq 'A') {   
                     $f{$col.$row}='import';  
                 }                  }
                   $formulas{$col.$row}='import' if ($col ne 'A');
             }              }
             $index++;              $index++;
         }          }
     }      }
     $cachedassess='';      $cachedassess='';
     undef %cachedstores;      undef %cachedstores;
     &setformulas($safeeval,%f);      $self->formulas(\%formulas);
     &setconstants($safeeval,%c);      $self->constants(\%constants);
 }  }
   
 # --------------------------------------------------- Load data for one student  # --------------------------------------------------- Load Course Sheet
   #
 sub loadcourse {  sub loadcourse {
     my ($safeeval,$sheetdata,$r)=@_;      my $self = shift;
     my %c=();      my ($r,$c)=@_;
     my %f=&getformulas($safeeval);      #
       my %constants=();
       my %formulas=$self->formulas();
       #
     my $total=0;      my $total=0;
     foreach (keys(%f)) {      foreach ($self->rows()) {
  if ($_=~/^A(\d+)/) {          $total++ if ($formulas{'A'.$_} !~ /^[!~-]/);
     unless ($f{$_}=~/^[\!\~\-]/) { $total++; }      }
         }  
     }      my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,
     my $now=0;        'Spreadsheet Status','Spreadsheet Calculation Progress', $total);
     my $since=time;      &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
     $r->print(<<ENDPOP);    'Processing Course Assessment Data');
 <script>  
     popwin=open('','popwin','width=400,height=100');      # It would be nice to load in the classlist and assessment info at this 
     popwin.document.writeln('<html><body bgcolor="#FFFFFF">'+      # point, before attacking the student spreadsheets.
       '<h3>Spreadsheet Calculation Progress</h3>'+      foreach my $row ($self->rows()) {
       '<form name=popremain>'+          if(defined($c) && ($c->aborted())) {
       '<input type=text size=35 name=remaining value=Starting></form>'+              last;
       '</body></html>');          }
     popwin.document.close();          my $cell = 'A'.$row;
 </script>          next if ($formulas{$cell}=~/^[\!\~\-]/);
 ENDPOP          my ($sname,$sdom) = split(':',$formulas{$cell});
     $r->rflush();          my $started = time;
     foreach (keys(%f)) {          my @studentdata=$self->exportsheet($sname,$sdom,'studentcalc',
  next if ($_!~/^A(\d+)/);                                       undef,undef,$r);
         my $row=$1;  
         next if (($f{$_}=~/^[\!\~\-]/)  || ($row==0));  
         my @studentdata=&exportsheet(split(/\:/,$f{$_}),  
                                      'studentcalc');  
         undef %userrdatas;          undef %userrdatas;
         $now++;   &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
         $r->print('<script>popwin.document.popremain.remaining.value="'.   'last student');
                   $now.'/'.$total.': '.int((time-$since)/$now*($total-$now)).  
                   ' secs remaining";</script>');  
         $r->rflush();   
         #  
         my $index=0;          my $index=0;
         foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',          foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',
                  'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') {                   'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') {
             if ($studentdata[$index]) {              if (defined($studentdata[$index])) {
                 my $col=$_;                  my $col=$_;
                 if ($studentdata[$index]=~/\D/) {                  if ($studentdata[$index]=~/\D/) {
                     $c{$col.$row}="'".$studentdata[$index]."'";                      $constants{$col.$row}="'".$studentdata[$index]."'";
                 } else {                  } else {
                     $c{$col.$row}=$studentdata[$index];                      $constants{$col.$row}=$studentdata[$index];
                 }                  }
                 unless ($col eq 'A') {                   unless ($col eq 'A') { 
                     $f{$col.$row}='import';                      $formulas{$col.$row}='import';
                 }                  }
                 $index++;              } 
             }              $index++;
         }          }
     }      }
     &setformulas($safeeval,%f);      $self->formulas(\%formulas);
     &setconstants($safeeval,%c);      $self->constants(\%constants);
     $r->print('<script>popwin.close()</script>');      &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
     $r->rflush();   
 }  }
   
 # ------------------------------------------------ Load data for one assessment  # ------------------------------------------------ Load data for one assessment
   #
 sub loadassessment {  sub loadassessment {
     my ($safeeval,$sheetdata)=@_;      my $self = shift;
       my ($r,$c)=@_;
   
     my $uhome = $sheetdata->{'uhome'};      my $uhome = $self->{'uhome'};
     my $uname = $sheetdata->{'uname'};      my $uname = $self->{'uname'};
     my $udom  = $sheetdata->{'udom'};      my $udom  = $self->{'udom'};
     my $symb  = $sheetdata->{'usymb'};      my $symb  = $self->{'usymb'};
     my $cid   = $sheetdata->{'cid'};      my $cid   = $self->{'cid'};
     my $cnum  = $sheetdata->{'cnum'};      my $cnum  = $self->{'cnum'};
     my $cdom  = $sheetdata->{'cdom'};      my $cdom  = $self->{'cdom'};
     my $chome = $sheetdata->{'chome'};      my $chome = $self->{'chome'};
       my $csec  = $self->{'csec'};
   
     my $namespace;      my $namespace;
     unless ($namespace=$cid) { return ''; }      unless ($namespace=$cid) { return ''; }
Line 2039  sub loadassessment { Line 3312  sub loadassessment {
         #          #
         # get data out of the dumped stores          # get data out of the dumped stores
         #           # 
         my $version=$cachedstores{'version:'.$symb};          if (exists($cachedstores{$symb})) {
         my $scope;              %returnhash = %{$cachedstores{$symb}};
         for ($scope=1;$scope<=$version;$scope++) {          } else {
             foreach (split(/\:/,$cachedstores{$scope.':keys:'.$symb})) {              %returnhash = ();
                 $returnhash{$_}=$cachedstores{$scope.':'.$symb.':'.$_};  
             }   
         }          }
     } else {      } else {
         #          #
         # restore individual          # restore individual
         #          #
         my $answer=&Apache::lonnet::reply(          %returnhash = &Apache::lonnet::restore($symb,$namespace,$udom,$uname);
                                           "restore:$udom:$uname:".  
                                           &Apache::lonnet::escape($namespace).":".  
                                           &Apache::lonnet::escape($symb),$uhome);  
         foreach (split(/\&/,$answer)) {  
             my ($name,$value)=split(/\=/,$_);  
             $returnhash{&Apache::lonnet::unescape($name)}=  
                 &Apache::lonnet::unescape($value);  
         }  
         my $version;  
         for ($version=1;$version<=$returnhash{'version'};$version++) {  
             foreach (split(/\:/,$returnhash{$version.':keys'})) {  
                 $returnhash{$_}=$returnhash{$version.':'.$_};  
             }   
         }  
     }      }
       #
     # returnhash now has all stores for this resource      # returnhash now has all stores for this resource
     # convert all "_" to "." to be able to use libraries, multiparts, etc      # convert all "_" to "." to be able to use libraries, multiparts, etc
       #
       # This is dumb.  It is also necessary :(
     my @oldkeys=keys %returnhash;      my @oldkeys=keys %returnhash;
       #
     foreach (@oldkeys) {      foreach my $name (@oldkeys) {
         my $name=$_;          my $value=$returnhash{$name};
         my $value=$returnhash{$_};          delete $returnhash{$name};
         delete $returnhash{$_};  
         $name=~s/\_/\./g;          $name=~s/\_/\./g;
         $returnhash{$name}=$value;          $returnhash{$name}=$value;
     }      }
Line 2082  sub loadassessment { Line 3341  sub loadassessment {
     undef %useropt;      undef %useropt;
   
     my $userprefix=$uname.'_'.$udom.'_';      my $userprefix=$uname.'_'.$udom.'_';
       
     unless ($uhome eq 'no_host') {       unless ($uhome eq 'no_host') { 
         # Get coursedata          # Get coursedata
         unless ((time-$courserdatas{$cid.'.last_cache'})<240) {          unless ((time-$courserdatas{$cid.'.last_cache'})<240) {
             my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.              my %Tmp = &Apache::lonnet::dump('resourcedata',$cdom,$cnum);
                                              ':resourcedata',$chome);              $courserdatas{$cid}=\%Tmp;
             if ($reply!~/^error\:/) {              $courserdatas{$cid.'.last_cache'}=time;
                 $courserdatas{$cid}=$reply;  
                 $courserdatas{$cid.'.last_cache'}=time;  
             }  
         }          }
         foreach (split(/\&/,$courserdatas{$cid})) {          while (my ($name,$value) = each(%{$courserdatas{$cid}})) {
             my ($name,$value)=split(/\=/,$_);              $courseopt{$userprefix.$name}=$value;
             $courseopt{$userprefix.&Apache::lonnet::unescape($name)}=  
                 &Apache::lonnet::unescape($value);    
         }          }
         # Get userdata (if present)          # Get userdata (if present)
         unless          unless ((time-$userrdatas{$uname.'@'.$udom.'.last_cache'})<240) {
             ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) {              my %Tmp = &Apache::lonnet::dump('resourcedata',$udom,$uname);
                 my $reply=              $userrdatas{$cid} = \%Tmp;
                     &Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome);              # Most of the time the user does not have a 'resourcedata.db' 
                 if ($reply!~/^error\:/) {              # file.  We need to cache that we got nothing instead of bothering
                     $userrdatas{$uname.'___'.$udom}=$reply;              # with requesting it every time.
                     $userrdatas{$uname.'___'.$udom.'.last_cache'}=time;              $userrdatas{$uname.'@'.$udom.'.last_cache'}=time;
                 }          }
             }          while (my ($name,$value) = each(%{$userrdatas{$cid}})) {
         foreach (split(/\&/,$userrdatas{$uname.'___'.$udom})) {              $useropt{$userprefix.$name}=$value;
             my ($name,$value)=split(/\=/,$_);  
             $useropt{$userprefix.&Apache::lonnet::unescape($name)}=  
                 &Apache::lonnet::unescape($value);  
         }          }
     }      }
     # now courseopt, useropt initialized for this user and course      # now courseopt, useropt initialized for this user and course
Line 2129  sub loadassessment { Line 3380  sub loadassessment {
     #      #
     my %c=();      my %c=();
     if (tie(%parmhash,'GDBM_File',      if (tie(%parmhash,'GDBM_File',
             &getcfn($safeeval).'_parms.db',&GDBM_READER(),0640)) {              $self->{'coursefilename'}.'_parms.db',&GDBM_READER(),0640)) {
         my %f=&getformulas($safeeval);          my %f=$self->formulas();
         foreach (keys(%f))  {          foreach my $row ($self->rows())  {
             next if ($_!~/^A/);              my $cell = 'A'.$row;
             next if  ($f{$_}=~/^[\!\~\-]/);              my $formula = $self->formula($cell);
             if ($f{$_}=~/^parameter/) {              next if ($formula =~/^[\!\~\-]/);
                 if ($thisassess{$f{$_}}) {              if ($formula =~ /^parameter/) {
                     my $val=&parmval($f{$_},$safeeval,$sheetdata);                  if (defined($thisassess{$formula})) {
                     $c{$_}=$val;                      my $val   = &parmval($formula,$symb,$uname,$udom,$csec);
                     $c{$f{$_}}=$val;                      $c{$cell}    = $val;
                       $c{$formula} = $val;
                 }                  }
             } else {              } else {
                 my $key=$f{$_};                  my $ckey=$formula;
                 my $ckey=$key;                  $formula=~s/^stores\_/resource\./;
                 $key=~s/^stores\_/resource\./;                  $formula=~s/\_/\./g;
                 $key=~s/\_/\./g;                  $c{$cell}=$returnhash{$formula};
                 $c{$_}=$returnhash{$key};                  $c{$ckey}=$returnhash{$formula};
                 $c{$ckey}=$returnhash{$key};  
             }              }
         }          }
         untie(%parmhash);          untie(%parmhash);
     }      }
     &setconstants($safeeval,%c);      $self->constants(\%c);
 }  
   
 # --------------------------------------------------------- Various form fields  
   
 sub textfield {  
     my ($title,$name,$value)=@_;  
     return "\n<p><b>$title:</b><br>".  
         '<input type=text name="'.$name.'" size=80 value="'.$value.'">';  
 }  }
   
 sub hiddenfield {  
     my ($name,$value)=@_;  
     return "\n".'<input type=hidden name="'.$name.'" value="'.$value.'">';  
 }  
   
 sub selectbox {  
     my ($title,$name,$value,%options)=@_;  
     my $selout="\n<p><b>$title:</b><br>".'<select name="'.$name.'">';  
     foreach (sort keys(%options)) {  
         $selout.='<option value="'.$_.'"';  
         if ($_ eq $value) { $selout.=' selected'; }  
         $selout.='>'.$options{$_}.'</option>';  
     }  
     return $selout.'</select>';  
 }  
   
 # =============================================== Update information in a sheet  # =============================================== Update information in a sheet
 #  #
 # Add new users or assessments, etc.  # Add new users or assessments, etc.
 #  #
   
 sub updatesheet {  sub updatesheet {
     my ($safeeval,$sheetdata)=@_;      my $self = shift;
     my $stype=$sheetdata->{'sheettype'};      if ($self->{'type'} eq 'classcalc') {
     if ($stype eq 'classcalc') {          return $self->updateclasssheet();
  return &updateclasssheet($safeeval,$sheetdata);  
     } else {      } else {
         return &updatestudentassesssheet($safeeval,$sheetdata);          return $self->updatestudentassesssheet();
     }      }
 }  }
   
Line 2197  sub updatesheet { Line 3423  sub updatesheet {
 #  #
 # Import the data for rows  # Import the data for rows
 #  #
   
 sub loadrows {  sub loadrows {
     my ($safeeval,$sheetdata,$r)=@_;      my $self = shift;
     my $stype=$sheetdata->{'sheettype'};      my ($r)=@_;
     if ($stype eq 'classcalc') {      my $c = $r->connection;
  &loadcourse($safeeval,$sheetdata,$r);      if ($self->{'type'} eq 'classcalc') {
     } elsif ($stype eq 'studentcalc') {          $self->loadcourse($r,$c);
         &loadstudent($safeeval,$sheetdata);      } elsif ($self->{'type'} eq 'studentcalc') {
     } else {          $self->loadstudent($r,$c);
         &loadassessment($safeeval,$sheetdata);  
     }  
 }  
   
 # ======================================================= Forced recalculation?  
   
 sub checkthis {  
     my ($keyname,$time)=@_;  
     return ($time<$expiredates{$keyname});  
 }  
   
 sub forcedrecalc {  
     my ($uname,$udom,$stype,$usymb)=@_;  
     my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;  
     my $time=$oldsheets{$key.'.time'};  
     if ($ENV{'form.forcerecalc'}) { return 1; }  
     unless ($time) { return 1; }  
     if ($stype eq 'assesscalc') {  
         my $map=(split(/\_\_\_/,$usymb))[0];  
         if (&checkthis('::assesscalc:',$time) ||  
             &checkthis('::assesscalc:'.$map,$time) ||  
             &checkthis('::assesscalc:'.$usymb,$time) ||  
             &checkthis($uname.':'.$udom.':assesscalc:',$time) ||  
             &checkthis($uname.':'.$udom.':assesscalc:'.$map,$time) ||  
             &checkthis($uname.':'.$udom.':assesscalc:'.$usymb,$time)) {  
             return 1;  
         }   
     } else {      } else {
         if (&checkthis('::studentcalc:',$time) ||           $self->loadassessment($r,$c);
             &checkthis($uname.':'.$udom.':studentcalc:',$time)) {  
     return 1;  
         }  
     }      }
     return 0;   
 }  }
   
 # ============================================================== Export handler  # ============================================================== Export handler
   # exportsheet
   # returns the export row for a spreadsheet.
 #  #
 # Non-interactive call from with program  
 #  
   
 sub exportsheet {  sub exportsheet {
     my ($uname,$udom,$stype,$usymb,$fn)=@_;      my $self = shift;
       my ($uname,$udom,$stype,$usymb,$fn,$r)=@_;
       my $flag = 0;
       $uname = $uname || $self->{'uname'};
       $udom  = $udom  || $self->{'udom'};
       $stype = $stype || $self->{'type'};
     my @exportarr=();      my @exportarr=();
     if (($usymb=~/^\_(\w+)/) && (!$fn)) {      # This handles the assessment sheets for '_feedback', etc
       if (defined($usymb) && ($usymb=~/^\_(\w+)/) && 
           (!defined($fn) || $fn eq '')) {
         $fn='default_'.$1;          $fn='default_'.$1;
     }      }
     #      #
Line 2258  sub exportsheet { Line 3458  sub exportsheet {
     #      #
     my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;      my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;
     my $found='';      my $found='';
     if ($oldsheets{$key}) {      if ($Apache::lonspreadsheet::oldsheets{$key}) {
         foreach (split(/\_\_\_\&\_\_\_/,$oldsheets{$key})) {          foreach (split(/___&\___/,$Apache::lonspreadsheet::oldsheets{$key})) {
             my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);              my ($name,$value)=split(/___=___/,$_);
             if ($name eq $fn) {              if ($name eq $fn) {
                 $found=$value;                  $found=$value;
             }              }
         }          }
     }      }
     unless ($found) {      unless ($found) {
         &cachedssheets($uname,$udom,&Apache::lonnet::homeserver($uname,$udom));          &cachedssheets($self,$uname,$udom);
         if ($oldsheets{$key}) {          if ($Apache::lonspreadsheet::oldsheets{$key}) {
             foreach (split(/\_\_\_\&\_\_\_/,$oldsheets{$key})) {              foreach (split(/___&\___/,$Apache::lonspreadsheet::oldsheets{$key})) {
                 my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);                  my ($name,$value)=split(/___=___/,$_);
                 if ($name eq $fn) {                  if ($name eq $fn) {
                     $found=$value;                      $found=$value;
                 }                  }
Line 2289  sub exportsheet { Line 3489  sub exportsheet {
         #          #
         # Return what was cached          # Return what was cached
         #          #
         @exportarr=split(/\_\_\_\;\_\_\_/,$found);          @exportarr=split(/___;___/,$found);
     } else {          return @exportarr;
         #  
         # Not cached  
         #          
         my ($thissheet,$sheetdata)=&makenewsheet($uname,$udom,$stype,$usymb);  
         &readsheet($thissheet,$sheetdata,$fn);  
         &updatesheet($thissheet,$sheetdata);  
         &loadrows($thissheet,$sheetdata);  
         &calcsheet($thissheet,$sheetdata);   
         @exportarr=&exportdata($thissheet,$sheetdata);  
         #  
         # Store now  
         #  
         my $cid=$ENV{'request.course.id'};   
         my $current='';  
         if ($stype eq 'studentcalc') {  
             $current=&Apache::lonnet::reply('get:'.  
                                             $ENV{'course.'.$cid.'.domain'}.':'.  
                                             $ENV{'course.'.$cid.'.num'}.  
                                             ':nohist_calculatedsheets:'.  
                                             &Apache::lonnet::escape($key),  
                                             $ENV{'course.'.$cid.'.home'});  
         } else {  
             $current=&Apache::lonnet::reply('get:'.$sheetdata->{'udom'}.':'.  
                                             $sheetdata->{'uname'}.  
                                             ':nohist_calculatedsheets_'.  
                                             $ENV{'request.course.id'}.':'.  
                                             &Apache::lonnet::escape($key),  
                                             $sheetdata->{'uhome'});  
         }  
         my %currentlystored=();  
         unless ($current=~/^error\:/) {  
             foreach (split(/___&\___/,&Apache::lonnet::unescape($current))) {  
                 my ($name,$value)=split(/___=___/,$_);  
                 $currentlystored{$name}=$value;  
             }  
         }  
         $currentlystored{$fn}=join('___;___',@exportarr);  
         #  
         my $newstore='';  
         foreach (keys(%currentlystored)) {  
             if ($newstore) { $newstore.='___&___'; }  
             $newstore.=$_.'___=___'.$currentlystored{$_};  
         }  
         my $now=time;  
         if ($stype eq 'studentcalc') {  
             &Apache::lonnet::reply('put:'.  
                                    $ENV{'course.'.$cid.'.domain'}.':'.  
                                    $ENV{'course.'.$cid.'.num'}.  
                                    ':nohist_calculatedsheets:'.  
                                    &Apache::lonnet::escape($key).'='.  
                                    &Apache::lonnet::escape($newstore).'&'.  
                                    &Apache::lonnet::escape($key).'.time='.$now,  
                                    $ENV{'course.'.$cid.'.home'});  
         } else {  
             &Apache::lonnet::reply('put:'.  
                                    $sheetdata->{'udom'}.':'.  
                                    $sheetdata->{'uname'}.  
                                    ':nohist_calculatedsheets_'.  
                                    $ENV{'request.course.id'}.':'.  
                                    &Apache::lonnet::escape($key).'='.  
                                    &Apache::lonnet::escape($newstore).'&'.  
                                    &Apache::lonnet::escape($key).'.time='.$now,  
                                    $sheetdata->{'uhome'});  
         }  
     }  
     return @exportarr;  
 }  
   
 # ============================================================ Expiration Dates  
 #  
 # Load previously cached student spreadsheets for this course  
 #  
 sub expirationdates {  
     undef %expiredates;  
     my $cid=$ENV{'request.course.id'};  
     my $reply=&Apache::lonnet::reply('dump:'.  
      $ENV{'course.'.$cid.'.domain'}.':'.  
                                      $ENV{'course.'.$cid.'.num'}.  
      ':nohist_expirationdates',  
                                      $ENV{'course.'.$cid.'.home'});  
     unless ($reply=~/^error\:/) {  
  foreach (split(/\&/,$reply)) {  
             my ($name,$value)=split(/\=/,$_);  
             $expiredates{&Apache::lonnet::unescape($name)}  
                         =&Apache::lonnet::unescape($value);  
         }  
     }  
 }  
   
 # ===================================================== Calculated sheets cache  
 #  
 # Load previously cached student spreadsheets for this course  
 #  
   
 sub cachedcsheets {  
     my $cid=$ENV{'request.course.id'};  
     my $reply=&Apache::lonnet::reply('dump:'.  
      $ENV{'course.'.$cid.'.domain'}.':'.  
                                      $ENV{'course.'.$cid.'.num'}.  
      ':nohist_calculatedsheets',  
                                      $ENV{'course.'.$cid.'.home'});  
     unless ($reply=~/^error\:/) {  
  foreach ( split(/\&/,$reply)) {  
             my ($name,$value)=split(/\=/,$_);  
             $oldsheets{&Apache::lonnet::unescape($name)}  
                       =&Apache::lonnet::unescape($value);  
         }  
     }  
 }  
   
 # ===================================================== Calculated sheets cache  
 #  
 # Load previously cached assessment spreadsheets for this student  
 #  
   
 sub cachedssheets {  
   my ($sname,$sdom,$shome)=@_;  
   unless (($loadedcaches{$sname.'_'.$sdom}) || ($shome eq 'no_host')) {  
     my $cid=$ENV{'request.course.id'};  
     my $reply=&Apache::lonnet::reply('dump:'.$sdom.':'.$sname.  
              ':nohist_calculatedsheets_'.  
                                       $ENV{'request.course.id'},  
                                      $shome);  
     unless ($reply=~/^error\:/) {  
  foreach ( split(/\&/,$reply)) {  
             my ($name,$value)=split(/\=/,$_);  
             $oldsheets{&Apache::lonnet::unescape($name)}  
                       =&Apache::lonnet::unescape($value);  
         }  
     }  
     $loadedcaches{$sname.'_'.$sdom}=1;  
   }  
 }  
   
 # ===================================================== Calculated sheets cache  
 #  
 # Load previously cached assessment spreadsheets for this student  
 #  
   
 # ================================================================ Main handler  
 #  
 # Interactive call to screen  
 #  
 #  
   
   
 sub handler {  
     my $r=shift;  
     if ($r->header_only) {  
         $r->content_type('text/html');  
         $r->send_http_header;  
         return OK;  
     }  
     # Global directory configs  
     $includedir = $r->dir_config('lonIncludes');  
     $tmpdir = $r->dir_config('lonDaemons').'/tmp/';  
     # Needs to be in a course  
     if (! $ENV{'request.course.fn'}) {   
         # Not in a course, or not allowed to modify parms  
         $ENV{'user.error.msg'}=  
             $r->uri.":opa:0:0:Cannot modify spreadsheet";  
         return HTTP_NOT_ACCEPTABLE;   
     }  
     # Get query string for limited number of parameters  
     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},  
                                             ['uname','udom','usymb','ufn']);  
     if (($ENV{'form.usymb'}=~/^\_(\w+)/) && (!$ENV{'form.ufn'})) {  
         $ENV{'form.ufn'}='default_'.$1;  
     }  
     # Interactive loading of specific sheet?  
     if (($ENV{'form.load'}) && ($ENV{'form.loadthissheet'} ne 'Default')) {  
         $ENV{'form.ufn'}=$ENV{'form.loadthissheet'};  
     }      }
     #      #
     # Determine the user name and domain for the sheet.      # Not cached
     my $aname;  
     my $adom;  
     unless ($ENV{'form.uname'}) {  
         $aname=$ENV{'user.name'};  
         $adom=$ENV{'user.domain'};  
     } else {  
         $aname=$ENV{'form.uname'};  
         $adom=$ENV{'form.udom'};  
     }  
     #      #
     # Open page      my $newsheet = Apache::lonspreadsheet::Spreadsheet->new($uname,$udom,
     $r->content_type('text/html');                                                           $stype,$usymb);
     $r->header_out('Cache-control','no-cache');      $newsheet->readsheet($fn);
     $r->header_out('Pragma','no-cache');      $newsheet->updatesheet();
     $r->send_http_header;      $newsheet->loadrows($r);
     # Screen output      $newsheet->calcsheet(); 
     $r->print('<html><head><title>LON-CAPA Spreadsheet</title>');      @exportarr=$newsheet->exportdata();
     $r->print(<<ENDSCRIPT);      ##
 <script language="JavaScript">      ## Store now
       ##
     function celledit(cn,cf) {  
         var cnf=prompt(cn,cf);  
         if (cnf!=null) {  
             document.sheet.unewfield.value=cn;  
             document.sheet.unewformula.value=cnf;  
             document.sheet.submit();  
         }  
     }  
   
     function changesheet(cn) {  
  document.sheet.unewfield.value=cn;  
         document.sheet.unewformula.value='changesheet';  
         document.sheet.submit();  
     }  
   
     function insertrow(cn) {  
  document.sheet.unewfield.value='insertrow';  
         document.sheet.unewformula.value=cn;  
         document.sheet.submit();  
     }  
   
 </script>  
 ENDSCRIPT  
     $r->print('</head>'.&Apache::loncommon::bodytag('Grades Spreadsheet').  
               '<form action="'.$r->uri.'" name=sheet method=post>');  
     $r->print(&hiddenfield('uname',$ENV{'form.uname'}).  
               &hiddenfield('udom',$ENV{'form.udom'}).  
               &hiddenfield('usymb',$ENV{'form.usymb'}).  
               &hiddenfield('unewfield','').  
               &hiddenfield('unewformula',''));  
     $r->rflush();  
     #      #
     # Full recalc?      # load in the old value
     if ($ENV{'form.forcerecalc'}) {  
         $r->print('<h4>Completely Recalculating Sheet ...</h4>');  
         undef %spreadsheets;  
         undef %courserdatas;  
         undef %userrdatas;  
         undef %defaultsheets;  
         undef %updatedata;  
     }  
     # Read new sheet or modified worksheet  
     $r->uri=~/\/(\w+)$/;  
     my ($asheet,$asheetdata)=&makenewsheet($aname,$adom,$1,$ENV{'form.usymb'});  
     #      #
     # If a new formula had been entered, go from work copy      my %currentlystored=();
     if ($ENV{'form.unewfield'}) {      if ($stype eq 'studentcalc') {
         $r->print('<h2>Modified Workcopy</h2>');          my @tmp = &Apache::lonnet::get('nohist_calculatedsheets',
         $ENV{'form.unewformula'}=~s/\'/\"/g;                                         [$key],
         $r->print('<p>New formula: '.$ENV{'form.unewfield'}.'='.                                         $self->{'cdom'},$self->{'cnum'});
                   $ENV{'form.unewformula'}.'<p>');          if ($tmp[0]!~/^error/) {
         &setfilename($asheet,$ENV{'form.ufn'});              # We only got one key, so we will access it directly.
         &tmpread($asheet,$ENV{'form.unewfield'},$ENV{'form.unewformula'});              foreach (split('___&___',$tmp[1])) {
     } elsif ($ENV{'form.saveas'}) {                  my ($key,$value) = split('___=___',$_);
         &setfilename($asheet,$ENV{'form.ufn'});                  $key = '' if (! defined($key));
         &tmpread($asheet);                  $currentlystored{$key} = $value;
     } else {              }
         &readsheet($asheet,$asheetdata,$ENV{'form.ufn'});  
     }  
     # Print out user information  
     unless ($asheetdata->{'sheettype'} eq 'classcalc') {  
         $r->print('<p><b>User:</b> '.$asheetdata->{'uname'}.  
                   '<br><b>Domain:</b> '.$asheetdata->{'udom'});  
         if (&getcsec($asheet) eq '-1') {  
             $r->print('<h3><font color=red>'.  
                       'Not a student in this course</font></h3>');  
         } else {  
             $r->print('<br><b>Section/Group:</b> '.$asheetdata->{'csec'});  
         }  
         if ($ENV{'form.usymb'}) {  
             $r->print('<br><b>Assessment:</b> <tt>'.  
                       $ENV{'form.usymb'}.'</tt>');  
         }  
     }  
     #  
     # Check user permissions  
     if (($asheetdata->{'sheettype'} eq 'classcalc'       ) ||   
         ($asheetdata->{'uname'}     ne $ENV{'user.name'} ) ||  
         ($asheetdata->{'udom'}      ne $ENV{'user.domain'})) {  
         unless (&Apache::lonnet::allowed('vgr',$asheetdata->{'cid'})) {  
             $r->print('<h1>Access Permission Denied</h1>'.  
                       '</form></body></html>');  
             return OK;  
         }          }
     }      } else {
     # Additional options          my @tmp = &Apache::lonnet::get('nohist_calculatedsheets_'.
     $r->print('<br />'.                                         $self->{'cid'},[$key],
               '<input type="submit" name="forcerecalc" '.                                         $self->{'udom'},$self->{'uname'});
               'value="Completely Recalculate Sheet"><p>');          if ($tmp[0]!~/^error/) {
     if ($asheetdata->{'sheettype'} eq 'assesscalc') {              # We only got one key, so we will access it directly.
         $r->print('<p><font size=+2>'.              foreach (split('___&___',$tmp[1])) {
                   '<a href="/adm/studentcalc?'.                  my ($key,$value) = split('___=___',$_);
                   'uname='.$asheetdata->{'uname'}.                  $key = '' if (! defined($key));
                   '&udom='.$asheetdata->{'udom'}.'">'.                  $currentlystored{$key} = $value;
                   'Level up: Student Sheet</a></font><p>');  
     }  
     if (($asheetdata->{'sheettype'} eq 'studentcalc') &&   
         (&Apache::lonnet::allowed('vgr',$asheetdata->{'cid'}))) {  
         $r->print ('<p><font size=+2><a href="/adm/classcalc">'.  
                    'Level up: Course Sheet</a></font><p>');  
     }  
     # Save dialog  
     if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {  
         my $fname=$ENV{'form.ufn'};  
         $fname=~s/\_[^\_]+$//;  
         if ($fname eq 'default') { $fname='course_default'; }  
         $r->print('<input type=submit name=saveas value="Save as ...">'.  
                   '<input type=text size=20 name=newfn value="'.$fname.'">'.  
                   'make default: <input type=checkbox name="makedefufn"><p>');  
     }  
     $r->print(&hiddenfield('ufn',&getfilename($asheet)));  
     # Load dialog  
     if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {  
         $r->print('<p><input type=submit name=load value="Load ...">'.  
                   '<select name="loadthissheet">'.  
                   '<option name="default">Default</option>');  
         foreach (&othersheets($asheet,$asheetdata->{'sheettype'})) {  
             $r->print('<option name="'.$_.'"');  
             if ($ENV{'form.ufn'} eq $_) {  
                 $r->print(' selected');  
             }              }
             $r->print('>'.$_.'</option>');  
         }   
         $r->print('</select><p>');  
         if (&gettype($asheet) eq 'studentcalc') {  
             &setothersheets($asheet,&othersheets($asheet,'assesscalc'));  
         }          }
     }      }
     # Cached sheets  
     &expirationdates();  
     undef %oldsheets;  
     undef %loadedcaches;  
     if ($asheetdata->{'sheettype'} eq 'classcalc') {  
         $r->print("Loading previously calculated student sheets ...\n");  
         $r->rflush();  
         &cachedcsheets();  
     } elsif ($asheetdata->{'sheettype'} eq 'studentcalc') {  
         $r->print("Loading previously calculated assessment sheets ...\n");  
         $r->rflush();  
         &cachedssheets($asheetdata->{'uname'},$asheetdata->{'udom'},  
                        $asheetdata->{'uhome'});  
     }  
     # Update sheet, load rows  
     $r->print("Loaded sheet(s), updating rows ...<br>\n");  
     $r->rflush();  
     #      #
     &updatesheet($asheet,$asheetdata);      # Add the new line
     $r->print("Updated rows, loading row data ...\n");  
     $r->rflush();  
     #      #
     &loadrows($asheet,$asheetdata,$r);      $currentlystored{$fn}=join('___;___',@exportarr);
     $r->print("Loaded row data, calculating sheet ...<br>\n");  
     $r->rflush();  
     #      #
     my $calcoutput=&calcsheet($asheet);      # Stick everything back together
     $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>');  
     # See if something to save  
     if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {  
         my $fname='';  
         if ($ENV{'form.saveas'} && ($fname=$ENV{'form.newfn'})) {  
             $fname=~s/\W/\_/g;  
             if ($fname eq 'default') { $fname='course_default'; }  
             $fname.='_'.$asheetdata->{'sheettype'};  
             &setfilename($asheet,$fname);  
             $ENV{'form.ufn'}=$fname;  
             $r->print('<p>Saving spreadsheet: '.  
                       &writesheet($asheet,$ENV{'form.makedefufn'}).'<p>');  
         }  
     }  
     #      #
     #Write the modified worksheet      my $newstore='';
     $r->print('<b>Current sheet:</b> '.&getfilename($asheet).'<p>');      foreach (keys(%currentlystored)) {
     &tmpwrite($asheet);          if ($newstore) { $newstore.='___&___'; }
     if ($asheetdata->{'sheettype'} eq 'studentcalc') {          $newstore.=$_.'___=___'.$currentlystored{$_};
         $r->print('<br>Show rows with empty A column: ');  
     } else {  
         $r->print('<br>Show empty rows: ');  
     }   
     #  
     $r->print(&hiddenfield('userselhidden','true').  
               '<input type="checkbox" name="showall" onClick="submit()"');  
     #  
     if ($ENV{'form.showall'}) {   
         $r->print(' checked');   
     } else {  
         unless ($ENV{'form.userselhidden'}) {  
             unless   
                 ($ENV{'course.'.$ENV{'request.course.id'}.'.hideemptyrows'} eq 'yes') {  
                     $r->print(' checked');  
                     $ENV{'form.showall'}=1;  
                 }  
         }  
     }      }
     $r->print('>');      my $now=time;
     #      #
     # CSV format checkbox (classcalc sheets only)      # Store away the new value
     if ($asheetdata->{'sheettype'} eq 'classcalc') {  
         $r->print(' Output CSV format: <input type="checkbox" '.  
                   'name="showcsv" onClick="submit()"');  
         if ($ENV{'form.showcsv'}) { $r->print(' checked'); }  
         $r->print('>');  
     }  
     #      #
     # Buttons to insert rows      my $timekey = $key.'.time';
     $r->print('&nbsp;Student Status: '.      if ($stype eq 'studentcalc') {
               &Apache::lonhtmlcommon::StatusOptions          my $result = &Apache::lonnet::put('nohist_calculatedsheets',
               ($ENV{'form.Status'},'sheet'));                                            { $key     => $newstore,
     $r->print(<<ENDINSERTBUTTONS);                                              $timekey => $now },
 <br>                                            $self->{'cdom'},
 <input type='button' onClick='insertrow("top");'                                             $self->{'cnum'});
 value='Insert Row Top'>      } else {
 <input type='button' onClick='insertrow("bottom");'           my $result = &Apache::lonnet::put('nohist_calculatedsheets_'.$self->{'cid'},
 value='Insert Row Bottom'><br>                                            { $key     => $newstore,
 ENDINSERTBUTTONS                                              $timekey => $now },
     # Print out sheet                                            $self->{'udom'},
     &outsheet($r,$asheet,$asheetdata);                                            $self->{'uname'});
     $r->print('</form></body></html>');      }
     #  Done      return @exportarr;
     return OK;  
 }  }
   
 1;  1;
 __END__  
   
   
   __END__
   
   

Removed from v.1.108  
changed lines
  Added in v.1.179


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