Diff for /loncom/interface/Attic/lonspreadsheet.pm between versions 1.100.4.2 and 1.162

version 1.100.4.2, 2002/09/27 20:41:25 version 1.162, 2003/01/13 21:52:11
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
Line 64  built-in functions. Line 53  built-in functions.
 package Apache::lonspreadsheet;  package Apache::lonspreadsheet;
                           
 use strict;  use strict;
   use Apache::Constants qw(:common :http);
   use Apache::lonnet;
   use Apache::lonhtmlcommon;
   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;
   
   #
   # Caches for coursewide information 
   #
   my %Section;
   
 #  #
 # Caches for previously calculated spreadsheets  # Caches for previously calculated spreadsheets
 #  #
Line 96  my %spreadsheets; Line 95  my %spreadsheets;
 my %courserdatas;  my %courserdatas;
 my %userrdatas;  my %userrdatas;
 my %defaultsheets;  my %defaultsheets;
 my %updatedata;  my %rowlabel_cache;
   
 #  #
 # These global hashes are dependent on user, course and resource,   # These global hashes are dependent on user, course and resource, 
Line 122  my $tmpdir; Line 121  my $tmpdir;
 # =============================================================================  # =============================================================================
 # ===================================== Implements an instance of a spreadsheet  # ===================================== Implements an instance of a spreadsheet
   
 sub initsheet {  ##
     my $safeeval = new Safe(shift);  ## mask - used to reside in the safe space.  
     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  
   
 #  
 # 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 %v;   
 undef %t;  
 undef %f;  
 undef %c;  
 undef %rl;  
 undef @os;  
   
 $maxrow=0;  
 $sheettype='';  
   
 # filename/reference of the sheet  
   
 $filename='';  
   
 # user data  
 $uname='';  
 $uhome='';  
 $udom='';  
   
 # course data  
   
 $csec='';  
 $chome='';  
 $cnum='';  
 $cdom='';  
 $cid='';  
 $cfn='';  
   
 # symb  
   
 $usymb='';  
   
 # error messages  
   
 $errormsg='';  
   
 sub mask {  sub mask {
     my ($lower,$upper)=@_;      my ($lower,$upper)=@_;
       $upper = $lower if (! defined($upper));
     $lower=~/([A-Za-z]|\*)(\d+|\*)/;      #
     my $la=$1;      my ($la,$ld) = ($lower=~/([A-Za-z]|\*)(\d+|\*)/);
     my $ld=$2;      my ($ua,$ud) = ($upper=~/([A-Za-z]|\*)(\d+|\*)/);
       #
     $upper=~/([A-Za-z]|\*)(\d+|\*)/;  
     my $ua=$1;  
     my $ud=$2;  
     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 201  sub mask { Line 144  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 221  sub mask { Line 163  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 245  sub mask { Line 189  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].']';
        }         }
            }             }
        }         }
Line 254  sub mask { Line 198  sub mask {
     return '^'.$alpha.$num."\$";      return '^'.$alpha.$num."\$";
 }  }
   
   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(\&Apache::lonspreadsheet::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.
       # set by &setformulas and returned by &getformulas
       # &setformulas is called by &readsheet, &tmpread, &updateclasssheet,
       # &updatestudentassesssheet, &loadstudent, &loadcourse
       # &getformulas is called by &writesheet, &tmpwrite, &updateclasssheet,
       # &updatestudentassesssheet, &loadstudent, &loadcourse, &loadassessment, 
   undef %c; # Holds the constants for a sheet.  In the assessment
       # sheets, this is the A column.  Used in &MINPARM, &MAXPARM, &expandnamed,
       # &sett, and &setconstants.  There is no &getconstants.
       # &setconstants 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;
   $sheettype = '';
   
   # filename/reference of the sheet
   $filename = '';
   
   # user data
   $uname = '';
   $uhome = '';
   $udom  = '';
   
   # course data
   
   $csec = '';
   $chome= '';
   $cnum = '';
   $cdom = '';
   $cid  = '';
   $coursefilename  = '';
   
   # symb
   
   $usymb = '';
   
   # error messages
   $errormsg = '';
   
   
 #-------------------------------------------------------  #-------------------------------------------------------
   
 =item UWCALC(hashname,modules,units,date)   =item UWCALC(hashname,modules,units,date) 
Line 385  sub CDLHASH { Line 401  sub CDLHASH {
     if ($key =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) {      if ($key =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) {
  my $keymask = &mask($key);   my $keymask = &mask($key);
  # Assume the keys are addresses   # Assume the keys are addresses
  my @Temp = grep /$keymask/,keys(%v);   my @Temp = grep /$keymask/,keys(%sheet_values);
  @Keys = $v{@Temp};   @Keys = $sheet_values{@Temp};
     } else {      } else {
  $Keys[0]= $key;   $Keys[0]= $key;
     }      }
Line 397  sub CDLHASH { Line 413  sub CDLHASH {
     @Keys = @Temp;      @Keys = @Temp;
     if ($value =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) {      if ($value =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) {
  my $valmask = &mask($value);   my $valmask = &mask($value);
  my @Temp = grep /$valmask/,keys(%v);   my @Temp = grep /$valmask/,keys(%sheet_values);
  @Values =$v{@Temp};   @Values =$sheet_values{@Temp};
     } else {      } else {
  $Values[0]= $value;   $Values[0]= $value;
     }      }
Line 432  sub GETHASH { Line 448  sub GETHASH {
  $index = 0;   $index = 0;
     }      }
     if ($key =~ /^[A-z]\d+$/) {      if ($key =~ /^[A-z]\d+$/) {
  $key = $v{$key};   $key = $sheet_values{$key};
     }      }
     return $hashes{$name}->{$key}->[$index];      return $hashes{$name}->{$key}->[$index];
 }  }
Line 489  sub HASH { Line 505  sub HASH {
     if ($key =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) {      if ($key =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) {
  my $keymask = &mask($key);   my $keymask = &mask($key);
  # Assume the keys are addresses   # Assume the keys are addresses
  my @Temp = grep /$keymask/,keys(%v);   my @Temp = grep /$keymask/,keys(%sheet_values);
  @Keys = $v{@Temp};   @Keys = $sheet_values{@Temp};
     } else {      } else {
  $Keys[0]= $key;   $Keys[0]= $key;
     }      }
Line 502  sub HASH { Line 518  sub HASH {
     # Check to see if we have multiple $value(s)       # Check to see if we have multiple $value(s) 
     if ($value =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) {      if ($value =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) {
  my $valmask = &mask($value);   my $valmask = &mask($value);
  my @Temp = grep /$valmask/,keys(%v);   my @Temp = grep /$valmask/,keys(%sheet_values);
  @Values =$v{@Temp};   @Values =$sheet_values{@Temp};
     } else {      } else {
  $Values[0]= $value;   $Values[0]= $value;
     }      }
Line 532  returns the number of items in the range Line 548  returns the number of items in the range
 #-------------------------------------------------------  #-------------------------------------------------------
 sub NUM {  sub NUM {
     my $mask=mask(@_);      my $mask=mask(@_);
     my $num= $#{@{grep(/$mask/,keys(%v))}}+1;      my $num= $#{@{grep(/$mask/,keys(%sheet_values))}}+1;
     return $num;         return $num;   
 }  }
   
Line 540  sub BIN { Line 556  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(%v)) {      foreach (grep /$mask/,keys(%sheet_values)) {
         if (($v{$_}>=$low) && ($v{$_}<=$high)) {          if (($sheet_values{$_}>=$low) && ($sheet_values{$_}<=$high)) {
             $num++;              $num++;
         }          }
     }      }
Line 561  returns the sum of items in the range. Line 577  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(%v)) {      foreach (grep /$mask/,keys(%sheet_values)) {
         $sum+=$v{$_};          $sum+=$sheet_values{$_};
     }      }
     return $sum;         return $sum;   
 }  }
Line 578  compute the average of the items in the Line 594  compute the average of the items in the
 #-------------------------------------------------------  #-------------------------------------------------------
 sub MEAN {  sub MEAN {
     my $mask=mask(@_);      my $mask=mask(@_);
     my $sum=0; my $num=0;      my $sum=0; 
     foreach (grep /$mask/,keys(%v)) {      my $num=0;
         $sum+=$v{$_};      foreach (grep /$mask/,keys(%sheet_values)) {
           $sum+=$sheet_values{$_};
         $num++;          $num++;
     }      }
     if ($num) {      if ($num) {
Line 602  compute the standard deviation of the it Line 619  compute the standard deviation of the it
 sub STDDEV {  sub STDDEV {
     my $mask=mask(@_);      my $mask=mask(@_);
     my $sum=0; my $num=0;      my $sum=0; my $num=0;
     foreach (grep /$mask/,keys(%v)) {      foreach (grep /$mask/,keys(%sheet_values)) {
         $sum+=$v{$_};          $sum+=$sheet_values{$_};
         $num++;          $num++;
     }      }
     unless ($num>1) { return undef; }      unless ($num>1) { return undef; }
     my $mean=$sum/$num;      my $mean=$sum/$num;
     $sum=0;      $sum=0;
     foreach (grep /$mask/,keys(%v)) {      foreach (grep /$mask/,keys(%sheet_values)) {
         $sum+=($v{$_}-$mean)**2;          $sum+=($sheet_values{$_}-$mean)**2;
     }      }
     return sqrt($sum/($num-1));          return sqrt($sum/($num-1));    
 }  }
Line 627  compute the product of the items in the Line 644  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(%v)) {      foreach (grep /$mask/,keys(%sheet_values)) {
         $prod*=$v{$_};          $prod*=$sheet_values{$_};
     }      }
     return $prod;         return $prod;   
 }  }
Line 645  compute the maximum of the items in the Line 662  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(%v)) {      foreach (grep /$mask/,keys(%sheet_values)) {
         unless ($max) { $max=$v{$_}; }          unless ($max) { $max=$sheet_values{$_}; }
         if (($v{$_}>$max) || ($max eq '-')) { $max=$v{$_}; }          if (($sheet_values{$_}>$max) || ($max eq '-')) { $max=$sheet_values{$_}; }
     }       } 
     return $max;         return $max;   
 }  }
Line 664  compute the minimum of the items in the Line 681  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(%v)) {      foreach (grep /$mask/,keys(%sheet_values)) {
         unless ($max) { $max=$v{$_}; }          unless ($max) { $max=$sheet_values{$_}; }
         if (($v{$_}<$min) || ($min eq '-')) { $min=$v{$_}; }          if (($sheet_values{$_}<$min) || ($min eq '-')) { 
               $min=$sheet_values{$_}; 
           }
     }      }
     return $min;         return $min;   
 }  }
Line 685  sub SUMMAX { Line 704  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(%v)) {      foreach (grep /$mask/,keys(%sheet_values)) {
  push (@inside,$v{$_});   push (@inside,$sheet_values{$_});
     }      }
     @inside=sort(@inside);      @inside=sort(@inside);
     my $sum=0; my $i;      my $sum=0; my $i;
Line 710  sub SUMMIN { Line 729  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(%v)) {      foreach (grep /$mask/,keys(%sheet_values)) {
  $inside[$#inside+1]=$v{$_};   $inside[$#inside+1]=$sheet_values{$_};
     }      }
     @inside=sort(@inside);      @inside=sort(@inside);
     my $sum=0; my $i;      my $sum=0; my $i;
Line 721  sub SUMMIN { Line 740  sub SUMMIN {
     return $sum;         return $sum;   
 }  }
   
   #-------------------------------------------------------
   
   =item MINPARM(parametername)
   
   Returns the minimum value of the parameters matching the parametername.
   parametername should be a string such as 'duedate'.
   
   =cut
   
   #-------------------------------------------------------
   sub MINPARM {
       my ($expression) = @_;
       my $min = undef;
       study($expression);
       foreach $parameter (keys(%c)) {
           next if ($parameter !~ /$expression/);
           if ((! defined($min)) || ($min > $c{$parameter})) {
               $min = $c{$parameter} 
           }
       }
       return $min;
   }
   
   #-------------------------------------------------------
   
   =item MAXPARM(parametername)
   
   Returns the maximum value of the parameters matching the input parameter name.
   parametername should be a string such as 'duedate'.
   
   =cut
   
   #-------------------------------------------------------
   sub MAXPARM {
       my ($expression) = @_;
       my $max = undef;
       study($expression);
       foreach $parameter (keys(%c)) {
           next if ($parameter !~ /$expression/);
           if ((! defined($min)) || ($max < $c{$parameter})) {
               $max = $c{$parameter} 
           }
       }
       return $max;
   }
   
   #--------------------------------------------------------
 sub expandnamed {  sub expandnamed {
     my $expression=shift;      my $expression=shift;
     if ($expression=~/^\&/) {      if ($expression=~/^\&/) {
Line 766  sub expandnamed { Line 832  sub expandnamed {
         foreach $parameter (keys(%c)) {          foreach $parameter (keys(%c)) {
             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) {
             $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) {
Line 777  sub expandnamed { Line 845  sub expandnamed {
                 }                  }
             }              }
         } 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;
     }      }
Line 791  sub sett { Line 861  sub sett {
     } else {      } else {
         $pattern='[A-Z]';          $pattern='[A-Z]';
     }      }
       # Deal with the template row
 # Deal with the template row  
     foreach (keys(%f)) {      foreach (keys(%f)) {
  if ($_=~/template\_(\w)/) {   next if ($_!~/template\_(\w)/);
   my $col=$1;          my $col=$1;
           unless ($col=~/^$pattern/) {          next if ($col=~/^$pattern/);
     foreach (keys(%f)) {          foreach (keys(%f)) {
       if ($_=~/A(\d+)/) {              next if ($_!~/A(\d+)/);
  my $trow=$1;              my $trow=$1;
                 if ($trow) {              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}=$f{'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 ','
                     $t{$lb}=~s/\.\.+/\,/g;              $t{$lb}=~s/\.\.+/\,/g;
                     # Replace 'A0' with the value from 'A0'              # Replace 'A0' with the value from 'A0'
                     $t{$lb}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$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.&expandnamed($2)/ge;
                 }          }
       }  
     }  
   }  
       }  
     }      }
       # Deal with the normal cells
 # Deal with the normal cells  
     foreach (keys(%f)) {      foreach (keys(%f)) {
  if (exists($f{$_}) && ($_!~/template\_/)) {   if (exists($f{$_}) && ($_!~/template\_/)) {
             my $matches=($_=~/^$pattern(\d+)/);              my $matches=($_=~/^$pattern(\d+)/);
Line 831  sub sett { Line 895  sub sett {
             } else {              } else {
        $t{$_}=$f{$_};         $t{$_}=$f{$_};
                $t{$_}=~s/\.\.+/\,/g;                 $t{$_}=~s/\.\.+/\,/g;
                $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g;                 $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
                $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge;                 $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge;
             }              }
         }          }
     }      }
 # For inserted lines, [B-Z] is also valid      # For inserted lines, [B-Z] is also valid
   
     unless ($sheettype eq 'assesscalc') {      unless ($sheettype eq 'assesscalc') {
        foreach (keys(%f)) {         foreach (keys(%f)) {
    if ($_=~/[B-Z](\d+)/) {     if ($_=~/[B-Z](\d+)/) {
        if ($f{'A'.$1}=~/^[\~\-]/) {         if ($f{'A'.$1}=~/^[\~\-]/) {
            $t{$_}=$f{$_};             $t{$_}=$f{$_};
                   $t{$_}=~s/\.\.+/\,/g;                    $t{$_}=~s/\.\.+/\,/g;
                   $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g;                    $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
                   $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge;                    $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&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'}=$f{'A0'};
     $t{'A0'}=~s/\.\.+/\,/g;      $t{'A0'}=~s/\.\.+/\,/g;
     $t{'A0'}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$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.&expandnamed($2)/ge;
 }  }
   
 sub calc {  sub calc {
     undef %v;      undef %sheet_values;
     &sett();      &sett();
     my $notfinished=1;      my $notfinished=1;
     my $lastcalc='';      my $lastcalc='';
Line 868  sub calc { Line 930  sub calc {
     while ($notfinished) {      while ($notfinished) {
  $notfinished=0;   $notfinished=0;
         foreach (keys(%t)) {          foreach (keys(%t)) {
             my $old=$v{$_};              #$errorlog .= "$_:".$t{$_};
             $v{$_}=eval $t{$_};              my $old=$sheet_values{$_};
               $sheet_values{$_}=eval $t{$_};
     if ($@) {      if ($@) {
  undef %v;   undef %sheet_values;
                 return $_.': '.$@;                  return $_.': '.$@;
             }              }
     if ($v{$_} ne $old) { $notfinished=1; $lastcalc=$_; }      if ($sheet_values{$_} ne $old) { $notfinished=1; $lastcalc=$_; }
               #$errorlog .= ":".$sheet_values{$_}."\n";
         }          }
         $depth++;          $depth++;
         if ($depth>100) {          if ($depth>100) {
     undef %v;      undef %sheet_values;
             return $lastcalc.': Maximum calculation depth exceeded';              return $lastcalc.': Maximum calculation depth exceeded';
         }          }
     }      }
     return '';      return '';
 }  }
   
   # ------------------------------------------- End of "Inside of the safe space"
   ENDDEFS
       $safeeval->reval($code);
       return $safeeval;
   }
   
   #
   # 
   #
 sub templaterow {  sub templaterow {
       my $sheet = 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 ('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 => $sheet->{'f'}->{'template_'.$_},
         $cols[$#cols+1]="'template_$_','$fm'".'___eq___'.$fm;                       value   => $sheet->{'f'}->{'template_'.$_} });
     }      }
     return @cols;      return ($rowlabel,@cols);
 }  }
   
 sub outrowassess {  sub outrowassess {
     my $n=shift;      # $n is the current row number
       my ($sheet,$n) = @_;
     my @cols=();      my @cols=();
       my $rowlabel='';
     if ($n) {      if ($n) {
        my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{'A'.$n});          my ($usy,$ufn)=split(/__&&&\__/,$sheet->{'f'}->{'A'.$n});
       if ($rl{$usy}) {          if (exists($sheet->{'rowlabel'}->{$usy})) {
        $cols[0]=$rl{$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.
       } else { $cols[0]=''; }              $rowlabel = $sheet->{'rowlabel'}->{$usy}.':'.
        foreach (@os) {                  &Apache::lonnet::escape($ufn);
            $cols[0].='<option name="'.$_.'"';          } else { 
             if ($ufn eq $_) {              $rowlabel = '';
                $cols[0].=' selected';          }
             }      } elsif ($ENV{'request.role'} =~ /^st\./) {
             $cols[0].='>'.$_.'</option>';          $rowlabel = 'Summary</td><td>0';
        }  
        $cols[0].='</select>';  
     } 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 => $sheet->{'f'}->{$_.$n},
         push(@cols,"'$_$n','$fm'".'___eq___'.$v{$_.$n});                       value   => $sheet->{'values'}->{$_.$n}});
     }      }
     return @cols;      return ($rowlabel,@cols);
 }  }
   
 sub outrow {  sub outrow {
     my $n=shift;      my ($sheet,$n)=@_;
     my @cols=();      my @cols=();
       my $rowlabel;
     if ($n) {      if ($n) {
        $cols[0]=$rl{$f{'A'.$n}};          $rowlabel = $sheet->{'rowlabel'}->{$sheet->{'f'}->{'A'.$n}};
     } else {      } else {
        $cols[0]='<b><font size=+1>Export</font></b>';          if ($sheet->{'sheettype'} 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 => $sheet->{'f'}->{$_.$n},
         $cols[$#cols+1]="'$_$n','$fm'".'___eq___'.$v{$_.$n};                       value   => $sheet->{'values'}->{$_.$n}});
     }      }
     return @cols;      return ($rowlabel,@cols);
 }  
   
 sub 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') {  
  $exportarray[$#exportarray+1]=$v{$_.'0'};  
     }   
     return @exportarray;  
 }  
   
 # ------------------------------------------- End of "Inside of the safe space"  
 ENDDEFS  
     $safeeval->reval($code);  
     return $safeeval;  
 }  }
   
 # ------------------------------------------------ Add or change formula values  # ------------------------------------------------ Add or change formula values
   
 sub setformulas {  sub setformulas {
     my ($safeeval,%f)=@_;      my ($sheet)=shift;
     %{$safeeval->varglob('f')}=%f;      %{$sheet->{'safe'}->varglob('f')}=%{$sheet->{'f'}};
 }  }
   
 # ------------------------------------------------ Add or change formula values  # ------------------------------------------------ Add or change formula values
   
 sub setconstants {  sub setconstants {
     my ($safeeval,%c)=@_;      my ($sheet)=shift;
     %{$safeeval->varglob('c')}=%c;      my ($constants) = @_;
       if (! ref($constants)) {
           my %tmp = @_;
           $constants = \%tmp;
       }
       $sheet->{'constants'} = $constants;
       return %{$sheet->{'safe'}->varglob('c')}=%{$sheet->{'constants'}};
 }  }
   
 # --------------------------------------------- Set names of other spreadsheets  # --------------------------------------------- Set names of other spreadsheets
   
 sub setothersheets {  sub setothersheets {
     my ($safeeval,@os)=@_;      my $sheet = shift;
     @{$safeeval->varglob('os')}=@os;      my @othersheets = @_;
       $sheet->{'othersheets'} = \@othersheets;
       @{$sheet->{'safe'}->varglob('os')}=@othersheets;
       return;
 }  }
   
 # ------------------------------------------------ Add or change formula values  # ------------------------------------------------ Add or change formula values
   
 sub setrowlabels {  sub setrowlabels {
     my ($safeeval,%rl)=@_;      my $sheet=shift;
     %{$safeeval->varglob('rl')}=%rl;      my ($rowlabel) = @_;
       if (! ref($rowlabel)) {
           my %tmp = @_;
           $rowlabel = \%tmp;
       }
       $sheet->{'rowlabel'}=$rowlabel;
 }  }
   
 # ------------------------------------------------------- Calculate spreadsheet  # ------------------------------------------------------- Calculate spreadsheet
   
 sub calcsheet {  sub calcsheet {
     my $safeeval=shift;      my $sheet=shift;
     return $safeeval->reval('&calc();');      my $result =  $sheet->{'safe'}->reval('&calc();');
 }      %{$sheet->{'values'}} = %{$sheet->{'safe'}->varglob('sheet_values')};
       return $result;
 # ------------------------------------------------------------------ Get values  
   
 sub getvalues {  
     my $safeeval=shift;  
     return $safeeval->reval('%v');  
 }  }
   
 # ---------------------------------------------------------------- Get formulas  # ---------------------------------------------------------------- Get formulas
   # Return a copy of the formulas
 sub getformulas {  sub getformulas {
     my $safeeval=shift;      my $sheet = shift;
     return %{$safeeval->varglob('f')};      return %{$sheet->{'safe'}->varglob('f')};
 }  
   
 # ----------------------------------------------------- Get value of $f{'A'.$n}  
   
 sub getfa {  
     my ($safeeval,$n)=@_;  
     return $safeeval->reval('$f{"A'.$n.'"}');  
 }  
   
 # -------------------------------------------------------------------- Get type  
   
 sub gettype {  
     my $safeeval=shift;  
     return $safeeval->reval('$sheettype');  
 }  }
   
 # ------------------------------------------------------------------ Set maxrow  sub geterrorlog {
       my $sheet = shift;
 sub setmaxrow {      return ${$sheet->{'safe'}->varglob('errorlog')};    
     my ($safeeval,$row)=@_;  }
     $safeeval->reval('$maxrow='.$row.';');  
 }  sub gettitle {
       my $sheet = shift;
 # ------------------------------------------------------------------ Get maxrow      if ($sheet->{'sheettype'} eq 'classcalc') {
           return $sheet->{'coursedesc'};
 sub getmaxrow {      } elsif ($sheet->{'sheettype'} eq 'studentcalc') {
     my $safeeval=shift;          return 'Grades for '.$sheet->{'uname'}.'@'.$sheet->{'udom'};
     return $safeeval->reval('$maxrow');      } elsif ($sheet->{'sheettype'} eq 'assesscalc') {
 }          if (($sheet->{'usymb'} eq '_feedback') ||
               ($sheet->{'usymb'} eq '_evaluation') ||
 # ---------------------------------------------------------------- Set filename              ($sheet->{'usymb'} eq '_discussion') ||
               ($sheet->{'usymb'} eq '_tutoring')) {
 sub setfilename {              my $title = $sheet->{'usymb'};
     my ($safeeval,$fn)=@_;              $title =~ s/^_//;
     $safeeval->reval('$filename="'.$fn.'";');              $title = ucfirst($title);
 }              return $title;
           }
 # ---------------------------------------------------------------- Get filename          return if (! defined($sheet->{'mapid'}) || 
                      $sheet->{'mapid'} !~ /^\d+$/);
 sub getfilename {          my $mapid = $sheet->{'mapid'};
     my $safeeval=shift;          return if (! defined($sheet->{'resid'}) || 
     return $safeeval->reval('$filename');                     $sheet->{'resid'} !~ /^\d+$/);
 }          my $resid = $sheet->{'resid'};
           my %course_db;
 # --------------------------------------------------------------- Get course ID          tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db',
               &GDBM_READER(),0640);
 sub getcid {          return if (! tied(%course_db));
     my $safeeval=shift;          my $key = 'title_'.$mapid.'.'.$resid;
     return $safeeval->reval('$cid');          my $title = '';
 }          if (exists($course_db{$key})) {
               $title = $course_db{$key};
 # --------------------------------------------------------- Get course filename          } else {
               $title = $sheet->{'usymb'};
 sub getcfn {          }
     my $safeeval=shift;          untie (%course_db);
     return $safeeval->reval('$cfn');          return $title;
 }      }
   
 # ----------------------------------------------------------- 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  
   
 sub getcsec {  
     my $safeeval=shift;  
     return $safeeval->reval('$csec');  
 }  
   
 # --------------------------------------------------------------- Get user name  
   
 sub getuname {  
     my $safeeval=shift;  
     return $safeeval->reval('$uname');  
 }  }
   
 # ------------------------------------------------------------- Get user domain  # ----------------------------------------------------- Get value of $f{'A'.$n}
   sub getfa {
 sub getudom {      my $sheet = shift;
     my $safeeval=shift;      my ($n)=@_;
     return $safeeval->reval('$udom');      return $sheet->{'safe'}->reval('$f{"A'.$n.'"}');
 }  }
   
 # --------------------------------------------------------------- Get user home  # ------------------------------------------------------------- Export of A-row
   sub exportdata {
 sub getuhome {      my $sheet=shift;
     my $safeeval=shift;      my @exportarray=();
     return $safeeval->reval('$uhome');      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') {
           if (exists($sheet->{'values'}->{$_.'0'})) {
               push(@exportarray,$sheet->{'values'}->{$_.'0'});
           } else {
               push(@exportarray,'');
           }
       } 
       return @exportarray;
 }  }
   
 # -------------------------------------------------------------------- Get symb  
   
 sub getusymb {  
     my $safeeval=shift;  
     return $safeeval->reval('$usymb');  
 }  
   
 # ------------------------------------------------------------- Export of A-row  
   
 sub exportdata {  sub update_student_sheet{
     my $safeeval=shift;      my ($sheet,$r,$c) = @_;
     return $safeeval->reval('&exportrowa()');      # Load in the studentcalc sheet
       &readsheet($sheet,'default_studentcalc');
       # Determine the structure (contained assessments, etc) of the sheet
       &updatesheet($sheet);
       # Load in the cached sheets for this student
       &cachedssheets($sheet);
       # Load in the (possibly cached) data from the assessment sheets        
       &loadstudent($sheet,$r,$c);
       # Compute the sheet
       &calcsheet($sheet);
 }  }
   
   
 # ========================================================== End of Spreadsheet  # ========================================================== End of Spreadsheet
 # =============================================================================  # =============================================================================
   
 #  #
 # Procedures for screen output  # Procedures for spreadsheet output
 #  #
 # --------------------------------------------- Produce output row n from sheet  # --------------------------------------------- Produce output row n from sheet
   
 sub rown {  sub get_row {
     my ($safeeval,$n)=@_;      my ($sheet,$n) = @_;
     my $defaultbg;      my ($rowlabel,@rowdata);
     my $rowdata='';      if ($n eq '-') { 
     my $dataflag=0;          ($rowlabel,@rowdata) = &templaterow($sheet);
     unless ($n eq '-') {      } elsif ($sheet->{'sheettype'} eq 'studentcalc') {
        $defaultbg=((($n-1)/5)==int(($n-1)/5))?'#E0E0':'#FFFF';          ($rowlabel,@rowdata) = &outrowassess($sheet,$n);
     } else {      } else {
        $defaultbg='#E0FF';          ($rowlabel,@rowdata) = &outrow($sheet,$n);
     }      }
     unless ($ENV{'form.showcsv'}) {      return ($rowlabel,@rowdata);
        $rowdata.="\n<tr><td><b><font size=+1>$n</font></b></td>";  }
     } else {  
        $rowdata.="\n".'"'.$n.'"';  ########################################################################
     }  ########################################################################
     my $showf=0;  sub sort_indicies {
     my $proc;      my $sheet = shift;
     my $maxred=1;      my @sortidx=();
     my $sheettype=&gettype($safeeval);      #
     if ($sheettype eq 'studentcalc') {      if ($sheet->{'sheettype'} eq 'classcalc') {
         $proc='&outrowassess';          my @sortby=(undef);
         $maxred=26;          # Skip row 0
     } else {          for (my $row=1;$row<=$sheet->{'maxrow'};$row++) {
         $proc='&outrow';              my (undef,$sname,$sdom,$fullname,$section,$id) = 
                   split(':',$sheet->{'rowlabel'}->{$sheet->{'f'}->{'A'.$row}});
               push (@sortby, lc($fullname));
               push (@sortidx, $row);
           }
           @sortidx = sort { $sortby[$a] cmp $sortby[$b]; } @sortidx;
       } elsif ($sheet->{'sheettype'} eq 'studentcalc') {
           my @sortby1=(undef);
           my @sortby2=(undef);
           # Skip row 0
           for (my $row=1;$row<=$sheet->{'maxrow'};$row++) {
               my ($key,undef) = split(/__&&&\__/,$sheet->{'f'}->{'A'.$row});
               my $rowlabel = $sheet->{'rowlabel'}->{$key};
               my (undef,$symb,$mapid,$resid,$title,$ufn) = 
                   split(':',$rowlabel);
               $ufn   = &Apache::lonnet::unescape($ufn);
               $symb  = &Apache::lonnet::unescape($symb);
               $title = &Apache::lonnet::unescape($title);
               my ($sequence) = ($symb =~ /\/([^\/]*\.sequence)/);
               if ($sequence eq '') {
                   $sequence = $symb;
               }
               push (@sortby1, $sequence);
               push (@sortby2, $title);
               push (@sortidx, $row);
           }
           @sortidx = sort { $sortby1[$a] cmp $sortby1[$b] || 
                                 $sortby2[$a] cmp $sortby2[$b] } @sortidx;
       } else {
           my @sortby=(undef);
           # Skip row 0
           for (my $row=1;$row<=$sheet->{'maxrow'};$row++) {
               push (@sortby, $sheet->{'safe'}->reval('$f{"A'.$row.'"}'));
               push (@sortidx, $row);
           }
           @sortidx = sort { $sortby[$a] cmp $sortby[$b]; } @sortidx;
       }
       return @sortidx;
   }
   
   #############################################################
   ###                                                       ###
   ###              Spreadsheet Output Routines              ###
   ###                                                       ###
   #############################################################
   
   ############################################
   ##         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 ($sheet,$r) = @_;
       my ($num_uneditable,$realm,$row_type);
       my $requester_is_student = ($ENV{'request.role'} =~ /^st\./);
       if ($sheet->{'sheettype'} eq 'assesscalc') {
           $num_uneditable = 1;
           $realm = 'Assessment';
           $row_type = 'Item';
       } elsif ($sheet->{'sheettype'} eq 'studentcalc') {
           $num_uneditable = 26;
           $realm = 'User';
           $row_type = 'Assessment';
       } elsif ($sheet->{'sheettype'} 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 {
               $tabledata.='<td>';
           }
           $tabledata.="<b><font size=+1>$_</font></b></td>";
           $label_num++;
     }      }
     if ($sheettype eq 'assesscalc') {      $tabledata.="</tr>\n";
         $maxred=1;      $r->print($tabledata);
     } else {      ####################################
         $maxred=26;      # Print out template row
       ####################################
       my ($num_cols_output,$row_html,$rowlabel,@rowdata);
       
       if (! $requester_is_student) {
           ($rowlabel,@rowdata) = &get_row($sheet,'-');
           $row_html = '<tr><td>'.&format_html_rowlabel($sheet,$rowlabel).'</td>';
           $num_cols_output = 0;
           foreach my $cell (@rowdata) {
               if ($requester_is_student || 
                   $num_cols_output++ < $num_uneditable) {
                   $row_html .= '<td bgcolor="#FFDDDD">';
                   $row_html .= &html_uneditable_cell($cell,'#FFDDDD');
               } else {
                   $row_html .= '<td bgcolor="#EOFFDD">';
                   $row_html .= &html_editable_cell($cell,'#E0FFDD');
               }
               $row_html .= '</td>';
           }
           $row_html.= "</tr>\n";
           $r->print($row_html);
     }      }
     if (&getfa($safeeval,$n)=~/^[\~\-]/) { $maxred=1; }      ####################################
     if ($n eq '-') { $proc='&templaterow'; $n=-1; $dataflag=1; }      # Print out summary/export row
     foreach ($safeeval->reval($proc.'('.$n.')')) {      ####################################
        my $bgcolor=$defaultbg.((($showf-1)/5==int(($showf-1)/5))?'99':'DD');      ($rowlabel,@rowdata) = &get_row($sheet,'0');
        my ($fm,$vl)=split(/\_\_\_eq\_\_\_/,$_);      $row_html = '<tr><td>'.&format_html_rowlabel($sheet,$rowlabel).'</td>';
        if ((($vl ne '') || ($vl eq '0')) &&      $num_cols_output = 0;
            (($showf==1) || ($sheettype ne 'studentcalc'))) { $dataflag=1; }      foreach my $cell (@rowdata) {
        if ($showf==0) { $vl=$_; }          if ($num_cols_output++ < 26 && ! $requester_is_student) {
       unless ($ENV{'form.showcsv'}) {              $row_html .= '<td bgcolor="#CCCCFF">';
        if ($showf<=$maxred) { $bgcolor='#FFDDDD'; }              $row_html .= &html_editable_cell($cell,'#CCCCFF');
        if (($n==0) && ($showf<=26)) { $bgcolor='#CCCCFF'; }           } else {
        if (($showf>$maxred) || ((!$n) && ($showf>0))) {              $row_html .= '<td bgcolor="#DDCCFF">';
    if ($vl eq '') {              $row_html .= &html_uneditable_cell($cell,'#CCCCFF');
        $vl='<font size=+2 color='.$bgcolor.'>&#35;</font>';          }
            }          $row_html .= '</td>';
            $rowdata.='<td bgcolor='.$bgcolor.'>';      }
            if ($ENV{'request.role'} =~ /^st\./) {      $row_html.= "</tr>\n";
                $rowdata.=$vl;      $r->print($row_html);
            } else {      $r->print('</table>');
                $rowdata.='<a href="javascript:celledit('.$fm.');">'.      ####################################
                    $vl.'</a>';      # Prepare to output rows
            }      ####################################
            $rowdata.='</td>';      my @Rows = &sort_indicies($sheet);
        } else {      #
            $rowdata.='<td bgcolor='.$bgcolor.'>&nbsp;'.$vl.'&nbsp;</td>';      # Loop through the rows and output them one at a time
        }      my $rows_output=0;
       } else {      foreach my $rownum (@Rows) {
   $rowdata.=',"'.$vl.'"';          my ($rowlabel,@rowdata) = &get_row($sheet,$rownum);
       }          next if ($rowlabel =~ /^\s*$/);
        $showf++;          next if (($sheet->{'sheettype'} eq 'assesscalc') && 
     }  # End of foreach($safeval...)                   (! $ENV{'form.showall'})                &&
     if ($ENV{'form.showall'} || ($dataflag)) {                   ($rowdata[0]->{'value'} =~ /^\s*$/));
        return $rowdata.($ENV{'form.showcsv'}?'':'</tr>');          if (! $ENV{'form.showall'} &&
     } else {              $sheet->{'sheettype'} =~ /^(studentcalc|classcalc)$/) {
        return '';              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 ($sheet->{'sheettype'} eq 'classcalc') {
               $row_html.='<td>'.&format_html_rowlabel($sheet,$rowlabel).'</td>';
               # Output links for each student?
               # Nope, that is already done for us in format_html_rowlabel 
               # (for now)
           } elsif ($sheet->{'sheettype'} eq 'studentcalc') {
               my $ufn = (split(/:/,$rowlabel))[5];
               $row_html.='<td>'.&format_html_rowlabel($sheet,$rowlabel);
               $row_html.= '<br>'.
                   '<select name="sel_'.$rownum.'" '.
                       'onChange="changesheet('.$rownum.')">'.
                           '<option name="default">Default</option>';
   
               foreach (@{$sheet->{'othersheets'}}) {
                   $row_html.='<option name="'.$_.'"';
                   if ($ufn eq $_) {
                       $row_html.=' selected';
                   }
                   $row_html.='>'.$_.'</option>';
               }
               $row_html.='</select></td>';
           } elsif ($sheet->{'sheettype'} eq 'assesscalc') {
               $row_html.='<td>'.&format_html_rowlabel($sheet,$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->rflush();
                   $r->print('<table border=2>'.
                             '<tr><td>&nbsp;<td>'.$row_type.'</td>'.
                             '<td>'.
                             join('</td><td>',
                                  (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
                                         'abcdefghijklmnopqrstuvwxyz'))).
                             "</td></tr>\n");
               }
               $rows_output++;
               $r->print($row_html);
           }
     }      }
       #
       $r->print('</table>');
       #
       # Debugging code (be sure to uncomment errorlog code in safe space):
       #
       # $r->print("\n<pre>");
       # $r->print(&geterrorlog($sheet));
       # $r->print("\n</pre>");
       return 1;
 }  }
   
 # ------------------------------------------------------------- Print out sheet  ############################################
   ##         csv output routines            ##
   ############################################
   sub outsheet_csv   {
       my ($sheet,$r) = @_;
       my $csvdata = '';
       my @Values;
       ####################################
       # Prepare to output rows
       ####################################
       my @Rows = &sort_indicies($sheet);
       #
       # Loop through the rows and output them one at a time
       my $rows_output=0;
       foreach my $rownum (@Rows) {
           my ($rowlabel,@rowdata) = &get_row($sheet,$rownum);
           next if ($rowlabel =~ /^\s*$/);
           push (@Values,&format_csv_rowlabel($sheet,$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;
   }
   
 sub outsheet {  ############################################
     my ($r,$safeeval)=@_;  ##        Excel output routines           ##
     my $maxred;  ############################################
     my $realm;  sub outsheet_recursive_excel {
     if (&gettype($safeeval) eq 'assesscalc') {      my ($sheet,$r) = @_;
         $maxred=1;      my $c = $r->connection;
         $realm='Assessment';      return undef if ($sheet->{'sheettype'} ne 'classcalc');
     } elsif (&gettype($safeeval) eq 'studentcalc') {      my ($workbook,$filename) = &create_excel_spreadsheet($sheet,$r);
         $maxred=26;      return undef if (! defined($workbook));
         $realm='User';      #
     } else {      # Create main worksheet
         $maxred=26;      my $main_worksheet = $workbook->addworksheet('main');
         $realm='Course';      #
     }      # Figure out who the students are
     my $maxyellow=52-$maxred;      my %f=&getformulas($sheet);
     my $tabledata;      my $count = 0;
     unless ($ENV{'form.showcsv'}) {      $r->print(<<END);
        $tabledata=  <p>
         '<table border=2><tr><th colspan=2 rowspan=2><font size=+2>'.  Compiling Excel Workbook with a worksheet for each student.
                   $realm.'</font></th>'.  </p><p>
                   '<td bgcolor=#FFDDDD colspan='.$maxred.  This operation may take longer than a complete recalculation of the
                   '><b><font size=+1>Import</font></b></td>'.  spreadsheet. 
                   '<td colspan='.$maxyellow.  </p><p>
   '><b><font size=+1>Calculations</font></b></td></tr><tr>';  To abort this operation, hit the stop button on your browser.
     my $showf=0;  </p><p>
     foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',  A link to the spreadsheet will be available at the end of this process.
      'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',  </p>
      'a','b','c','d','e','f','g','h','i','j','k','l','m',  <p>
      'n','o','p','q','r','s','t','u','v','w','x','y','z') {  END
         $showf++;      $r->rflush();
         if ($showf<=$maxred) {       my $starttime = time;
            $tabledata.='<td bgcolor="#FFDDDD">';       foreach my $rownum (&sort_indicies($sheet)) {
         } else {          $count++;
            $tabledata.='<td>';          my ($sname,$sdom) = split(':',$f{'A'.$rownum});
           my $student_excel_worksheet=$workbook->addworksheet($sname.'@'.$sdom);
           # Create a new spreadsheet
           my $studentsheet = &makenewsheet($sname,$sdom,'studentcalc',undef);
           # Read in the spreadsheet definition
           &update_student_sheet($studentsheet,$r,$c);
           # Stuff the sheet into excel
           &export_sheet_as_excel($studentsheet,$student_excel_worksheet);
           my $totaltime = int((time - $starttime) / $count * $sheet->{'maxrow'});
           my $timeleft = int((time - $starttime) / $count * ($sheet->{'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;
         }          }
         $tabledata.="<b><font size=+1>$_</font></b></td>";  
     }      }
     $tabledata.='</tr>'.&rown($safeeval,'-').&rown($safeeval,0);      #
    } else { $tabledata='<pre>'; }      if(! $c->aborted() ) {
           $r->print('All students spreadsheets completed!<br />');
           $r->rflush();
           #
           # &export_sheet_as_excel fills $worksheet with the data from $sheet
           &export_sheet_as_excel($sheet,$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 ($sheet,$r) = @_;
       my ($workbook,$filename) = &create_excel_spreadsheet($sheet,$r);
       return undef if (! defined($workbook));
       my $sheetname;
       if ($sheet->{'sheettype'} eq 'classcalc') {
           $sheetname = 'Main';
       } elsif ($sheet->{'sheettype'} eq 'studentcalc') {
           $sheetname = $sheet->{'uname'}.'@'.$sheet->{'udom'};
       } elsif ($sheet->{'sheettype'} eq 'assesscalc') {
           $sheetname = $sheet->{'uname'}.'@'.$sheet->{'udom'}.' assessment';
       }
       my $worksheet = $workbook->addworksheet($sheetname);
       #
       # &export_sheet_as_excel fills $worksheet with the data from $sheet
       &export_sheet_as_excel($sheet,$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 ($sheet,$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 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);
   }
   
     $r->print($tabledata);  sub export_sheet_as_excel {
       my $sheet = shift;
       my $worksheet = shift;
       #
       my $rows_output = 0;
       my $cols_output = 0;
       ####################################
       #    Write an identifying row      #
       ####################################
       my @Headerinfo = ($sheet->{'coursedesc'});
       my $title = &gettitle($sheet);
       $cols_output = 0;    
       if (defined($title)) {
           $worksheet->write($rows_output++,$cols_output++,$title);
       }
       ####################################
       #   Write the summary/export row   #
       ####################################
       my ($rowlabel,@rowdata) = &get_row($sheet,'0');
       my $label = &format_excel_rowlabel($sheet,$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($sheet);
       #
       # Loop through the rows and output them one at a time
       foreach my $rownum (@Rows) {
           my ($rowlabel,@rowdata) = &get_row($sheet,$rownum);
           next if ($rowlabel =~ /^[\s]*$/);
           $cols_output = 0;
           my $label = &format_excel_rowlabel($sheet,$rowlabel);
           if ( ! $ENV{'form.showall'} &&
                $sheet->{'sheettype'} =~ /^(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++,$label);
           if (ref($label)) {
               $cols_output = (scalar(@$label));
           }
           foreach my $cell (@rowdata) {
               $worksheet->write($rows_output,$cols_output++,$cell->{'value'});
           }
           $rows_output++;
       }
       return;
   }
   
     my $row;  ############################################
     my $maxrow=&getmaxrow($safeeval);  ##          XML output routines           ##
   ############################################
   sub outsheet_xml   {
       my ($sheet,$r) = @_;
       ## Someday XML
       ## Will be rendered for the user
       ## But not on this day
   }
   
     my @sortby=();  ##
     my @sortidx=();  ## Outsheet - calls other outsheet_* functions
     for ($row=1;$row<=$maxrow;$row++) {  ##
        $sortby[$row-1]=$safeeval->reval('$f{"A'.$row.'"}');  sub outsheet {
        $sortidx[$row-1]=$row-1;      my ($sheet,$r)=@_;
     }      if (! exists($ENV{'form.output'})) {
     @sortidx=sort { $sortby[$a] cmp $sortby[$b]; } @sortidx;          $ENV{'form.output'} = 'HTML';
       }
         my $what='Student';      if (lc($ENV{'form.output'}) eq 'csv') {
         if (&gettype($safeeval) eq 'assesscalc') {          &outsheet_csv($sheet,$r);
     $what='Item';      } elsif (lc($ENV{'form.output'}) eq 'excel') {
  } elsif (&gettype($safeeval) eq 'studentcalc') {          &outsheet_excel($sheet,$r);
             $what='Assessment';      } elsif (lc($ENV{'form.output'}) eq 'recursive excel') {
         }          &outsheet_recursive_excel($sheet,$r);
   #    } elsif (lc($ENV{'form.output'}) eq 'xml' ) {
     my $n=0;  #        &outsheet_xml($sheet,$r);
     for ($row=0;$row<$maxrow;$row++) {      } else {
      my $thisrow=&rown($safeeval,$sortidx[$row]+1);          &outsheet_html($sheet,$r);
      if ($thisrow) {  
        if (($n/25==int($n/25)) && (!$ENV{'form.showcsv'})) {  
  $r->print("</table>\n<br>\n");  
         $r->rflush();  
         $r->print('<table border=2><tr><td>&nbsp;<td>'.$what.'</td>');  
         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') {  
            $r->print('<td>'.$_.'</td>');  
         }  
         $r->print('</tr>');  
        }  
        $n++;  
        $r->print($thisrow);  
       }  
     }      }
     $r->print($ENV{'form.showcsv'}?'</pre>':'</table>');  
 }  }
   
 #  ########################################################################
 # ----------------------------------------------- Read list of available sheets  ########################################################################
 #   
 sub othersheets {  sub othersheets {
     my ($safeeval,$stype)=@_;      my ($sheet,$stype)=@_;
       $stype = $sheet->{'sheettype'} if (! defined($stype));
     #      #
     my $cnum=&getcnum($safeeval);      my $cnum  = $sheet->{'cnum'};
     my $cdom=&getcdom($safeeval);      my $cdom  = $sheet->{'cdom'};
     my $chome=&getchome($safeeval);      my $chome = $sheet->{'chome'};
     #      #
     my @alternatives=();      my @alternatives=();
     my %results=&Apache::lonnet::dump($stype.'_spreadsheets',$cdom,$cnum);      my %results=&Apache::lonnet::dump($stype.'_spreadsheets',$cdom,$cnum);
Line 1314  sub othersheets { Line 1752  sub othersheets {
     return @alternatives;       return @alternatives; 
 }  }
   
   
 #  #
 # -------------------------------------- Parse a spreadsheet  # -------------------------------------- Parse a spreadsheet
 #   # 
Line 1346  sub parse_sheet { Line 1783  sub parse_sheet {
 #  #
 # -------------------------------------- Read spreadsheet formulas for a course  # -------------------------------------- Read spreadsheet formulas for a course
 #  #
   
 sub readsheet {  sub readsheet {
   my ($safeeval,$fn)=@_;      my ($sheet,$fn)=@_;
   my $stype=&gettype($safeeval);      #
   my $cnum=&getcnum($safeeval);      my $stype = $sheet->{'sheettype'};
   my $cdom=&getcdom($safeeval);      my $cnum  = $sheet->{'cnum'};
   my $chome=&getchome($safeeval);      my $cdom  = $sheet->{'cdom'};
       my $chome = $sheet->{'chome'};
   if (! defined($fn)) {      #
       # There is no filename. Look for defaults in course and global, cache      if (! defined($fn)) {
       unless ($fn=$defaultsheets{$cnum.'_'.$cdom.'_'.$stype}) {          # There is no filename. Look for defaults in course and global, cache
           my %tmphash = &Apache::lonnet::get('environment',          unless ($fn=$defaultsheets{$cnum.'_'.$cdom.'_'.$stype}) {
                                              ['spreadsheet_default_'.$stype],              my %tmphash = &Apache::lonnet::get('environment',
                                              $cdom,$cnum);                                                 ['spreadsheet_default_'.$stype],
           my ($tmp) = keys(%tmphash);                                                 $cdom,$cnum);
           if ($tmp =~ /^(con_lost|error|no_such_host)/i) {              my ($tmp) = keys(%tmphash);
               $fn = 'default_'.$stype;              if ($tmp =~ /^(con_lost|error|no_such_host)/i) {
           } else {                  $fn = 'default_'.$stype;
               $fn = $tmphash{'spreadsheet_default_'.$stype};              } else {
           }                   $fn = $tmphash{'spreadsheet_default_'.$stype};
           unless (($fn) && ($fn!~/^error\:/)) {              } 
       $fn='default_'.$stype;              unless (($fn) && ($fn!~/^error\:/)) {
           }                  $fn='default_'.$stype;
           $defaultsheets{$cnum.'_'.$cdom.'_'.$stype}=$fn;               }
       }              $defaultsheets{$cnum.'_'.$cdom.'_'.$stype}=$fn; 
   }          }
       }
 # ---------------------------------------------------------- fn now has a value      # $fn now has a value
       $sheet->{'filename'} = $fn;
   &setfilename($safeeval,$fn);      # see if sheet is cached
       my $fstring='';
 # ------------------------------------------------------ see if sheet is cached      if ($fstring=$spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}) {
   my $fstring='';          my %tmp = split(/___;___/,$fstring);
   if ($fstring=$spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}) {          $sheet->{'f'} = \%tmp;
       &setformulas($safeeval,split(/\_\_\_\;\_\_\_/,$fstring));          &setformulas($sheet);
   } else {      } else {
           # Not cached, need to read
 # ---------------------------------------------------- Not cached, need to read          my %f=();
           if ($fn=~/^default\_/) {
      my %f=();              my $sheetxml='';
               my $fh;
      if ($fn=~/^default\_/) {              my $dfn=$fn;
          my $sheetxml='';              $dfn=~s/\_/\./g;
          my $fh;              if ($fh=Apache::File->new($includedir.'/'.$dfn)) {
          my $dfn=$fn;                  $sheetxml=join('',<$fh>);
          $dfn=~s/\_/\./g;              } else {
          if ($fh=Apache::File->new($includedir.'/'.$dfn)) {                  # $sheetxml='<field row="0" col="A">"Error"</field>';
              $sheetxml=join('',<$fh>);                  $sheetxml='<field row="0" col="A"></field>';
          } else {              }
              $sheetxml='<field row="0" col="A">"Error"</field>';              %f=%{&parse_sheet(\$sheetxml)};
          }          } elsif($fn=~/\/*\.spreadsheet$/) {
          %f=%{&parse_sheet(\$sheetxml)};              my $sheetxml=&Apache::lonnet::getfile
      } elsif($fn=~/\/*\.spreadsheet$/) {                  (&Apache::lonnet::filelocation('',$fn));
          my $sheetxml=&Apache::lonnet::getfile              if ($sheetxml == -1) {
              (&Apache::lonnet::filelocation('',$fn));                  $sheetxml='<field row="0" col="A">"Error loading spreadsheet '
          if ($sheetxml == -1) {                      .$fn.'"</field>';
              $sheetxml='<field row="0" col="A">"Error loading spreadsheet '              }
                  .$fn.'"</field>';              %f=%{&parse_sheet(\$sheetxml)};
          }          } else {
          %f=%{&parse_sheet(\$sheetxml)};              my %tmphash = &Apache::lonnet::dump($fn,$cdom,$cnum);
      } else {              my ($tmp) = keys(%tmphash);
          my $sheet='';              if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
          my %tmphash = &Apache::lonnet::dump($fn,$cdom,$cnum);                  foreach (keys(%tmphash)) {
          my ($tmp) = keys(%tmphash);                      $f{$_}=$tmphash{$_};
          unless ($tmp =~ /^(con_lost|error|no_such_host)/i) {                  }
              foreach (keys(%tmphash)) {              } else {
                  $f{$_}=$tmphash{$_};                  # Unable to grab the specified spreadsheet,
              }                  # so we get the default ones instead.
          }                  $fn = 'default_'.$stype;
      }                  $sheet->{'filename'} = $fn;
 # --------------------------------------------------------------- Cache and set                  my $dfn = $fn;
        $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f);                    $dfn =~ s/\_/\./g;
        &setformulas($safeeval,%f);                  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
           $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f);  
           $sheet->{'f'}=\%f;
           &setformulas($sheet);
     }      }
 }  }
   
 # -------------------------------------------------------- Make new spreadsheet  # -------------------------------------------------------- Make new spreadsheet
   
 sub makenewsheet {  sub makenewsheet {
     my ($uname,$udom,$stype,$usymb)=@_;      my ($uname,$udom,$stype,$usymb)=@_;
     my $safeeval=initsheet($stype);      my $sheet={};
     $safeeval->reval(      $sheet->{'uname'} = $uname;
        '$uname="'.$uname.      $sheet->{'udom'}  = $udom;
       '";$udom="'.$udom.      $sheet->{'sheettype'} = $stype;
       '";$uhome="'.&Apache::lonnet::homeserver($uname,$udom).      $sheet->{'usymb'} = $usymb;
       '";$sheettype="'.$stype.      $sheet->{'mapid'} = $ENV{'form.mapid'};
       '";$usymb="'.$usymb.      $sheet->{'resid'} = $ENV{'form.resid'};
       '";$csec="'.&Apache::lonnet::usection($udom,$uname,      $sheet->{'cid'}   = $ENV{'request.course.id'};
                                             $ENV{'request.course.id'}).      $sheet->{'csec'}  = $Section{$uname.':'.$udom};
       '";$cid="'.$ENV{'request.course.id'}.      $sheet->{'coursefilename'}   = $ENV{'request.course.fn'};
       '";$cfn="'.$ENV{'request.course.fn'}.      $sheet->{'cnum'}  = $ENV{'course.'.$ENV{'request.course.id'}.'.num'};
       '";$cnum="'.$ENV{'course.'.$ENV{'request.course.id'}.'.num'}.      $sheet->{'cdom'}  = $ENV{'course.'.$ENV{'request.course.id'}.'.domain'};
       '";$cdom="'.$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.      $sheet->{'chome'} = $ENV{'course.'.$ENV{'request.course.id'}.'.home'};
       '";$chome="'.$ENV{'course.'.$ENV{'request.course.id'}.'.home'}.'";');      $sheet->{'coursedesc'} = $ENV{'course.'.$ENV{'request.course.id'}.
     return $safeeval;                                        '.description'};
       $sheet->{'uhome'} = &Apache::lonnet::homeserver($uname,$udom);
       #
       #
       $sheet->{'f'} = {};
       $sheet->{'constants'} = {};
       $sheet->{'othersheets'} = [];
       $sheet->{'rowlabel'} = {};
       #
       #
       $sheet->{'safe'}=&initsheet($sheet->{'sheettype'});
       #
       # Place all the %$sheet items into the safe space except the safe space
       # itself
       my $initstring = '';
       foreach (qw/uname udom sheettype usymb cid csec coursefilename
                cnum cdom chome uhome/) {
           $initstring.= qq{\$$_="$sheet->{$_}";};
       }
       $sheet->{'safe'}->reval($initstring);
       return $sheet;
 }  }
   
 # ------------------------------------------------------------ Save spreadsheet  # ------------------------------------------------------------ Save spreadsheet
   
 sub writesheet {  sub writesheet {
   my ($safeeval,$makedef)=@_;      my ($sheet,$makedef)=@_;
   my $cid=&getcid($safeeval);      my $cid=$sheet->{'cid'};
   if (&Apache::lonnet::allowed('opa',$cid)) {      if (&Apache::lonnet::allowed('opa',$cid)) {
     my %f=&getformulas($safeeval);          my %f=&getformulas($sheet);
     my $stype=&gettype($safeeval);          my $stype= $sheet->{'sheettype'};
     my $cnum=&getcnum($safeeval);          my $cnum = $sheet->{'cnum'};
     my $cdom=&getcdom($safeeval);          my $cdom = $sheet->{'cdom'};
     my $chome=&getchome($safeeval);          my $chome= $sheet->{'chome'};
     my $fn=&getfilename($safeeval);          my $fn   = $sheet->{'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          foreach (keys(%f)) {
     my $sheetdata='';              delete($f{$_}) if ($f{$_} eq 'import');
     foreach (keys(%f)) {          }
      unless ($f{$_} eq 'import') {          my $reply = &Apache::lonnet::put($fn,\%f,$cdom,$cnum);
        $sheetdata.=&Apache::lonnet::escape($_).'='.          if ($reply eq 'ok') {
    &Apache::lonnet::escape($f{$_}).'&';              $reply = &Apache::lonnet::put($stype.'_spreadsheets',
      }                              {$fn => $ENV{'user.name'}.'@'.$ENV{'user.domain'}},
     }                                            $cdom,$cnum);
     $sheetdata=~s/\&$//;              if ($reply eq 'ok') {
     my $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'.$fn.':'.                  if ($makedef) { 
               $sheetdata,$chome);                      $reply = &Apache::lonnet::put('environment',
     if ($reply eq 'ok') {                                      {'spreadsheet_default_'.$stype => $fn },
           $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'.                                                    $cdom,$cnum);
               $stype.'_spreadsheets:'.                      if ($reply eq 'ok' && 
               &Apache::lonnet::escape($fn).'='.$ENV{'user.name'}.'@'.                          ($sheet->{'sheettype'} eq 'studentcalc' ||
                                                $ENV{'user.domain'},                           $sheet->{'sheettype'} eq 'assesscalc')) {
               $chome);                          # Expire the spreadsheets of the other students.
           if ($reply eq 'ok') {                          &Apache::lonnet::expirespread('','','studentcalc','');
               if ($makedef) {                       }
                 return &Apache::lonnet::reply('put:'.$cdom.':'.$cnum.                      return $reply;
                                 ':environment:spreadsheet_default_'.$stype.'='.                  } 
                                 &Apache::lonnet::escape($fn),                  return $reply;
                                 $chome);              } 
       } else {              return $reply;
   return $reply;          } 
          }          return $reply;
    } else {      }
        return $reply;      return 'unauthorized';
            }  
       } else {  
   return $reply;  
       }  
   }  
   return 'unauthorized';  
 }  }
   
 # ----------------------------------------------- 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 ($sheet) = @_;
     my $fn=$ENV{'user.name'}.'_'.      my $fn=$ENV{'user.name'}.'_'.
            $ENV{'user.domain'}.'_spreadsheet_'.&getusymb($safeeval).'_'.          $ENV{'user.domain'}.'_spreadsheet_'.$sheet->{'usymb'}.'_'.
            &getfilename($safeeval);             $sheet->{'filename'};
     $fn=~s/\W/\_/g;      $fn=~s/\W/\_/g;
     $fn=$tmpdir.$fn.'.tmp';      $fn=$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 = &getformulas($sheet);
           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 ($sheet,$nfield,$nform)=@_;
     my $fn=$ENV{'user.name'}.'_'.      my $fn=$ENV{'user.name'}.'_'.
            $ENV{'user.domain'}.'_spreadsheet_'.&getusymb($safeeval).'_'.             $ENV{'user.domain'}.'_spreadsheet_'.$sheet->{'usymb'}.'_'.
            &getfilename($safeeval);             $sheet->{'filename'};
     $fn=~s/\W/\_/g;      $fn=~s/\W/\_/g;
     $fn=$tmpdir.$fn.'.tmp';      $fn=$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;  
                 }  
             }  
         }          }
     }      }
   #            chomp($value);
   #            $fo{$name}=$value;
   #            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') {  #    } elsif ($nfield eq 'insertrow') {
         $countrows++;  #        $countrows++;
         my $newrow=substr('000000'.$countrows,-7);  #        my $newrow=substr('000000'.$countrows,-7);
         if ($nform eq 'top') {  #        if ($nform eq 'top') {
     $fo{'A'.$countrows}='--- '.$newrow;  #    $fo{'A'.$countrows}='--- '.$newrow;
         } else {  #        } else {
             $fo{'A'.$countrows}='~~~ '.$newrow;  #            $fo{'A'.$countrows}='~~~ '.$newrow;
         }  #        }
     } else {      } else {
        if ($nfield) { $fo{$nfield}=$nform; }         if ($nfield) { $fo{$nfield}=$nform; }
     }      }
     &setformulas($safeeval,%fo);      $sheet->{'f'}=\%fo;
       &setformulas($sheet);
 }  }
   
 # ================================================================== Parameters  ##################################################
 # -------------------------------------------- Figure out a cascading parameter  ##################################################
 #  
 # For this function to work  
 #  
 # * parmhash needs to be tied  
 # * courseopt and useropt need to be initialized for this user and course  
 #  
   
 sub parmval {  =pod
     my ($what,$safeeval)=@_;  
     my $cid=&getcid($safeeval);  
     my $csec=&getcsec($safeeval);  
     my $uname=&getuname($safeeval);  
     my $udom=&getudom($safeeval);  
     my $symb=&getusymb($safeeval);  
   
     unless ($symb) { return ''; }  
     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 ($uname) {   
   
        if ($useropt{$courselevelr}) { return $useropt{$courselevelr}; }  
   
        if ($useropt{$courselevelm}) { return $useropt{$courselevelm}; }  
   
        if ($useropt{$courselevel}) { return $useropt{$courselevel}; }  =item &parmval()
   
       }  Determine the value of a parameter.
   
 # --------------------------------------------------------- third, check course  Inputs: $what, the parameter needed, $sheet, the safe space
        
        if ($csec) {  
    
         if ($courseopt{$seclevelr}) { return $courseopt{$seclevelr}; }  
   
         if ($courseopt{$seclevelm}) { return $courseopt{$seclevelm}; }    Returns: The value of a parameter, or '' if none.
   
         if ($courseopt{$seclevel}) { return $courseopt{$seclevel}; }  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.
   
        if ($courseopt{$courselevelr}) { return $courseopt{$courselevelr}; }  =cut
   
        if ($courseopt{$courselevelm}) { return $courseopt{$courselevelm}; }  
   
        if ($courseopt{$courselevel}) { return $courseopt{$courselevel}; }  
   
 # ----------------------------------------------------- second, check map parms  
   
        my $thisparm=$parmhash{$symbparm};  
        if ($thisparm) { return $thisparm; }  
   
 # -------------------------------------------------------- first, check default  
   
        return &Apache::lonnet::metadata($fn,$rwhat.'.default');  ##################################################
           ##################################################
   sub parmval {
       my ($what,$sheet)=@_;
       my $symb  = $sheet->{'usymb'};
       unless ($symb) { return ''; }
       #
       my $cid   = $sheet->{'cid'};
       my $csec  = $sheet->{'csec'};
       my $uname = $sheet->{'uname'};
       my $udom  = $sheet->{'udom'};
       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));
       # first, check default
       return &Apache::lonnet::metadata($fn,$rwhat.'.default');
   }
   
   
   ##################################################################
   ##                  Row label formatting routines               ##
   ##################################################################
   sub format_html_rowlabel {
       my $sheet = shift;
       my $rowlabel = shift;
       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   = 'default' if (!defined($ufn) || $ufn eq '');
           $ufn   = &Apache::lonnet::unescape($ufn);
           $symb  = &Apache::lonnet::unescape($symb);
           $title = &Apache::lonnet::unescape($title);
           $result = '<a href="/adm/assesscalc?usymb='.$symb.
               '&uname='.$sheet->{'uname'}.'&udom='.$sheet->{'udom'}.
                   '&ufn='.$ufn.
                       '&mapid='.$mapid.'&resid='.$resid.'">'.$title.'</a>';
       } elsif ($type eq 'student') {
           my ($sname,$sdom,$fullname,$section,$id) = split(':',$labeldata);
           if ($fullname =~ /^\s*$/) {
               $fullname = $sname.'@'.$sdom;
           }
           $result ='<a href="/adm/studentcalc?uname='.$sname.
               '&udom='.$sdom.'">';
           $result.=$section.'&nbsp;'.$id."&nbsp;".$fullname.'</a>';
       } elsif ($type eq 'parameter') {
           $result = $labeldata;
       } else {
           $result = '<b><font size=+1>'.$rowlabel.'</font></b>';
       }
       return $result;
   }
   
   sub format_csv_rowlabel {
       my $sheet = shift;
       my $rowlabel = shift;
       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);
           $result = join('","',($sname,$sdom,$fullname,$section,$id));
       } elsif ($type eq 'parameter') {
           $labeldata =~ s/<br>/ /g;
           $result = $labeldata;
       } else {
           $result = $rowlabel;
       }
       return '"'.$result.'"';
   }
   
   sub format_excel_rowlabel {
       my $sheet = shift;
       my $rowlabel = shift;
       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;
 }  }
   
 # ---------------------------------------------- Update rows for course listing  # ---------------------------------------------- Update rows for course listing
   
 sub updateclasssheet {  sub updateclasssheet {
     my $safeeval=shift;      my ($sheet) = @_;
     my $cnum=&getcnum($safeeval);      my $cnum  =$sheet->{'cnum'};
     my $cdom=&getcdom($safeeval);      my $cdom  =$sheet->{'cdom'};
     my $cid=&getcid($safeeval);      my $cid   =$sheet->{'cid'};
     my $chome=&getchome($safeeval);      my $chome =$sheet->{'chome'};
       #
 # ---------------------------------------------- Read class list and row labels      %Section = ();
       #
     my $classlst=&Apache::lonnet::reply      # Read class list and row labels
                                  ('dump:'.$cdom.':'.$cnum.':classlist',$chome);      my $classlist = &Apache::loncoursedata::get_classlist();
       if (! defined($classlist)) {
           return 'Could not access course classlist';
       } 
       #
     my %currentlist=();      my %currentlist=();
     my $now=time;      foreach my $student (keys(%$classlist)) {
     unless ($classlst=~/^error\:/) {          my ($studentDomain,$studentName,$end,$start,$id,$studentSection,
         foreach (split(/\&/,$classlst)) {              $fullname,$status)   =   @{$classlist->{$student}};
             my ($name,$value)=split(/\=/,$_);          $Section{$studentName.':'.$studentDomain} = $studentSection;
             my ($end,$start)=split(/\:/,&Apache::lonnet::unescape($value));          if ($ENV{'form.Status'} eq $status || $ENV{'form.Status'} eq 'Any') {
             my $active=1;              $currentlist{$student}=join(':',('student',$studentName,
             if (($end) && ($now>$end)) { $active=0; }                                               $studentDomain,$fullname,
             $active = 1 if ($ENV{'form.Status'} eq 'Any');                                               $studentSection,$id));
             $active = !$active if ($ENV{'form.Status'} eq 'Expired');          }
             if ($active) {      }
                 my $rowlabel='';      #
                 $name=&Apache::lonnet::unescape($name);      # Find discrepancies between the course row table and this
                 my ($sname,$sdom)=split(/\:/,$name);      #
                 my $ssec=&Apache::lonnet::usection($sdom,$sname,$cid);      my %f=&getformulas($sheet);
 #                if ($ssec==-1) {      my $changed=0;
 #   unless ($ENV{'form.showcsv'}) {      #
 #                    $rowlabel='<font color=red>Data not available: '.$name.      $sheet->{'maxrow'}=0;
 #      '</font>';      my %existing=();
 #   } else {      #
 #       $rowlabel='ERROR","'.$name.      # Now obsolete rows
 #                                 '","Data not available","","","';      foreach (keys(%f)) {
 #                   }          if ($_=~/^A(\d+)/) {
 #                } else {              if ($1 > $sheet->{'maxrow'}) {
                     my %reply=&Apache::lonnet::idrget($sdom,$sname);                  $sheet->{'maxrow'}= $1;
                     my $reply=&Apache::lonnet::reply('get:'.$sdom.':'.$sname.  
       ':environment:firstname&middlename&lastname&generation',  
                       &Apache::lonnet::homeserver($sname,$sdom));  
    unless ($ENV{'form.showcsv'}) {  
                     $rowlabel='<a href="/adm/studentcalc?uname='.$sname.  
                               '&udom='.$sdom.'">'.  
                               $ssec.'&nbsp;'.$reply{$sname}.'<br>';  
                     foreach ( split(/\&/,$reply)) {  
                         $rowlabel.=&Apache::lonnet::unescape($_).' ';  
                     }  
                     $rowlabel.='</a>';  
    } else {  
     $rowlabel=$ssec.'","'.$reply{$sname}.'"';  
                     my $ncount=0;  
                     foreach (split(/\&/,$reply)) {  
                         $rowlabel.=',"'.&Apache::lonnet::unescape($_).'"';  
                         $ncount++;  
                     }  
                     unless ($ncount==4) { $rowlabel.=',""'; }  
                     $rowlabel=~s/\"$//;  
    }  
 #                }  
  $currentlist{&Apache::lonnet::unescape($name)}=$rowlabel;  
             }              }
         } # end of foreach (split(/\&/,$classlst))              $existing{$f{$_}}=1;
 #              unless ((defined($currentlist{$f{$_}})) || (!$1) ||
 # -------------------- Find discrepancies between the course row table and this                      ($f{$_}=~/^(~~~|---)/)) {
 #                  $f{$_}='!!! Obsolete';
         my %f=&getformulas($safeeval);                  $changed=1;
         my $changed=0;  
   
         my $maxrow=0;  
         my %existing=();  
   
 # ----------------------------------------------------------- Now obsolete rows  
  foreach (keys(%f)) {  
     if ($_=~/^A(\d+)/) {  
                 $maxrow=($1>$maxrow)?$1:$maxrow;  
                 $existing{$f{$_}}=1;  
  unless ((defined($currentlist{$f{$_}})) || (!$1) ||  
                         ($f{$_}=~/^(\~\~\~|\-\-\-)/)) {  
    $f{$_}='!!! Obsolete';  
                    $changed=1;  
                 }  
             }              }
         }          }
       }
 # -------------------------------------------------------- New and unknown keys      #
            # New and unknown keys
         foreach (sort keys(%currentlist)) {      foreach my $student (sort keys(%currentlist)) {
             unless ($existing{$_}) {          unless ($existing{$student}) {
  $changed=1;              $changed=1;
                 $maxrow++;              $sheet->{'maxrow'}++;
                 $f{'A'.$maxrow}=$_;              $f{'A'.$sheet->{'maxrow'}}=$student;
             }  
         }          }
        
         if ($changed) { &setformulas($safeeval,%f); }  
   
         &setmaxrow($safeeval,$maxrow);  
         &setrowlabels($safeeval,%currentlist);  
   
     } else {  
         return 'Could not access course data';  
     }      }
       if ($changed) { 
           $sheet->{'f'} = \%f;
           &setformulas($sheet,%f); 
       }
       #
       &setrowlabels($sheet,\%currentlist);
 }  }
   
 # ----------------------------------- Update rows for student and assess sheets  # ----------------------------------- Update rows for student and assess sheets
   sub get_student_rowlabels {
 sub updatestudentassesssheet {      my ($sheet) = @_;
     my $safeeval=shift;      #
     my %bighash;      my %course_db;
     my $stype=&gettype($safeeval);      #
     my $uname=&getuname($safeeval);      my $stype = $sheet->{'sheettype'};
     my $udom =&getudom($safeeval);      my $uname = $sheet->{'uname'};
     my %current=();      my $udom  = $sheet->{'udom'};
     unless ($updatedata{      #
         $ENV{'request.course.fn'}.'_'.$stype.'_'.$uname.'_'.$udom}) {      $sheet->{'rowlabel'} = {};
 # -------------------------------------------------------------------- Tie hash      #
       if (tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db',      my $identifier =$sheet->{'coursefilename'}.'_'.$stype;
                        &GDBM_READER(),0640)) {      if  ($rowlabel_cache{$identifier}) {
 # --------------------------------------------------------- Get all assessments          %{$sheet->{'rowlabel'}}=split(/___;___/,$rowlabel_cache{$identifier});
       } else {
  my %allkeys=('timestamp' =>           # Get the data and store it in the cache
                      'Timestamp of Last Transaction<br>timestamp',          # Tie hash
                      'subnumber' =>          tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db',
                      'Number of Submissions<br>subnumber',              &GDBM_READER(),0640);
                      'tutornumber' =>          if (! tied(%course_db)) {
                      'Number of Tutor Responses<br>tutornumber',              return 'Could not access course data';
                      'totalpoints' =>          }
                      'Total Points Granted<br>totalpoints');          #
           my %assesslist = ();
         my $adduserstr='';          foreach ('Feedback','Evaluation','Tutoring','Discussion') {
         if ((&getuname($safeeval) ne $ENV{'user.name'}) ||              my $symb = '_'.lc($_);
             (&getudom($safeeval) ne $ENV{'user.domain'})) {              $assesslist{$symb} = join(':',('symb',$symb,0,0,
             $adduserstr='&uname='.&getuname($safeeval).                                             &Apache::lonnet::escape($_)));
  '&udom='.&getudom($safeeval);          }
         }          #
           while (my ($key,$srcf) = each(%course_db)) {
         my %allassess=('_feedback' =>              next if ($key !~ /^src_(\d+)\.(\d+)$/);
               '<a href="/adm/assesscalc?usymb=_feedback'.$adduserstr.              my $mapid = $1;
                        '">Feedback</a>',              my $resid = $2;
                        '_evaluation' =>              my $id   = $mapid.'.'.$resid;
               '<a href="/adm/assesscalc?usymb=_evaluation'.$adduserstr.              if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) {
                        '">Evaluation</a>',                  my $symb=
                        '_tutoring' =>                      &Apache::lonnet::declutter($course_db{'map_id_'.$mapid}).
               '<a href="/adm/assesscalc?usymb=_tutoring'.$adduserstr.                          '___'.$resid.'___'.&Apache::lonnet::declutter($srcf);
                        '">Tutoring</a>',                  $assesslist{$symb} ='symb:'.&Apache::lonnet::escape($symb).':'
                        '_discussion' =>                      .$mapid.':'.$resid.':'.
               '<a href="/adm/assesscalc?usymb=_discussion'.$adduserstr.                          &Apache::lonnet::escape($course_db{'title_'.$id});
                        '">Discussion</a>'  
         );  
   
         foreach (keys(%bighash)) {  
     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)$/) {  
                  my $symb=  
                      &Apache::lonnet::declutter($bighash{'map_id_'.$mapid}).  
     '___'.$resid.'___'.  
     &Apache::lonnet::declutter($srcf);  
  $allassess{$symb}=  
             '<a href="/adm/assesscalc?usymb='.$symb.$adduserstr.'">'.  
                      $bighash{'title_'.$id}.'</a>';  
                  if ($stype eq 'assesscalc') {  
      foreach (split(/\,/,  
     &Apache::lonnet::metadata($srcf,'keys'))) {  
                        if (($_=~/^stores\_(.*)/) || ($_=~/^parameter\_(.*)/)) {  
   my $key=$_;  
                           my $display=  
       &Apache::lonnet::metadata($srcf,$key.'.display');  
                           unless ($display) {  
                               $display.=  
          &Apache::lonnet::metadata($srcf,$key.'.name');  
                           }  
                           $display.='<br>'.$key;  
                           $allkeys{$key}=$display;  
        }  
                    } # end of foreach  
          }  
       }  
    }  
         } # end of foreach (keys(%bighash))  
         untie(%bighash);  
       
 #  
 # %allkeys has a list of storage and parameter displays by unikey  
 # %allassess has a list of all resource displays by symb  
 #  
   
         if ($stype eq 'assesscalc') {  
     %current=%allkeys;  
         } elsif ($stype eq 'studentcalc') {  
             %current=%allassess;  
         }  
         $updatedata{$ENV{'request.course.fn'}.'_'.$stype.'_'.$uname.'_'.$udom}=  
     join('___;___',%current);  
     } else {  
         return 'Could not access course data';  
     }  
 # ------------------------------------------------------ Get current from cache  
     } else {  
         %current=split(/\_\_\_\;\_\_\_/,  
      $updatedata{$ENV{'request.course.fn'}.'_'.$stype.'_'.$uname.'_'.$udom});  
     }  
 # -------------------- Find discrepancies between the course row table and this  
 #  
         my %f=&getformulas($safeeval);  
         my $changed=0;  
   
         my $maxrow=0;  
         my %existing=();  
   
 # ----------------------------------------------------------- Now obsolete rows  
  foreach (keys(%f)) {  
     if ($_=~/^A(\d+)/) {  
                 $maxrow=($1>$maxrow)?$1:$maxrow;  
                 my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{$_});  
                 $existing{$usy}=1;  
  unless ((defined($current{$usy})) || (!$1) ||  
                         ($f{$_}=~/^(\~\~\~|\-\-\-)/)){  
     $f{$_}='!!! Obsolete';  
                    $changed=1;  
         } elsif ($ufn) {  
     $current{$usy}  
                        =~s/assesscalc\?usymb\=/assesscalc\?ufn\=$ufn\&usymb\=/;  
                 }  
             }              }
         }          }
           untie(%course_db);
           # Store away the data
           $sheet->{'rowlabel'} = \%assesslist;
           $rowlabel_cache{$identifier}=join('___;___',%{$sheet->{'rowlabel'}});
       }
   
   }
   
 # -------------------------------------------------------- New and unknown keys  sub get_assess_rowlabels {
            my ($sheet) = @_;
         foreach (keys(%current)) {      #
             unless ($existing{$_}) {      my %course_db;
  $changed=1;      #
                 $maxrow++;      my $stype = $sheet->{'sheettype'};
                 $f{'A'.$maxrow}=$_;      my $uname = $sheet->{'uname'};
       my $udom  = $sheet->{'udom'};
       my $usymb = $sheet->{'usymb'};
       #
       $sheet->{'rowlabel'} = {};
       my $identifier =$sheet->{'coursefilename'}.'_'.$stype.'_'.$usymb;
       #
       if  ($rowlabel_cache{$identifier}) {
           %{$sheet->{'rowlabel'}}=split(/___;___/,$rowlabel_cache{$identifier});
       } else {
           # Get the data and store it in the cache
           # Tie hash
           tie(%course_db,'GDBM_File',$sheet->{'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)_/);
                       my $display=
                           &Apache::lonnet::metadata($srcf,$key.'.display');
                       unless ($display) {
                           $display.=
                               &Apache::lonnet::metadata($srcf,$key.'.name');
                       }
                       $display.='<br>'.$key;
                       $parameter_labels{$key}='parameter:'.$display;
                   } # end of foreach
             }              }
         }          }
               untie(%course_db);
         if ($changed) { &setformulas($safeeval,%f); }          # Store away the results
           $sheet->{'rowlabel'} = \%parameter_labels;
           $rowlabel_cache{$identifier}=join('___;___',%{$sheet->{'rowlabel'}});
       }
           
   }
   
         &setmaxrow($safeeval,$maxrow);  sub updatestudentassesssheet {
         &setrowlabels($safeeval,%current);      my $sheet = shift;
        if ($sheet->{'sheettype'} eq 'studentcalc') {
         undef %current;          &get_student_rowlabels($sheet);
         undef %existing;      } else {
           &get_assess_rowlabels($sheet);
       }
       # Determine if any of the information has changed
       my %f=&getformulas($sheet);
       my $changed=0;
       
       $sheet->{'maxrow'} = 0;
       my %existing=();
       # Now obsolete rows
       foreach my $cell (keys(%f)) {
           my $formula = $f{$cell};
           next if ($cell !~ /^A(\d+)/);
           $sheet->{'maxrow'} = $1 if ($1 > $sheet->{'maxrow'});
           my ($usy,$ufn)=split(/__&&&\__/,$formula);
           $existing{$usy}=1;
           unless ((exists($sheet->{'rowlabel'}->{$usy}) && 
                    (defined($sheet->{'rowlabel'}->{$usy})) || (!$1) ||
                    ($formula =~ /^(~~~|---)/) )) {
               $f{$cell}='!!! Obsolete';
               $changed=1;
           }
       }
       # New and unknown keys
       foreach (keys(%{$sheet->{'rowlabel'}})) {
           unless ($existing{$_}) {
               $changed=1;
               $sheet->{'maxrow'}++;
               $f{'A'.$sheet->{'maxrow'}}=$_;
           }
       }
       if ($changed) { 
           $sheet->{'f'} = \%f;
           &setformulas($sheet); 
       }
 }  }
   
 # ------------------------------------------------ Load data for one assessment  # ------------------------------------------------ Load data for one assessment
   
 sub loadstudent {  sub loadstudent{
     my $safeeval=shift;      my ($sheet,$r,$c)=@_;
     my %c=();      my %constants=();
     my %f=&getformulas($safeeval);      my %formulas=&getformulas($sheet);
     $cachedassess=&getuname($safeeval).':'.&getudom($safeeval);      $cachedassess=$sheet->{'uname'}.':'.$sheet->{'udom'};
     %cachedstores=();      # Get ALL the student preformance data
     {      my @tmp = &Apache::lonnet::dumpcurrent($sheet->{'cid'},
       my $reply=&Apache::lonnet::reply('dump:'.&getudom($safeeval).':'.  #    my @tmp = &Apache::lonnet::dump($sheet->{'cid'},
                                                &getuname($safeeval).':'.                                             $sheet->{'udom'},
                                                &getcid($safeeval),                                             $sheet->{'uname'},
                                                &getuhome($safeeval));                                             undef);
       unless ($reply=~/^error\:/) {      if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
  foreach ( split(/\&/,$reply)) {          %cachedstores = @tmp;
             my ($name,$value)=split(/\=/,$_);  #        &Apache::lonnet::logthis("-------------------------------------");
             $cachedstores{&Apache::lonnet::unescape($name)}=  #        foreach (keys(%cachedstores)) {
                   &Apache::lonnet::unescape($value);  #            &Apache::lonnet::logthis("data for ".$_);
  }  #        }
       }  #        &Apache::lonnet::logthis("-------------------------------------");
     }      }
       undef @tmp;
       # 
     my @assessdata=();      my @assessdata=();
     foreach (keys(%f)) {      foreach my $cell (keys(%formulas)) {
  if ($_=~/^A(\d+)/) {          my $value = $formulas{$cell};
    my $row=$1;          if(defined($c) && ($c->aborted())) {
            unless (($f{$_}=~/^[\!\~\-]/) || ($row==0)) {              last;
       my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{$_});          }
       @assessdata=&exportsheet(&getuname($safeeval),   next if ($cell !~ /^A(\d+)/);
                                        &getudom($safeeval),          my $row=$1;
                                        'assesscalc',$usy,$ufn);          next if (($value =~ /^[!~-]/) || ($row==0));
               my $index=0;          my ($usy,$ufn)=split(/__&&&\__/,$value);
               foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',          @assessdata=&exportsheet($sheet,$sheet->{'uname'},
                'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') {                                   $sheet->{'udom'},
                   if ($assessdata[$index]) {                                   'assesscalc',$usy,$ufn,$r);
      my $col=$_;          my $index=0;
      if ($assessdata[$index]=~/\D/) {          foreach my $col ('A','B','C','D','E','F','G','H','I','J','K','L','M',
                          $c{$col.$row}="'".$assessdata[$index]."'";                           'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') {
       } else {              if (defined($assessdata[$index])) {
          $c{$col.$row}=$assessdata[$index];                  if ($assessdata[$index]=~/\D/) {
      }                      $constants{$col.$row}="'".$assessdata[$index]."'";
                      unless ($col eq 'A') {                   } else {
  $f{$col.$row}='import';                      $constants{$col.$row}=$assessdata[$index];
                      }                  }
   }                  $formulas{$col.$row}='import' if ($col ne 'A');
                   $index++;              }
               }              $index++;
    }  
         }          }
     }      }
     $cachedassess='';      $cachedassess='';
     undef %cachedstores;      undef %cachedstores;
     &setformulas($safeeval,%f);      $sheet->{'f'} = \%formulas;
     &setconstants($safeeval,%c);      &setformulas($sheet);
       &setconstants($sheet,\%constants);
 }  }
   
 # --------------------------------------------------- Load data for one student  # --------------------------------------------------- Load data for one student
   #
 sub loadcourse {  sub loadcourse {
     my ($safeeval,$r)=@_;      my ($sheet,$r,$c)=@_;
     my %c=();      #
     my %f=&getformulas($safeeval);      my %constants=();
       my %formulas=&getformulas($sheet);
       #
     my $total=0;      my $total=0;
     foreach (keys(%f)) {      foreach (keys(%formulas)) {
  if ($_=~/^A(\d+)/) {   if ($_=~/^A(\d+)/) {
     unless ($f{$_}=~/^[\!\~\-]/) { $total++; }      unless ($formulas{$_}=~/^[\!\~\-]/) { $total++; }
         }          }
     }      }
     my $now=0;      my $now=0;
Line 1975  sub loadcourse { Line 2494  sub loadcourse {
 </script>  </script>
 ENDPOP  ENDPOP
     $r->rflush();      $r->rflush();
     foreach (keys(%f)) {      foreach (keys(%formulas)) {
  if ($_=~/^A(\d+)/) {          if(defined($c) && ($c->aborted())) {
    my $row=$1;              last;
            unless (($f{$_}=~/^[\!\~\-]/)  || ($row==0)) {          }
       my @studentdata=&exportsheet(split(/\:/,$f{$_}),   next if ($_!~/^A(\d+)/);
                                            'studentcalc');          my $row=$1;
               undef %userrdatas;          next if (($formulas{$_}=~/^[\!\~\-]/)  || ($row==0));
               $now++;          my ($sname,$sdom) = split(':',$formulas{$_});
               $r->print('<script>popwin.document.popremain.remaining.value="'.          my @studentdata=&exportsheet($sheet,$sname,$sdom,'studentcalc',
                                        undef,undef,$r);
           undef %userrdatas;
           $now++;
           $r->print('<script>popwin.document.popremain.remaining.value="'.
                   $now.'/'.$total.': '.int((time-$since)/$now*($total-$now)).                    $now.'/'.$total.': '.int((time-$since)/$now*($total-$now)).
                         ' secs remaining";</script>');                    ' secs remaining";</script>');
               $r->rflush();           $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);      $sheet->{'f'}=\%formulas;
     &setconstants($safeeval,%c);      &setformulas($sheet);
       &setconstants($sheet,\%constants);
     $r->print('<script>popwin.close()</script>');      $r->print('<script>popwin.close()</script>');
     $r->rflush();       $r->rflush(); 
 }  }
   
 # ------------------------------------------------ Load data for one assessment  # ------------------------------------------------ Load data for one assessment
   #
 sub loadassessment {  sub loadassessment {
     my $safeeval=shift;      my ($sheet,$r,$c)=@_;
   
     my $uhome=&getuhome($safeeval);      my $uhome = $sheet->{'uhome'};
     my $uname=&getuname($safeeval);      my $uname = $sheet->{'uname'};
     my $udom=&getudom($safeeval);      my $udom  = $sheet->{'udom'};
     my $symb=&getusymb($safeeval);      my $symb  = $sheet->{'usymb'};
     my $cid=&getcid($safeeval);      my $cid   = $sheet->{'cid'};
     my $cnum=&getcnum($safeeval);      my $cnum  = $sheet->{'cnum'};
     my $cdom=&getcdom($safeeval);      my $cdom  = $sheet->{'cdom'};
     my $chome=&getchome($safeeval);      my $chome = $sheet->{'chome'};
   
     my $namespace;      my $namespace;
     unless ($namespace=$cid) { return ''; }      unless ($namespace=$cid) { return ''; }
       # Get stored values
 # ----------------------------------------------------------- Get stored values      my %returnhash=();
       if ($cachedassess eq $uname.':'.$udom) {
    my %returnhash=();          #
           # get data out of the dumped stores
    if ($cachedassess eq $uname.':'.$udom) {          # 
 #  #        my $version=$cachedstores{'version:'.$symb};
 # get data out of the dumped stores  #        my $scope;
 #   #        for ($scope=1;$scope<=$version;$scope++) {
   #            foreach (split(/\:/,$cachedstores{$scope.':keys:'.$symb})) {
        my $version=$cachedstores{'version:'.$symb};  #                $returnhash{$_}=$cachedstores{$scope.':'.$symb.':'.$_};
        my $scope;  #            } 
        for ($scope=1;$scope<=$version;$scope++) {  #        }
            foreach (split(/\:/,$cachedstores{$scope.':keys:'.$symb})) {          if (exists($cachedstores{$symb})) {
                $returnhash{$_}=$cachedstores{$scope.':'.$symb.':'.$_};              %returnhash = %{$cachedstores{$symb}};
            }           } else {
        }  #            &Apache::lonnet::logthis("No data for ".$symb);
               %returnhash = ();
    } 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;
     }      }
       # initialize coursedata and userdata for this user
 # ---------------------------- initialize coursedata and userdata for this user  
     undef %courseopt;      undef %courseopt;
     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          unless ((time-$courserdatas{$cid.'.last_cache'})<240) {
         ((time-$courserdatas{$cid.'.last_cache'})<240) {              my %Tmp = &Apache::lonnet::dump('resourcedata',$cdom,$cnum);
          my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.              $courserdatas{$cid}=\%Tmp;
               ':resourcedata',$chome);  
          if ($reply!~/^error\:/) {  
             $courserdatas{$cid}=$reply;  
             $courserdatas{$cid.'.last_cache'}=time;              $courserdatas{$cid.'.last_cache'}=time;
          }          }
       }          while (my ($name,$value) = each(%{$courserdatas{$cid}})) {
       foreach (split(/\&/,$courserdatas{$cid})) {              $courseopt{$userprefix.$name}=$value;
          my ($name,$value)=split(/\=/,$_);          }
          $courseopt{$userprefix.&Apache::lonnet::unescape($name)}=          # Get userdata (if present)
                     &Apache::lonnet::unescape($value);            unless ((time-$userrdatas{$uname.'@'.$udom.'.last_cache'})<240) {
       }              my %Tmp = &Apache::lonnet::dump('resourcedata',$udom,$uname);
 # --------------------------------------------------- Get userdata (if present)              $userrdatas{$cid} = \%Tmp;
       unless              # Most of the time the user does not have a 'resourcedata.db' 
         ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) {              # file.  We need to cache that we got nothing instead of bothering
          my $reply=              # with requesting it every time.
        &Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome);              $userrdatas{$uname.'@'.$udom.'.last_cache'}=time;
          if ($reply!~/^error\:/) {          }
      $userrdatas{$uname.'___'.$udom}=$reply;          while (my ($name,$value) = each(%{$userrdatas{$cid}})) {
      $userrdatas{$uname.'___'.$udom.'.last_cache'}=time;              $useropt{$userprefix.$name}=$value;
          }          }
       }  
       foreach (split(/\&/,$userrdatas{$uname.'___'.$udom})) {  
          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
 # (used by parmval)      # (used by parmval)
       #
 #      # Load keys for this assessment only
 # Load keys for this assessment only      #
 #  
     my %thisassess=();      my %thisassess=();
     my ($symap,$syid,$srcf)=split(/\_\_\_/,$symb);      my ($symap,$syid,$srcf)=split(/\_\_\_/,$symb);
       
     foreach (split(/\,/,&Apache::lonnet::metadata($srcf,'keys'))) {      foreach (split(/\,/,&Apache::lonnet::metadata($srcf,'keys'))) {
         $thisassess{$_}=1;          $thisassess{$_}=1;
     }       } 
 #      #
 # Load parameters      # Load parameters
 #      #
    my %c=();      my %c=();
       if (tie(%parmhash,'GDBM_File',
    if (tie(%parmhash,'GDBM_File',              $sheet->{'coursefilename'}.'_parms.db',&GDBM_READER(),0640)) {
            &getcfn($safeeval).'_parms.db',&GDBM_READER(),0640)) {          my %f=&getformulas($sheet);
     my %f=&getformulas($safeeval);          foreach my $cell (keys(%f))  {
     foreach (keys(%f))  {              next if ($cell !~ /^A/);
  if ($_=~/^A/) {              next if  ($f{$cell} =~/^[\!\~\-]/);
             unless ($f{$_}=~/^[\!\~\-]/) {              if ($f{$cell}=~/^parameter/) {
         if ($f{$_}=~/^parameter/) {                  if (defined($thisassess{$f{$cell}})) {
  if ($thisassess{$f{$_}}) {                      my $val       = &parmval($f{$cell},$sheet);
                   my $val=&parmval($f{$_},$safeeval);                      $c{$cell}     = $val;
                   $c{$_}=$val;                      $c{$f{$cell}} = $val;
                   $c{$f{$_}}=$val;                  }
         }              } else {
        } else {                  my $key=$f{$cell};
   my $key=$f{$_};                  my $ckey=$key;
                   my $ckey=$key;                  $key=~s/^stores\_/resource\./;
                   $key=~s/^stores\_/resource\./;                  $key=~s/\_/\./g;
                   $key=~s/\_/\./g;                  $c{$cell}=$returnhash{$key};
            $c{$_}=$returnhash{$key};                  $c{$ckey}=$returnhash{$key};
                   $c{$ckey}=$returnhash{$key};              }
        }  
    }  
         }          }
           untie(%parmhash);
     }      }
     untie(%parmhash);      &setconstants($sheet,\%c);
    }  
    &setconstants($safeeval,%c);  
 }  }
   
 # --------------------------------------------------------- Various form fields  # --------------------------------------------------------- Various form fields
Line 2170  sub loadassessment { Line 2663  sub loadassessment {
 sub textfield {  sub textfield {
     my ($title,$name,$value)=@_;      my ($title,$name,$value)=@_;
     return "\n<p><b>$title:</b><br>".      return "\n<p><b>$title:</b><br>".
            '<input type=text name="'.$name.'" size=80 value="'.$value.'">';          '<input type=text name="'.$name.'" size=80 value="'.$value.'">';
 }  }
   
 sub hiddenfield {  sub hiddenfield {
Line 2195  sub selectbox { Line 2688  sub selectbox {
 #  #
   
 sub updatesheet {  sub updatesheet {
     my $safeeval=shift;      my ($sheet)=@_;
     my $stype=&gettype($safeeval);      if ($sheet->{'sheettype'} eq 'classcalc') {
     if ($stype eq 'classcalc') {   return &updateclasssheet($sheet);
  return &updateclasssheet($safeeval);  
     } else {      } else {
         return &updatestudentassesssheet($safeeval);          return &updatestudentassesssheet($sheet);
     }      }
 }  }
   
Line 2210  sub updatesheet { Line 2702  sub updatesheet {
 #  #
   
 sub loadrows {  sub loadrows {
     my ($safeeval,$r)=@_;      my ($sheet,$r)=@_;
     my $stype=&gettype($safeeval);      my $c = $r->connection;
       my $stype=$sheet->{'sheettype'};
     if ($stype eq 'classcalc') {      if ($stype eq 'classcalc') {
  &loadcourse($safeeval,$r);   &loadcourse($sheet,$r,$c);
     } elsif ($stype eq 'studentcalc') {      } elsif ($stype eq 'studentcalc') {
         &loadstudent($safeeval);          &loadstudent($sheet,$r,$c);
     } else {      } else {
         &loadassessment($safeeval);          &loadassessment($sheet,$r,$c);
     }      }
 }  }
   
Line 2225  sub loadrows { Line 2718  sub loadrows {
   
 sub checkthis {  sub checkthis {
     my ($keyname,$time)=@_;      my ($keyname,$time)=@_;
     return ($time<$expiredates{$keyname});      if (! exists($expiredates{$keyname})) {
           return 0;
       } else {
           return ($time<$expiredates{$keyname});
       }
 }  }
   
 sub forcedrecalc {  sub forcedrecalc {
     my ($uname,$udom,$stype,$usymb)=@_;      my ($uname,$udom,$stype,$usymb)=@_;
     my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;      my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;
Line 2234  sub forcedrecalc { Line 2732  sub forcedrecalc {
     if ($ENV{'form.forcerecalc'}) { return 1; }      if ($ENV{'form.forcerecalc'}) { return 1; }
     unless ($time) { return 1; }      unless ($time) { return 1; }
     if ($stype eq 'assesscalc') {      if ($stype eq 'assesscalc') {
         my $map=(split(/\_\_\_/,$usymb))[0];          my $map=(split(/___/,$usymb))[0];
         if (&checkthis('::assesscalc:',$time) ||          if (&checkthis('::assesscalc:',$time) ||
             &checkthis('::assesscalc:'.$map,$time) ||              &checkthis('::assesscalc:'.$map,$time) ||
             &checkthis('::assesscalc:'.$usymb,$time) ||              &checkthis('::assesscalc:'.$usymb,$time) ||
Line 2242  sub forcedrecalc { Line 2740  sub forcedrecalc {
             &checkthis($uname.':'.$udom.':assesscalc:'.$map,$time) ||              &checkthis($uname.':'.$udom.':assesscalc:'.$map,$time) ||
             &checkthis($uname.':'.$udom.':assesscalc:'.$usymb,$time)) {              &checkthis($uname.':'.$udom.':assesscalc:'.$usymb,$time)) {
             return 1;              return 1;
         }           }
     } else {      } else {
         if (&checkthis('::studentcalc:',$time) ||           if (&checkthis('::studentcalc:',$time) || 
             &checkthis($uname.':'.$udom.':studentcalc:',$time)) {              &checkthis($uname.':'.$udom.':studentcalc:',$time)) {
Line 2253  sub forcedrecalc { Line 2751  sub forcedrecalc {
 }  }
   
 # ============================================================== 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 ($sheet,$uname,$udom,$stype,$usymb,$fn,$r)=@_;
  my @exportarr=();      my $flag = 0;
       $uname = $uname || $sheet->{'uname'};
  if (($usymb=~/^\_(\w+)/) && (!$fn)) {      $udom  = $udom  || $sheet->{'udom'};
     $fn='default_'.$1;      $stype = $stype || $sheet->{'sheettype'};
  }      my @exportarr=();
       # This handles the assessment sheets for '_feedback', etc
 #      if (defined($usymb) && ($usymb=~/^\_(\w+)/) && 
 # Check if cached          (!defined($fn) || $fn eq '')) {
 #          $fn='default_'.$1;
       }
  my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;      #
  my $found='';      # Check if cached
       #
  if ($oldsheets{$key}) {      my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;
      foreach (split(/\_\_\_\&\_\_\_/,$oldsheets{$key})) {      my $found='';
          my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);      if ($oldsheets{$key}) {
          if ($name eq $fn) {          foreach (split(/___&\___/,$oldsheets{$key})) {
      $found=$value;              my ($name,$value)=split(/___=___/,$_);
          }  
      }  
  }  
   
  unless ($found) {  
      &cachedssheets($uname,$udom,&Apache::lonnet::homeserver($uname,$udom));  
      if ($oldsheets{$key}) {  
  foreach (split(/\_\_\_\&\_\_\_/,$oldsheets{$key})) {  
             my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);  
             if ($name eq $fn) {              if ($name eq $fn) {
         $found=$value;                  $found=$value;
             }              }
         }           }
      }      }
  }      unless ($found) {
 #          &cachedssheets($sheet,$uname,$udom);
 # Check if still valid          if ($oldsheets{$key}) {
 #              foreach (split(/___&\___/,$oldsheets{$key})) {
  if ($found) {                  my ($name,$value)=split(/___=___/,$_);
      if (&forcedrecalc($uname,$udom,$stype,$usymb)) {                  if ($name eq $fn) {
  $found='';                      $found=$value;
      }                  }
  }              } 
            }
  if ($found) {  
 #  
 # Return what was cached  
 #  
      @exportarr=split(/\_\_\_\;\_\_\_/,$found);  
   
  } else {  
 #  
 # Not cached  
 #          
   
     my $thissheet=&makenewsheet($uname,$udom,$stype,$usymb);  
     &readsheet($thissheet,$fn);  
     &updatesheet($thissheet);  
     &loadrows($thissheet);  
     &calcsheet($thissheet);   
     @exportarr=&exportdata($thissheet);  
 #  
 # 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:'.  
                                      &getudom($thissheet).':'.  
                                      &getuname($thissheet).  
      ':nohist_calculatedsheets_'.  
                                      $ENV{'request.course.id'}.':'.  
                                      &Apache::lonnet::escape($key),  
                                      &getuhome($thissheet));  
   
     }      }
       #
       # Check if still valid
       #
       if ($found) {
           if (&forcedrecalc($uname,$udom,$stype,$usymb)) {
               $found='';
           }
       }
       if ($found) {
           #
           # Return what was cached
           #
           @exportarr=split(/___;___/,$found);
           return @exportarr;
       }
       #
       # Not cached
       #
       my ($newsheet)=&makenewsheet($uname,$udom,$stype,$usymb);
       &readsheet($newsheet,$fn);
       &updatesheet($newsheet);
       &loadrows($newsheet,$r);
       &calcsheet($newsheet); 
       @exportarr=&exportdata($newsheet);
       ##
       ## Store now
       ##
       #
       # load in the old value
       #
     my %currentlystored=();      my %currentlystored=();
     unless ($current=~/^error\:/) {      if ($stype eq 'studentcalc') {
        foreach (split(/\_\_\_\&\_\_\_/,&Apache::lonnet::unescape($current))) {          my @tmp = &Apache::lonnet::get('nohist_calculatedsheets',
            my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);                                         [$key],
            $currentlystored{$name}=$value;                                         $sheet->{'cdom'},$sheet->{'cnum'});
        }          if ($tmp[0]!~/^error/) {
               # We only got one key, so we will access it directly.
               foreach (split('___&___',$tmp[1])) {
                   my ($key,$value) = split('___=___',$_);
                   $key = '' if (! defined($key));
                   $currentlystored{$key} = $value;
               }
           }
       } else {
           my @tmp = &Apache::lonnet::get('nohist_calculatedsheets_'.
                                          $sheet->{'cid'},[$key],
                                          $sheet->{'udom'},$sheet->{'uname'});
           if ($tmp[0]!~/^error/) {
               # We only got one key, so we will access it directly.
               foreach (split('___&___',$tmp[1])) {
                   my ($key,$value) = split('___=___',$_);
                   $key = '' if (! defined($key));
                   $currentlystored{$key} = $value;
               }
           }
     }      }
       #
       # Add the new line
       #
     $currentlystored{$fn}=join('___;___',@exportarr);      $currentlystored{$fn}=join('___;___',@exportarr);
       #
       # Stick everything back together
       #
     my $newstore='';      my $newstore='';
     foreach (keys(%currentlystored)) {      foreach (keys(%currentlystored)) {
         if ($newstore) { $newstore.='___&___'; }          if ($newstore) { $newstore.='___&___'; }
         $newstore.=$_.'___=___'.$currentlystored{$_};          $newstore.=$_.'___=___'.$currentlystored{$_};
     }      }
     my $now=time;      my $now=time;
       #
       # Store away the new value
       #
       my $timekey = $key.'.time';
     if ($stype eq 'studentcalc') {      if ($stype eq 'studentcalc') {
        &Apache::lonnet::reply('put:'.          my $result = &Apache::lonnet::put('nohist_calculatedsheets',
                          $ENV{'course.'.$cid.'.domain'}.':'.                                            { $key     => $newstore,
                          $ENV{'course.'.$cid.'.num'}.                                              $timekey => $now },
  ':nohist_calculatedsheets:'.                                            $sheet->{'cdom'},
                          &Apache::lonnet::escape($key).'='.                                            $sheet->{'cnum'});
  &Apache::lonnet::escape($newstore).'&'.      } else {
                          &Apache::lonnet::escape($key).'.time='.$now,          my $result = &Apache::lonnet::put('nohist_calculatedsheets_'.$sheet->{'cid'},
                          $ENV{'course.'.$cid.'.home'});                                            { $key     => $newstore,
    } else {                                              $timekey => $now },
        &Apache::lonnet::reply('put:'.                                            $sheet->{'udom'},
                          &getudom($thissheet).':'.                                            $sheet->{'uname'});
                          &getuname($thissheet).      }
  ':nohist_calculatedsheets_'.      return @exportarr;
                          $ENV{'request.course.id'}.':'.  
                          &Apache::lonnet::escape($key).'='.  
  &Apache::lonnet::escape($newstore).'&'.  
                          &Apache::lonnet::escape($key).'.time='.$now,  
                          &getuhome($thissheet));  
    }  
  }  
  return @exportarr;  
 }  }
   
 # ============================================================ Expiration Dates  # ============================================================ Expiration Dates
 #  #
 # Load previously cached student spreadsheets for this course  # Load previously cached student spreadsheets for this course
 #  #
   sub load_spreadsheet_expirationdates {
 sub expirationdates {  
     undef %expiredates;      undef %expiredates;
     my $cid=$ENV{'request.course.id'};      my $cid=$ENV{'request.course.id'};
     my $reply=&Apache::lonnet::reply('dump:'.      my @tmp = &Apache::lonnet::dump('nohist_expirationdates',
      $ENV{'course.'.$cid.'.domain'}.':'.                                      $ENV{'course.'.$cid.'.domain'},
                                      $ENV{'course.'.$cid.'.num'}.                                      $ENV{'course.'.$cid.'.num'});
      ':nohist_expirationdates',      if (lc($tmp[0]) !~ /^error/){
                                      $ENV{'course.'.$cid.'.home'});          %expiredates = @tmp;
     unless ($reply=~/^error\:/) {  
  foreach (split(/\&/,$reply)) {  
             my ($name,$value)=split(/\=/,$_);  
             $expiredates{&Apache::lonnet::unescape($name)}  
                         =&Apache::lonnet::unescape($value);  
         }  
     }      }
 }  }
   
Line 2407  sub expirationdates { Line 2901  sub expirationdates {
   
 sub cachedcsheets {  sub cachedcsheets {
     my $cid=$ENV{'request.course.id'};      my $cid=$ENV{'request.course.id'};
     my $reply=&Apache::lonnet::reply('dump:'.      my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets',
      $ENV{'course.'.$cid.'.domain'}.':'.                                      $ENV{'course.'.$cid.'.domain'},
                                      $ENV{'course.'.$cid.'.num'}.                                      $ENV{'course.'.$cid.'.num'});
      ':nohist_calculatedsheets',      if ($tmp[0] !~ /^error/) {
                                      $ENV{'course.'.$cid.'.home'});          my %StupidTempHash = @tmp;
     unless ($reply=~/^error\:/) {          while (my ($key,$value) = each %StupidTempHash) {
  foreach ( split(/\&/,$reply)) {              $oldsheets{$key} = $value;
             my ($name,$value)=split(/\=/,$_);  
             $oldsheets{&Apache::lonnet::unescape($name)}  
                       =&Apache::lonnet::unescape($value);  
         }          }
     }      }
 }  }
Line 2427  sub cachedcsheets { Line 2918  sub cachedcsheets {
 #  #
   
 sub cachedssheets {  sub cachedssheets {
   my ($sname,$sdom,$shome)=@_;      my ($sheet,$uname,$udom) = @_;
   unless (($loadedcaches{$sname.'_'.$sdom}) || ($shome eq 'no_host')) {      $uname = $uname || $sheet->{'uname'};
     my $cid=$ENV{'request.course.id'};      $udom  = $udom  || $sheet->{'udom'};
     my $reply=&Apache::lonnet::reply('dump:'.$sdom.':'.$sname.      if (! $loadedcaches{$uname.'_'.$udom}) {
              ':nohist_calculatedsheets_'.          my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets_'.
                                       $ENV{'request.course.id'},                                          $ENV{'request.course.id'},
                                      $shome);                                          $sheet->{'udom'},
     unless ($reply=~/^error\:/) {                                          $sheet->{'uname'});
  foreach ( split(/\&/,$reply)) {          if ($tmp[0] !~ /^error/) {
             my ($name,$value)=split(/\=/,$_);              my %TempHash = @tmp;
             $oldsheets{&Apache::lonnet::unescape($name)}              my $count = 0;
                       =&Apache::lonnet::unescape($value);              while (my ($key,$value) = each %TempHash) {
                   $oldsheets{$key} = $value;
                   $count++;
               }
               $loadedcaches{$sheet->{'uname'}.'_'.$sheet->{'udom'}}=1;
         }          }
     }      }
     $loadedcaches{$sname.'_'.$sdom}=1;      
   }  
 }  }
   
 # ===================================================== Calculated sheets cache  # ===================================================== Calculated sheets cache
Line 2455  sub cachedssheets { Line 2949  sub cachedssheets {
 # Interactive call to screen  # Interactive call to screen
 #  #
 #  #
   
   
 sub handler {  sub handler {
     my $r=shift;      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) {      if ($r->header_only) {
       $r->content_type('text/html');          $r->content_type('text/html');
       $r->send_http_header;          $r->send_http_header;
       return OK;          return OK;
     }      }
       #
       # Global directory configs
       #
       $includedir = $r->dir_config('lonIncludes');
       $tmpdir = $r->dir_config('lonDaemons').'/tmp/';
       #
       # 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\./) {      if ($ENV{'request.role'} =~ /^st\./) {
         delete $ENV{'form.unewfield'}   if (exists($ENV{'form.unewfield'}));          delete $ENV{'form.unewfield'}   if (exists($ENV{'form.unewfield'}));
         delete $ENV{'form.unewformula'} if (exists($ENV{'form.unewformula'}));          delete $ENV{'form.unewformula'} if (exists($ENV{'form.unewformula'}));
     }      }
       #
 # ---------------------------------------------------- Global directory configs      # Look for special assessment spreadsheets - '_feedback', etc.
       #
 $includedir=$r->dir_config('lonIncludes');      if (($ENV{'form.usymb'}=~/^\_(\w+)/) && (!$ENV{'form.ufn'} || 
 $tmpdir=$r->dir_config('lonDaemons').'/tmp/';                                               $ENV{'form.ufn'} eq '' || 
                                                $ENV{'form.ufn'} eq 'default')) {
 # ----------------------------------------------------- Needs to be in a course          $ENV{'form.ufn'}='default_'.$1;
   
   if ($ENV{'request.course.fn'}) {   
   
 # --------------------------- 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;  
     }      }
       if (!$ENV{'form.ufn'} || $ENV{'form.ufn'} eq 'default') {
 # -------------------------------------- Interactive loading of specific sheet?          $ENV{'form.ufn'}='course_default_'.$sheettype;
       }
       #
       # Interactive loading of specific sheet?
       #
     if (($ENV{'form.load'}) && ($ENV{'form.loadthissheet'} ne 'Default')) {      if (($ENV{'form.load'}) && ($ENV{'form.loadthissheet'} ne 'Default')) {
  $ENV{'form.ufn'}=$ENV{'form.loadthissheet'};          $ENV{'form.ufn'}=$ENV{'form.loadthissheet'};
     }      }
 # ------------------------------------------- Nothing there? Must be login user      #
       # Determine the user name and domain for the sheet.
     my $aname;      my $aname;
     my $adom;      my $adom;
   
     unless ($ENV{'form.uname'}) {      unless ($ENV{'form.uname'}) {
  $aname=$ENV{'user.name'};          $aname=$ENV{'user.name'};
         $adom=$ENV{'user.domain'};          $adom=$ENV{'user.domain'};
     } else {      } else {
         $aname=$ENV{'form.uname'};          $aname=$ENV{'form.uname'};
         $adom=$ENV{'form.udom'};          $adom=$ENV{'form.udom'};
     }      }
       #
 # ------------------------------------------------------------------- Open page      # Open page, try to prevent browser cache.
       #
     $r->content_type('text/html');      $r->content_type('text/html');
     $r->header_out('Cache-control','no-cache');      $r->header_out('Cache-control','no-cache');
     $r->header_out('Pragma','no-cache');      $r->header_out('Pragma','no-cache');
     $r->send_http_header;      $r->send_http_header;
       #
 # --------------------------------------------------------------- Screen output      # Header....
       #
     $r->print('<html><head><title>LON-CAPA Spreadsheet</title>');      $r->print('<html><head><title>LON-CAPA Spreadsheet</title>');
       my $nothing = "''";
       if ($ENV{'browser.type'} eq 'explorer') {
           $nothing = "'javascript:void(0);'";
       }
   
     if ($ENV{'request.role'} !~ /^st\./) {      if ($ENV{'request.role'} !~ /^st\./) {
         $r->print(<<ENDSCRIPT);          $r->print(<<ENDSCRIPT);
 <script language="JavaScript">  <script language="JavaScript">
   
     function celledit(cn,cf) {      var editwin;
         var cnf=prompt(cn,cf);  
         if (cnf!=null) {      function celledit(cellname,cellformula) {
             document.sheet.unewfield.value=cn;          var edit_text = '';
             document.sheet.unewformula.value=cnf;          // cellformula may contain less-than and greater-than symbols, so
             document.sheet.submit();          // 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) {      function changesheet(cn) {
Line 2544  $tmpdir=$r->dir_config('lonDaemons').'/t Line 3104  $tmpdir=$r->dir_config('lonDaemons').'/t
 </script>  </script>
 ENDSCRIPT  ENDSCRIPT
     }      }
     $r->print('</head><body bgcolor="#FFFFFF">'.      $r->print('</head>'.&Apache::loncommon::bodytag('Grades Spreadsheet').
        '<img align=right src=/adm/lonIcons/lonlogos.gif>'.                '<form action="'.$r->uri.'" name="sheet" method="post">');
        '<h1>LON-CAPA Spreadsheet</h1>'.      $r->print(&hiddenfield('uname',$ENV{'form.uname'}).
        '<form action="'.$r->uri.'" name=sheet method=post>'.                &hiddenfield('udom',$ENV{'form.udom'}).
        &hiddenfield('uname',$ENV{'form.uname'}).                &hiddenfield('usymb',$ENV{'form.usymb'}).
        &hiddenfield('udom',$ENV{'form.udom'}).                &hiddenfield('unewfield','').
        &hiddenfield('usymb',$ENV{'form.usymb'}).                &hiddenfield('unewformula',''));
        &hiddenfield('unewfield','').  
        &hiddenfield('unewformula',''));  
   
 # ---------------------- Make sure that this gets out, even if user hits "stop"  
   
     $r->rflush();      $r->rflush();
       #
 # ---------------------------------------------------------------- Full recalc?      # Full recalc?
       #
   
     if ($ENV{'form.forcerecalc'}) {      if ($ENV{'form.forcerecalc'}) {
  $r->print('<h4>Completely Recalculating Sheet ...</h4>');          $r->print('<h4>Completely Recalculating Sheet ...</h4>');
         undef %spreadsheets;          undef %spreadsheets;
         undef %courserdatas;          undef %courserdatas;
         undef %userrdatas;          undef %userrdatas;
         undef %defaultsheets;          undef %defaultsheets;
         undef %updatedata;          undef %rowlabel_cache;
    }  
    
 # ---------------------------------------- Read new sheet or modified worksheet  
   
     $r->uri=~/\/(\w+)$/;  
   
     my $asheet=&makenewsheet($aname,$adom,$1,$ENV{'form.usymb'});  
   
 # ------------------------ 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>New formula: '.$ENV{'form.unewfield'}.'='.  
                   $ENV{'form.unewformula'}.'<p>');  
         &setfilename($asheet,$ENV{'form.ufn'});  
  &tmpread($asheet,  
                  $ENV{'form.unewfield'},$ENV{'form.unewformula'});  
   
      } elsif ($ENV{'form.saveas'}) {  
         &setfilename($asheet,$ENV{'form.ufn'});  
  &tmpread($asheet);  
     } else {  
         &readsheet($asheet,$ENV{'form.ufn'});  
     }  
   
 # -------------------------------------------------- Print out user information  
   
     unless (&gettype($asheet) eq 'classcalc') {  
         $r->print('<p><b>User:</b> '.&getuname($asheet).  
                   '<br><b>Domain:</b> '.&getudom($asheet));  
 #        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> '.&getcsec($asheet));  
 #        }  
         if ($ENV{'form.usymb'}) {  
            $r->print('<br><b>Assessment:</b> <tt>'.$ENV{'form.usymb'}.'</tt>');  
         }  
     }      }
       # Read new sheet or modified worksheet
 # ---------------------------------------------------------------- Course title      my ($sheet)=&makenewsheet($aname,$adom,$sheettype,$ENV{'form.usymb'});
       #
     $r->print('<h1>'.      # Check user permissions
             $ENV{'course.'.$ENV{'request.course.id'}.'.description'}.      if (($sheet->{'sheettype'} eq 'classcalc'       ) || 
              '</h1><h3>'.localtime().'</h3>');          ($sheet->{'uname'}     ne $ENV{'user.name'} ) ||
           ($sheet->{'udom'}      ne $ENV{'user.domain'})) {
 # ---------------------------------------------------- See if user can see this          unless (&Apache::lonnet::allowed('vgr',$sheet->{'cid'})) {
               $r->print('<h1>Access Permission Denied</h1>'.
     if ((&gettype($asheet) eq 'classcalc') ||                         '</form></body></html>');
         (&getuname($asheet) ne $ENV{'user.name'}) ||  
         (&getudom($asheet) ne $ENV{'user.domain'})) {  
         unless (&Apache::lonnet::allowed('vgr',&getcid($asheet))) {  
     $r->print(  
            '<h1>Access Permission Denied</h1></form></body></html>');  
             return OK;              return OK;
         }          }
     }      }
       # Print out user information
 # ---------------------------------------------------------- Additional options      $r->print('<h2>'.$sheet->{'coursedesc'}.'</h2>');
       if ($sheet->{'sheettype'} ne 'classcalc') {
     $r->print(          $r->print('<h2>'.&gettitle($sheet).'</h2><p>');
  '<input type=submit name=forcerecalc value="Completely Recalculate Sheet"><p>'      }
  );      if ($sheet->{'sheettype'} eq 'assesscalc') {
     if (&gettype($asheet) eq 'assesscalc') {          $r->print('<b>User:</b> '.$sheet->{'uname'}.
        $r->print ('<p><font size=+2><a href="/adm/studentcalc?uname='.                    '<br /><b>Domain:</b> '.$sheet->{'udom'}.'<br />');
                                                &getuname($asheet).      }
                                                '&udom='.&getudom($asheet).      if ($sheet->{'sheettype'} eq 'studentcalc' || 
                   '">Level up: Student Sheet</a></font><p>');          $sheet->{'sheettype'} eq 'assesscalc') {
     }          $r->print('<b>Section/Group:</b>'.$sheet->{'csec'}.'</p>');
           } 
     if ((&gettype($asheet) eq 'studentcalc') &&       #
         (&Apache::lonnet::allowed('vgr',&getcid($asheet)))) {      # If a new formula had been entered, go from work copy
        $r->print (      if ($ENV{'form.unewfield'}) {
                    '<p><font size=+2><a href="/adm/classcalc">'.          $r->print('<h2>Modified Workcopy</h2>');
                    'Level up: Course Sheet</a></font><p>');          #$ENV{'form.unewformula'}=~s/\'/\"/g;
     }          $r->print('<p>Cell '.$ENV{'form.unewfield'}.' = <pre>');
               $r->print(&HTML::Entities::encode($ENV{'form.unewformula'}).
                     '</pre></p>');
 # ----------------------------------------------------------------- Save dialog          $sheet->{'filename'} = $ENV{'form.ufn'};
           &tmpread($sheet,$ENV{'form.unewfield'},$ENV{'form.unewformula'});
       } elsif ($ENV{'form.saveas'}) {
           $sheet->{'filename'} = $ENV{'form.ufn'};
           &tmpread($sheet);
       } else {
           &readsheet($sheet,$ENV{'form.ufn'});
       }
       # Additional options
       if ($sheet->{'sheettype'} 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->{'sheettype'} 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'})) {      if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
         my $fname=$ENV{'form.ufn'};          my $fname=$ENV{'form.ufn'};
         $fname=~s/\_[^\_]+$//;          $fname=~s/\_[^\_]+$//;
         if ($fname eq 'default') { $fname='course_default'; }          if ($fname eq 'default') { $fname='course_default'; }
         $r->print('<input type=submit name=saveas value="Save as ...">'.          $r->print('<input type=submit name=saveas value="Save as ...">'.
               '<input type=text size=20 name=newfn value="'.$fname.                    '<input type=text size=20 name=newfn value="'.$fname.'">'.
               '"> (make default: <input type=checkbox name="makedefufn">)<p>');                    'make default: <input type=checkbox name="makedefufn"><p>');
     }      }
       $r->print(&hiddenfield('ufn',$sheet->{'filename'}));
     $r->print(&hiddenfield('ufn',&getfilename($asheet)));      # Load dialog
   
 # ----------------------------------------------------------------- Load dialog  
     if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {      if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
  $r->print('<p><input type=submit name=load value="Load ...">'.          $r->print('<p><input type=submit name=load value="Load ...">'.
                   '<select name="loadthissheet">'.                    '<select name="loadthissheet">'.
                   '<option name="default">Default</option>');                    '<option name="default">Default</option>');
         foreach (&othersheets($asheet,&gettype($asheet))) {          foreach (&othersheets($sheet)) {
     $r->print('<option name="'.$_.'"');              $r->print('<option name="'.$_.'"');
             if ($ENV{'form.ufn'} eq $_) {              if ($ENV{'form.ufn'} eq $_) {
                $r->print(' selected');                  $r->print(' selected');
             }              }
             $r->print('>'.$_.'</option>');              $r->print('>'.$_.'</option>');
         }           } 
         $r->print('</select><p>');          $r->print('</select><p>');
         if (&gettype($asheet) eq 'studentcalc') {          if ($sheet->{'sheettype'} eq 'studentcalc') {
     &setothersheets($asheet,&othersheets($asheet,'assesscalc'));              &setothersheets($sheet,
                               &othersheets($sheet,'assesscalc'));
         }          }
     }      }
       #
 # --------------------------------------------------------------- Cached sheets      # Set up caching mechanisms
       #
     &expirationdates();      &load_spreadsheet_expirationdates();
       # Clear out old caches if we have not seen this class before.
     undef %oldsheets;      if (exists($oldsheets{'course'}) &&
     undef %loadedcaches;          $oldsheets{'course'} ne $sheet->{'cid'}) {
           undef %oldsheets;
     if (&gettype($asheet) eq 'classcalc') {          undef %loadedcaches;
         $r->print("Loading previously calculated student sheets ...<br>\n");      }
       $oldsheets{'course'} = $sheet->{'cid'};
       #
       if ($sheet->{'sheettype'} eq 'classcalc') {
           $r->print("Loading previously calculated student sheets ...\n");
         $r->rflush();          $r->rflush();
         &cachedcsheets();          &cachedcsheets();
     } elsif (&gettype($asheet) eq 'studentcalc') {      } elsif ($sheet->{'sheettype'} eq 'studentcalc') {
         $r->print("Loading previously calculated assessment sheets ...<br>\n");          $r->print("Loading previously calculated assessment sheets ...\n");
         $r->rflush();          $r->rflush();
         &cachedssheets(&getuname($asheet),&getudom($asheet),          &cachedssheets($sheet);
                        &getuhome($asheet));  
     }      }
       # Update sheet, load rows
 # ----------------------------------------------------- Update sheet, load rows  
   
     $r->print("Loaded sheet(s), updating rows ...<br>\n");      $r->print("Loaded sheet(s), updating rows ...<br>\n");
     $r->rflush();      $r->rflush();
       #
     &updatesheet($asheet);      &updatesheet($sheet);
       $r->print("Updated rows, loading row data ...\n");
     $r->print("Updated rows, loading row data ...<br>\n");  
     $r->rflush();      $r->rflush();
       #
     &loadrows($asheet,$r);      &loadrows($sheet,$r);
   
     $r->print("Loaded row data, calculating sheet ...<br>\n");      $r->print("Loaded row data, calculating sheet ...<br>\n");
     $r->rflush();      $r->rflush();
       #
     my $calcoutput=&calcsheet($asheet);      my $calcoutput=&calcsheet($sheet);
     $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>');      $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>');
       # See if something to save
 # ---------------------------------------------------- See if something to save  
   
     if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {      if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
         my $fname='';          my $fname='';
  if ($ENV{'form.saveas'} && ($fname=$ENV{'form.newfn'})) {          if ($ENV{'form.saveas'} && ($fname=$ENV{'form.newfn'})) {
             $fname=~s/\W/\_/g;              $fname=~s/\W/\_/g;
             if ($fname eq 'default') { $fname='course_default'; }              if ($fname eq 'default') { $fname='course_default'; }
             $fname.='_'.&gettype($asheet);              $fname.='_'.$sheet->{'sheettype'};
             &setfilename($asheet,$fname);              $sheet->{'filename'} = $fname;
             $ENV{'form.ufn'}=$fname;              $ENV{'form.ufn'}=$fname;
     $r->print('<p>Saving spreadsheet: '.              $r->print('<p>Saving spreadsheet: '.
                          &writesheet($asheet,$ENV{'form.makedefufn'}).'<p>');                        &writesheet($sheet,$ENV{'form.makedefufn'}).
  }                        '<p>');
           }
     }      }
       #
 # ------------------------------------------------ Write the modified worksheet      # Write the modified worksheet
       $r->print('<b>Current sheet:</b> '.$sheet->{'filename'}.'</p>');
    $r->print('<b>Current sheet:</b> '.&getfilename($asheet).'<p>');      &tmpwrite($sheet);
       if ($sheet->{'sheettype'} eq 'assesscalc') {
    &tmpwrite($asheet);          $r->print('<p>Show rows with empty A column: ');
   
     if (&gettype($asheet) eq 'studentcalc') {  
  $r->print('<br>Show rows with empty A column: ');  
     } else {      } else {
         $r->print('<br>Show empty rows: ');          $r->print('<p>Show empty rows: ');
     }       }
       #
     $r->print(&hiddenfield('userselhidden','true').      $r->print(&hiddenfield('userselhidden','true').
              '<input type=checkbox name=showall onClick="submit()"');                '<input type="checkbox" name="showall" onClick="submit()"');
       #
     if ($ENV{'form.showall'}) {       if ($ENV{'form.showall'}) { 
        $r->print(' checked');           $r->print(' checked'); 
     } else {      } else {
  unless ($ENV{'form.userselhidden'}) {          unless ($ENV{'form.userselhidden'}) {
            unless               unless 
  ($ENV{'course.'.$ENV{'request.course.id'}.'.hideemptyrows'} eq 'yes') {                  ($ENV{'course.'.$sheet->{'cid'}.'.hideemptyrows'} eq 'yes') {
           $r->print(' checked');                      $r->print(' checked');
           $ENV{'form.showall'}=1;                      $ENV{'form.showall'}=1;
            }                  }
        }          }
     }      }
     $r->print('>');      $r->print('>');
       #
     if (&gettype($asheet) eq 'classcalc') {      # output format select box 
        $r->print(      $r->print(' Output as <select name="output" size="1" onChange="submit()">'.
    ' Output CSV format: <input type=checkbox name=showcsv onClick="submit()"');                "\n");
        if ($ENV{'form.showcsv'}) { $r->print(' checked'); }      foreach my $mode (qw/HTML CSV Excel/) {
        $r->print('>');          $r->print('<option value="'.$mode.'"');
     }          if ($ENV{'form.output'} eq $mode) {
               $r->print(' selected ');
 # ------------------------------------------------------------------ Insertrows          } 
     $r->print('&nbsp;Student Status: '.          $r->print('>'.$mode.'</option>'."\n");
               &Apache::lonhtmlcommon::StatusOptions      }
               ($ENV{'form.Status'},'sheet'));  #
   #    Mulit-sheet excel takes too long and does not work at all for large
    $r->print(<<ENDINSERTBUTTONS);  #    classes.  Future inclusion of this option may be possible with the
 <br>  #    Spreadsheet::WriteExcel::Big and speed improvements.
 <input type='button' onClick='insertrow("top");'   #
 value='Insert Row Top'>  #    if ($sheet->{'sheettype'} eq 'classcalc') {
 <input type='button' onClick='insertrow("bottom");'   #        $r->print('<option value="recursive excel"');
 value='Insert Row Bottom'><br>  #        if ($ENV{'form.output'} eq 'recursive excel') {
 ENDINSERTBUTTONS  #            $r->print(' selected ');
   #        } 
 # ------------------------------------------------------------- Print out sheet  #        $r->print(">Multi-Sheet Excel</option>\n");
   #    }
     &outsheet($r,$asheet);      $r->print("</select>\n");
       #
       if ($sheet->{'sheettype'} 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
       &outsheet($sheet,$r);
     $r->print('</form></body></html>');      $r->print('</form></body></html>');
       #  Done
 # ------------------------------------------------------------------------ Done  
   } else {  
 # ----------------------------- 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;   
   }  
     return OK;      return OK;
   
 }  }
   
 1;  1;
 __END__  __END__
   
   
   
   

Removed from v.1.100.4.2  
changed lines
  Added in v.1.162


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