Diff for /loncom/interface/Attic/lonspreadsheet.pm between versions 1.104 and 1.144

version 1.104, 2002/08/30 19:47:47 version 1.144, 2002/11/20 16:46:31
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::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 94  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 120  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 %sheet_values;   
 undef %t;  
 undef %f;  
 undef %c;  
 undef %rowlabel;  
 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 199  sub mask { Line 143  sub mask {
           $alpha='['.$la.'-Za-'.$ua.']';            $alpha='['.$la.'-Za-'.$ua.']';
        }         }
     }         }   
   
     if (($ld eq '*') || ($ud eq '*')) {      if (($ld eq '*') || ($ud eq '*')) {
  $num='\d+';   $num='\d+';
     } else {      } else {
Line 219  sub mask { Line 162  sub mask {
        } else {         } else {
            my @lda=($ld=~m/\d/g);             my @lda=($ld=~m/\d/g);
            my @uda=($ud=~m/\d/g);             my @uda=($ud=~m/\d/g);
            my $i; $j=0; $notdone=1;             my $i; 
              my $j=0; 
              my $notdone=1;
            for ($i=0;($i<=$#lda)&&($notdone);$i++) {             for ($i=0;($i<=$#lda)&&($notdone);$i++) {
                if ($lda[$i]==$uda[$i]) {                 if ($lda[$i]==$uda[$i]) {
    $num.=$lda[$i];     $num.=$lda[$i];
Line 243  sub mask { Line 188  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 252  sub mask { Line 197  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 576  compute the average of the items in the Line 593  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; 
       my $num=0;
     foreach (grep /$mask/,keys(%sheet_values)) {      foreach (grep /$mask/,keys(%sheet_values)) {
         $sum+=$sheet_values{$_};          $sum+=$sheet_values{$_};
         $num++;          $num++;
Line 813  sub expandnamed { Line 831  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 824  sub expandnamed { Line 844  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 863  sub sett { Line 885  sub sett {
     }      }
     # Deal with the normal cells      # Deal with the normal cells
     foreach (keys(%f)) {      foreach (keys(%f)) {
  if (($f{$_}) && ($_!~/template\_/)) {   if (exists($f{$_}) && ($_!~/template\_/)) {
             my $matches=($_=~/^$pattern(\d+)/);              my $matches=($_=~/^$pattern(\d+)/);
             if  (($matches) && ($1)) {              if  (($matches) && ($1)) {
         unless ($f{$_}=~/^\!/) {          unless ($f{$_}=~/^\!/) {
Line 907  sub calc { Line 929  sub calc {
     while ($notfinished) {      while ($notfinished) {
  $notfinished=0;   $notfinished=0;
         foreach (keys(%t)) {          foreach (keys(%t)) {
               #$errorlog .= "$_:".$t{$_};
             my $old=$sheet_values{$_};              my $old=$sheet_values{$_};
             $sheet_values{$_}=eval $t{$_};              $sheet_values{$_}=eval $t{$_};
     if ($@) {      if ($@) {
Line 914  sub calc { Line 937  sub calc {
                 return $_.': '.$@;                  return $_.': '.$@;
             }              }
     if ($sheet_values{$_} 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) {
Line 924  sub calc { Line 948  sub calc {
     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';
     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_'.$_};          my $fm=$sheet->{'f'}->{'template_'.$_};
         $fm=~s/[\'\"]/\&\#34;/g;          $fm=~s/[\'\"]/\&\#34;/g;
         push(@cols,"'template_$_','$fm'".'___eq___'.$fm);          push(@cols,{ name    => 'template_'.$_,
                        formula => $fm,
                        value   => $fm });
     }      }
     return @cols;      return ($rowlabel,@cols);
 }  }
   
 #  
 # This is actually used for the student spreadsheet, not the assessment sheet  
 # Do not be fooled by the name!  
 #  
 sub outrowassess {  sub outrowassess {
     # $n is the current row number      # $n is the current row number
     my $n=shift;      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 ($rowlabel{$usy}) {          if (exists($sheet->{'rowlabel'}->{$usy})) {
             $cols[0]=$rowlabel{$usy}.'<br>'.              $rowlabel = $sheet->{'rowlabel'}->{$usy};
                 '<select name="sel_'.$n.'" onChange="changesheet('.$n.')">'.  
                     '<option name="default">Default</option>';  
         } else {           } else { 
             $cols[0]='';               $rowlabel = '';
         }  
         foreach (@os) {  
             $cols[0].='<option name="'.$_.'"';  
             if ($ufn eq $_) {  
                 $cols[0].=' selected';  
             }  
             $cols[0].='>'.$_.'</option>';  
         }          }
         $cols[0].='</select>';  
     } else {      } else {
         $cols[0]='<b><font size=+1>Export</font></b>';          $rowlabel = 'Export';
     }      }
     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};          my $fm=$sheet->{'f'}->{$_.$n};
         $fm=~s/[\'\"]/\&\#34;/g;          $fm=~s/[\'\"]/\&\#34;/g;
         push(@cols,"'$_$n','$fm'".'___eq___'.$sheet_values{$_.$n});          push(@cols,{ name    => $_.$n,
                        formula => $fm,
                        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]=$rowlabel{$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';
           } else {
               $rowlabel = 'Export';
           }
     }      }
     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};          my $fm=$sheet->{'f'}->{$_.$n};
         $fm=~s/[\'\"]/\&\#34;/g;          $fm=~s/[\'\"]/\&\#34;/g;
         push(@cols,"'$_$n','$fm'".'___eq___'.$sheet_values{$_.$n});          push(@cols,{ name    => $_.$n,
                        formula => $fm,
                        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') {  
  push(@exportarray,$sheet_values{$_.'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,%rowlabel)=@_;      my $sheet=shift;
     %{$safeeval->varglob('rowlabel')}=%rowlabel;      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('%sheet_values');  
 }  }
   
 # ---------------------------------------------------------------- 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 setmaxrow {  
     my ($safeeval,$row)=@_;  
     $safeeval->reval('$maxrow='.$row.';');  
 }  
   
 # ------------------------------------------------------------------ Get maxrow  
   
 sub getmaxrow {  
     my $safeeval=shift;  
     return $safeeval->reval('$maxrow');  
 }  
   
 # ---------------------------------------------------------------- Set filename  
   
 sub setfilename {  
     my ($safeeval,$fn)=@_;  
     $safeeval->reval('$filename="'.$fn.'";');  
 }  
   
 # ---------------------------------------------------------------- Get filename  
   
 sub getfilename {  
     my $safeeval=shift;  
     return $safeeval->reval('$filename');  
 }  
   
 # --------------------------------------------------------------- Get course ID  
   
 sub getcid {  
     my $safeeval=shift;  
     return $safeeval->reval('$cid');  
 }  
   
 # --------------------------------------------------------- Get course filename  
   
 sub getcfn {  
     my $safeeval=shift;  
     return $safeeval->reval('$cfn');  
 }  
   
 # ----------------------------------------------------------- Get course number  
   
 sub getcnum {  
     my $safeeval=shift;  
     return $safeeval->reval('$cnum');  
 }  
   
 # ------------------------------------------------------------- Get course home  
   
 sub getchome {  
     my $safeeval=shift;  
     return $safeeval->reval('$chome');  
 }  }
   
 # ----------------------------------------------------------- Get course domain  sub geterrorlog {
       my $sheet = shift;
 sub getcdom {      return ${$sheet->{'safe'}->varglob('errorlog')};    
     my $safeeval=shift;  }
     return $safeeval->reval('$cdom');  
 }  sub gettitle {
       my $sheet = shift;
 # ---------------------------------------------------------- Get course section      if ($sheet->{'sheettype'} eq 'classcalc') {
           return $sheet->{'coursedesc'};
 sub getcsec {      } elsif ($sheet->{'sheettype'} eq 'studentcalc') {
     my $safeeval=shift;          return 'Grades for '.$sheet->{'uname'}.'@'.$sheet->{'udom'};
     return $safeeval->reval('$csec');      } elsif ($sheet->{'sheettype'} eq 'assesscalc') {
 }          if (($sheet->{'usymb'} eq '_feedback') ||
               ($sheet->{'usymb'} eq '_evaluation') ||
 # --------------------------------------------------------------- Get user name              ($sheet->{'usymb'} eq '_discussion') ||
               ($sheet->{'usymb'} eq '_tutoring')) {
 sub getuname {              my $title = $sheet->{'usymb'};
     my $safeeval=shift;              $title =~ s/^_//;
     return $safeeval->reval('$uname');              $title = ucfirst($title);
               return $title;
           }
           return if (! defined($sheet->{'mapid'}) || 
                      $sheet->{'mapid'} !~ /^\d+$/);
           my $mapid = $sheet->{'mapid'};
           return if (! defined($sheet->{'resid'}) || 
                      $sheet->{'resid'} !~ /^\d+$/);
           my $resid = $sheet->{'resid'};
           my %course_db;
           tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db',
               &GDBM_READER(),0640);
           return if (! tied(%course_db));
           my $key = 'title_'.$mapid.'.'.$resid;
           my $title = '';
           if (exists($course_db{$key})) {
               $title = $course_db{$key};
           } else {
               $title = $sheet->{'usymb'};
           }
           untie (%course_db);
           return $title;
       }
 }  }
   
 # ------------------------------------------------------------- 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 update_student_sheet{
       my ($sheet,$r,$c) = @_;
 sub exportdata {      # Load in the studentcalc sheet
     my $safeeval=shift;      &readsheet($sheet,'default_studentcalc');
     return $safeeval->reval('&exportrowa()');      # 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='';  
     my $dataflag=0;  
     unless ($n eq '-') {  
        $defaultbg=((($n-1)/5)==int(($n-1)/5))?'#E0E0':'#FFFF';  
     } else {  
        $defaultbg='#E0FF';  
     }  
     unless ($ENV{'form.showcsv'}) {  
        $rowdata.="\n<tr><td><b><font size=+1>$n</font></b></td>";  
     } else {  
        $rowdata.="\n".'"'.$n.'"';  
     }  
     my $showf=0;  
     my $proc;  
     my $maxred=1;  
     my $sheettype=&gettype($safeeval);  
     if ($sheettype eq 'studentcalc') {  
         $proc='&outrowassess';  
         $maxred=26;  
     } else {  
         $proc='&outrow';  
     }  
     if ($sheettype eq 'assesscalc') {  
         $maxred=1;  
     } else {  
         $maxred=26;  
     }  
     if (&getfa($safeeval,$n)=~/^[\~\-]/) { $maxred=1; }  
     if ($n eq '-') {       if ($n eq '-') { 
         $proc='&templaterow';           ($rowlabel,@rowdata) = &templaterow($sheet);
         $n=-1;       } elsif ($sheet->{'sheettype'} eq 'studentcalc') {
         $dataflag=1;           ($rowlabel,@rowdata) = &outrowassess($sheet,$n);
     }  
     foreach ($safeeval->reval($proc.'('.$n.')')) {  
        my $bgcolor=$defaultbg.((($showf-1)/5==int(($showf-1)/5))?'99':'DD');  
        my ($fm,$vl)=split(/\_\_\_eq\_\_\_/,$_);  
        if ((($vl ne '') || ($vl eq '0')) &&  
            (($showf==1) || ($sheettype ne 'studentcalc'))) { $dataflag=1; }  
        if ($showf==0) { $vl=$_; }  
       unless ($ENV{'form.showcsv'}) {  
        if ($showf<=$maxred) { $bgcolor='#FFDDDD'; }  
        if (($n==0) && ($showf<=26)) { $bgcolor='#CCCCFF'; }   
        if (($showf>$maxred) || ((!$n) && ($showf>0))) {  
    if ($vl eq '') {  
        $vl='<font size=+2 color='.$bgcolor.'>&#35;</font>';  
            }  
            $rowdata.=  
        '<td bgcolor='.$bgcolor.'><a href="javascript:celledit('.$fm.');">'.$vl.  
        '</a></td>';  
        } else {  
            $rowdata.='<td bgcolor='.$bgcolor.'>&nbsp;'.$vl.'&nbsp;</td>';  
        }  
       } else {  
   $rowdata.=',"'.$vl.'"';  
       }  
        $showf++;  
     }  # End of foreach($safeval...)  
     if ($ENV{'form.showall'} || ($dataflag)) {  
        return $rowdata.($ENV{'form.showcsv'}?'':'</tr>');  
     } else {      } else {
        return '';          ($rowlabel,@rowdata) = &outrow($sheet,$n);
     }      }
       return ($rowlabel,@rowdata);
 }  }
   
 # ------------------------------------------------------------- Print out sheet  ########################################################################
   ########################################################################
 sub outsheet {  sub sort_indicies {
     my ($r,$safeeval)=@_;      my $sheet = shift;
     my $maxred;  
     my $realm;  
     if (&gettype($safeeval) eq 'assesscalc') {  
         $maxred=1;  
         $realm='Assessment';  
     } elsif (&gettype($safeeval) eq 'studentcalc') {  
         $maxred=26;  
         $realm='User';  
     } else {  
         $maxred=26;  
         $realm='Course';  
     }  
     my $maxyellow=52-$maxred;  
     my $tabledata;  
     unless ($ENV{'form.showcsv'}) {  
        $tabledata=  
         '<table border=2><tr><th colspan=2 rowspan=2><font size=+2>'.  
                   $realm.'</font></th>'.  
                   '<td bgcolor=#FFDDDD colspan='.$maxred.  
                   '><b><font size=+1>Import</font></b></td>'.  
                   '<td colspan='.$maxyellow.  
   '><b><font size=+1>Calculations</font></b></td></tr><tr>';  
        my $showf=0;  
        foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',  
                 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',  
                 'a','b','c','d','e','f','g','h','i','j','k','l','m',  
                 'n','o','p','q','r','s','t','u','v','w','x','y','z') {  
            $showf++;  
            if ($showf<=$maxred) {   
                $tabledata.='<td bgcolor="#FFDDDD">';   
            } else {  
                $tabledata.='<td>';  
            }  
            $tabledata.="<b><font size=+1>$_</font></b></td>";  
        }  
        $tabledata.='</tr>'.&rown($safeeval,'-').&rown($safeeval,0);  
    } else { $tabledata='<pre>'; }  
   
     $r->print($tabledata);  
   
     my $row;  
     my $maxrow=&getmaxrow($safeeval);  
   
     my @sortby=();  
     my @sortidx=();      my @sortidx=();
     for ($row=1;$row<=$maxrow;$row++) {      #
        $sortby[$row-1]=$safeeval->reval('$f{"A'.$row.'"}');      if ($sheet->{'sheettype'} eq 'classcalc') {
        $sortidx[$row-1]=$row-1;          my @sortby=(undef);
     }          # Skip row 0
     @sortidx=sort { $sortby[$a] cmp $sortby[$b]; } @sortidx;          for (my $row=1;$row<=$sheet->{'maxrow'};$row++) {
         my $what='Student';              my (undef,$sname,$sdom,$fullname,$section,$id) = 
         if (&gettype($safeeval) eq 'assesscalc') {                  split(':',$sheet->{'rowlabel'}->{$sheet->{'f'}->{'A'.$row}});
     $what='Item';              push (@sortby, lc($fullname));
  } elsif (&gettype($safeeval) eq 'studentcalc') {              push (@sortidx, $row);
             $what='Assessment';          }
         }          @sortidx = sort { $sortby[$a] cmp $sortby[$b]; } @sortidx;
       } elsif ($sheet->{'sheettype'} eq 'studentcalc') {
     my $n=0;          my @sortby1=(undef);
     for ($row=0;$row<$maxrow;$row++) {          my @sortby2=(undef);
         my $thisrow=&rown($safeeval,$sortidx[$row]+1);          # Skip row 0
         if ($thisrow) {          for (my $row=1;$row<=$sheet->{'maxrow'};$row++) {
             if (($n/25==int($n/25)) && (!$ENV{'form.showcsv'})) {              my (undef,$symb,$uname,$udom,$mapid,$resid,$title) = 
                   split(':',$sheet->{'rowlabel'}->{$sheet->{'f'}->{'A'.$row}});
               $symb = &Apache::lonnet::unescape($symb);
               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>';
           }
       }
       if ($value =~ /^\s*$/ ) {
           $value = '<font color="'.$bgcolor.'">#</font>';
       }
       $formula =~ s/\n/\\n/gs;
       $result .= '<a href="javascript:celledit(\''.
           $name.'\',\''.$formula.'\');">'.$value.'</a>';
       return $result;
   }
   
   sub html_uneditable_cell {
       my ($cell,$bgcolor) = @_;
       my $value = (defined($cell) ? $cell->{'value'} : '');
       return '&nbsp;'.$value.'&nbsp;';
   }
   
   sub outsheet_html  {
       my ($sheet,$r) = @_;
       my ($num_uneditable,$realm,$row_type);
       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="1" 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++;
       }
       $tabledata.="</tr>\n";
       $r->print($tabledata);
       ####################################
       # Print out template row
       ####################################
       my ($rowlabel,@rowdata) = &get_row($sheet,'-');
       my $row_html = '<tr><td>'.&format_html_rowlabel($rowlabel).'</td>';
       my $num_cols_output = 0;
       foreach my $cell (@rowdata) {
           if ($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);
       ####################################
       # Print out summary/export row
       ####################################
       my ($rowlabel,@rowdata) = &get_row($sheet,'0');
       $row_html = '<tr><td>'.&format_html_rowlabel($rowlabel).'</td>';
       $num_cols_output = 0;
       foreach my $cell (@rowdata) {
           if ($num_cols_output++ < 26) {
               $row_html .= '<td bgcolor="#CCCCFF">';
               $row_html .= &html_editable_cell($cell,'#CCCCFF');
           } else {
               $row_html .= '<td bgcolor="#DDCCFF">';
               $row_html .= &html_uneditable_cell(undef,'#CCCCFF');
           }
           $row_html .= '</td>';
       }
       $row_html.= "</tr>\n";
       $r->print($row_html);
       $r->print('</table>');
       ####################################
       # 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*$/);
           next if (($sheet->{'sheettype'} eq 'assesscalc') && 
                    (! $ENV{'form.showall'})                &&
                    ($rowdata[0]->{'value'} =~ /^\s*$/));
           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);
           }
           #
           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($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') {
               $row_html.='<td>'.&format_html_rowlabel($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($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 ($shown_cells < $num_uneditable) {
                   $row_html .= &html_uneditable_cell($cell,$bgcolor);
               } else {
                   $row_html .= &html_editable_cell($cell,$bgcolor);
               }
               $row_html.='</td>';
               $shown_cells++;
           }
           if ($row_html) {
               if ($rows_output % 25 == 0) {
                 $r->print("</table>\n<br>\n");                  $r->print("</table>\n<br>\n");
                 $r->rflush();                  $r->rflush();
                 $r->print('<table border=2><tr><td>&nbsp;<td>'.$what.'</td>');                  $r->print('<table border=2>'.
                 $r->print('<td>'.join('</td><td>',                            '<tr><td>&nbsp;<td>'.$row_type.'</td>'.
                                       (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.                            '<td>'.
                                             'abcdefghijklmnopqrstuvwxyz'))).                            join('</td><td>',
                                  (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
                                         'abcdefghijklmnopqrstuvwxyz'))).
                           "</td></tr>\n");                            "</td></tr>\n");
             }              }
             $n++;              $rows_output++;
             $r->print($thisrow);              $r->print($row_html);
         }          }
     }      }
     $r->print($ENV{'form.showcsv'}?'</pre>':'</table>');      #
       $r->print('</table>');
       #
       # Debugging code (be sure to uncomment errorlog code in safe space):
       #
       # $r->print("\n<pre>");
       # $r->print(&geterrorlog($sheet));
       # $r->print("\n</pre>");
       return 1;
 }  }
   
 #  ############################################
 # ----------------------------------------------- Read list of available sheets  ##         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($rowlabel));
           foreach my $cell (@rowdata) {
               push (@Values,'"'.$cell->{'value'}.'"');
           }
           $csvdata.= join(',',@Values)."\n";
           @Values = ();
       }
       #
       # Write the CSV data to a file and serve up a link
       #
       my $filename = '/prtspool/'.
           $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
           time.'_'.rand(1000000000).'.csv';
       my $file;
       unless ($file = Apache::File->new('>'.'/home/httpd'.$filename)) {
           $r->log_error("Couldn't open $filename for output $!");
           $r->print("Problems occured in writing the csv file.  ".
                     "This error has been logged.  ".
                     "Please alert your LON-CAPA administrator.");
           $r->print("<pre>\n".$csvdata."</pre>\n");
           return 0;
       }
       print $file $csvdata;
       close($file);
       $r->print('<br /><br />'.
                 '<a href="'.$filename.'">Your CSV spreadsheet.</a>'."\n");
       #
       return 1;
   }
   
   ############################################
   ##        Excel output routines           ##
   ############################################
   sub outsheet_recursive_excel {
       my ($sheet,$r) = @_;
       my $c = $r->connection;
       return undef if ($sheet->{'sheettype'} ne 'classcalc');
       my ($workbook,$filename) = &create_excel_spreadsheet($sheet,$r);
       return undef if (! defined($workbook));
       #
       # Create main worksheet
       my $main_worksheet = $workbook->addworksheet('main');
       #
       # Figure out who the students are
       my %f=&getformulas($sheet);
       my $count = 0;
       $r->print(<<END);
   <p>
   Compiling Excel Workbook with a worksheet for each student.
   </p><p>
   This operation may take longer than a complete recalculation of the
   spreadsheet. 
   </p><p>
   To abort this operation, hit the stop button on your browser.
   </p><p>
   A link to the spreadsheet will be available at the end of this process.
   </p>
   <p>
   END
       $r->rflush();
       my $starttime = time;
       foreach (keys(%f)) {
    next if ($_!~/^A(\d+)/ || $1 == 0 || ($f{$_}=~/^[!~-]/));
           $count++;
           my ($sname,$sdom) = split(':',$f{$_});
           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;
           }
       }
       #
       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);
   }
   
   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($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($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;
   }
   
   ############################################
   ##          XML output routines           ##
   ############################################
   sub outsheet_xml   {
       my ($sheet,$r) = @_;
       ## Someday XML
       ## Will be rendered for the user
       ## But not on this day
   }
   
   ##
   ## Outsheet - calls other outsheet_* functions
   ##
   sub outsheet {
       my ($sheet,$r)=@_;
       if (! exists($ENV{'form.output'})) {
           $ENV{'form.output'} = 'HTML';
       }
       if (lc($ENV{'form.output'}) eq 'csv') {
           &outsheet_csv($sheet,$r);
       } elsif (lc($ENV{'form.output'}) eq 'excel') {
           &outsheet_excel($sheet,$r);
       } elsif (lc($ENV{'form.output'}) eq 'recursive excel') {
           &outsheet_recursive_excel($sheet,$r);
   #    } elsif (lc($ENV{'form.output'}) eq 'xml' ) {
   #        &outsheet_xml($sheet,$r);
       } else {
           &outsheet_html($sheet,$r);
       }
   }
   
   ########################################################################
   ########################################################################
 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 1355  sub othersheets { Line 1730  sub othersheets {
     return @alternatives;       return @alternatives; 
 }  }
   
   
 #  #
 # -------------------------------------- Parse a spreadsheet  # -------------------------------------- Parse a spreadsheet
 #   # 
Line 1387  sub parse_sheet { Line 1761  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)) {      if (! defined($fn)) {
         # There is no filename. Look for defaults in course and global, cache          # There is no filename. Look for defaults in course and global, cache
         unless ($fn=$defaultsheets{$cnum.'_'.$cdom.'_'.$stype}) {          unless ($fn=$defaultsheets{$cnum.'_'.$cdom.'_'.$stype}) {
Line 1414  sub readsheet { Line 1788  sub readsheet {
         }          }
     }      }
     # $fn now has a value      # $fn now has a value
     &setfilename($safeeval,$fn);      $sheet->{'filename'} = $fn;
     # see if sheet is cached      # see if sheet is cached
     my $fstring='';      my $fstring='';
     if ($fstring=$spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}) {      if ($fstring=$spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}) {
         &setformulas($safeeval,split(/\_\_\_\;\_\_\_/,$fstring));          my %tmp = split(/___;___/,$fstring);
           $sheet->{'f'} = \%tmp;
           &setformulas($sheet);
     } else {      } else {
         # Not cached, need to read          # Not cached, need to read
         my %f=();          my %f=();
Line 1430  sub readsheet { Line 1806  sub readsheet {
             if ($fh=Apache::File->new($includedir.'/'.$dfn)) {              if ($fh=Apache::File->new($includedir.'/'.$dfn)) {
                 $sheetxml=join('',<$fh>);                  $sheetxml=join('',<$fh>);
             } else {              } else {
                 $sheetxml='<field row="0" col="A">"Error"</field>';                  # $sheetxml='<field row="0" col="A">"Error"</field>';
                   $sheetxml='<field row="0" col="A"></field>';
             }              }
             %f=%{&parse_sheet(\$sheetxml)};              %f=%{&parse_sheet(\$sheetxml)};
         } elsif($fn=~/\/*\.spreadsheet$/) {          } elsif($fn=~/\/*\.spreadsheet$/) {
Line 1453  sub readsheet { Line 1830  sub readsheet {
         }          }
         # Cache and set          # Cache and set
         $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f);            $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f);  
         &setformulas($safeeval,%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
         my $sheetdata='';  
         foreach (keys(%f)) {          foreach (keys(%f)) {
             unless ($f{$_} eq 'import') {              delete($f{$_}) if ($f{$_} eq 'import');
                 $sheetdata.=&Apache::lonnet::escape($_).'='.  
                     &Apache::lonnet::escape($f{$_}).'&';  
             }  
         }          }
         $sheetdata=~s/\&$//;          my $reply = &Apache::lonnet::put($fn,\%f,$cdom,$cnum);
         my $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'.$fn.':'.  
                                          $sheetdata,$chome);  
         if ($reply eq 'ok') {          if ($reply eq 'ok') {
             $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'.              $reply = &Apache::lonnet::put($stype.'_spreadsheets',
                                           $stype.'_spreadsheets:'.                              {$fn => $ENV{'user.name'}.'@'.$ENV{'user.domain'}},
                                           &Apache::lonnet::escape($fn).                                            $cdom,$cnum);
                                           '='.$ENV{'user.name'}.'@'.  
                                           $ENV{'user.domain'},  
                                           $chome);  
             if ($reply eq 'ok') {              if ($reply eq 'ok') {
                 if ($makedef) {                   if ($makedef) { 
                     return &Apache::lonnet::reply('put:'.$cdom.':'.$cnum.                      return &Apache::lonnet::put('environment',
                                                   ':environment:'.                                    {'spreadsheet_default_'.$stype => $fn },
                                                   'spreadsheet_default_'.                                                  $cdom,$cnum);
                                                   $stype.'='.  
                                                   &Apache::lonnet::escape($fn),  
                                                   $chome);  
                 }                   } 
                 return $reply;                  return $reply;
             }               } 
Line 1531  sub writesheet { Line 1915  sub writesheet {
 # ----------------------------------------------- Make a temp copy of the sheet  # ----------------------------------------------- Make a temp copy of the sheet
 # "Modified workcopy" - interactive only  # "Modified workcopy" - interactive only
 #  #
   
 sub tmpwrite {  sub tmpwrite {
     my $safeeval=shift;      my ($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));   print $fh join("\n",&getformulas($sheet));
     }      }
 }  }
   
 # ---------------------------------------------------------- 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;
Line 1572  sub tmpread { Line 1954  sub tmpread {
         }          }
     }      }
     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};
         }          }
Line 1587  sub tmpread { Line 1969  sub tmpread {
     } else {      } else {
        if ($nfield) { $fo{$nfield}=$nform; }         if ($nfield) { $fo{$nfield}=$nform; }
     }      }
     &setformulas($safeeval,%fo);      $sheet->{'f'}=\%fo;
       &setformulas($sheet);
 }  }
   
 ##################################################  ##################################################
Line 1599  sub tmpread { Line 1982  sub tmpread {
   
 Determine the value of a parameter.  Determine the value of a parameter.
   
 Inputs: $what, the parameter needed, $safeeval, the safe space  Inputs: $what, the parameter needed, $sheet, the safe space
   
 Returns: The value of a parameter, or '' if none.  Returns: The value of a parameter, or '' if none.
   
Line 1615  this user and course. Line 1998  this user and course.
 ##################################################  ##################################################
 ##################################################  ##################################################
 sub parmval {  sub parmval {
     my ($what,$safeeval)=@_;      my ($what,$sheet)=@_;
     my $symb  = &getusymb($safeeval);      my $symb  = $sheet->{'usymb'};
     unless ($symb) { return ''; }      unless ($symb) { return ''; }
     #      #
     my $cid   = &getcid($safeeval);      my $cid   = $sheet->{'cid'};
     my $csec  = &getcsec($safeeval);      my $csec  = $sheet->{'csec'};
     my $uname = &getuname($safeeval);      my $uname = $sheet->{'uname'};
     my $udom  = &getudom($safeeval);      my $udom  = $sheet->{'udom'};
     my $result='';      my $result='';
     #      #
     my ($mapname,$id,$fn)=split(/\_\_\_/,$symb);      my ($mapname,$id,$fn)=split(/\_\_\_/,$symb);
Line 1643  sub parmval { Line 2026  sub parmval {
     my $courselevelr = $usercourseprefix.'.'.$symbparm;      my $courselevelr = $usercourseprefix.'.'.$symbparm;
     my $courselevelm = $usercourseprefix.'.'.$mapparm;      my $courselevelm = $usercourseprefix.'.'.$mapparm;
     # fourth, check user      # fourth, check user
     if ($uname) {       if (defined($uname)) {
         return $useropt{$courselevelr} if ($useropt{$courselevelr});          return $useropt{$courselevelr} if (defined($useropt{$courselevelr}));
         return $useropt{$courselevelm} if ($useropt{$courselevelm});          return $useropt{$courselevelm} if (defined($useropt{$courselevelm}));
         return $useropt{$courselevel} if ($useropt{$courselevel});          return $useropt{$courselevel}  if (defined($useropt{$courselevel}));
     }      }
     # third, check course      # third, check course
     if ($csec) {      if (defined($csec)) {
         return $courseopt{$seclevelr} if ($courseopt{$seclevelr});          return $courseopt{$seclevelr} if (defined($courseopt{$seclevelr}));
         return $courseopt{$seclevelm} if ($courseopt{$seclevelm});          return $courseopt{$seclevelm} if (defined($courseopt{$seclevelm}));
         return $courseopt{$seclevel} if ($courseopt{$seclevel});          return $courseopt{$seclevel}  if (defined($courseopt{$seclevel}));
     }      }
     #      #
     return $courseopt{$courselevelr} if ($courseopt{$courselevelr});      return $courseopt{$courselevelr} if (defined($courseopt{$courselevelr}));
     return $courseopt{$courselevelm} if ($courseopt{$courselevelm});      return $courseopt{$courselevelm} if (defined($courseopt{$courselevelm}));
     return $courseopt{$courselevel} if ($courseopt{$courselevel});      return $courseopt{$courselevel}  if (defined($courseopt{$courselevel}));
     # second, check map parms      # second, check map parms
     my $thisparm = $parmhash{$symbparm};      my $thisparm = $parmhash{$symbparm};
     return $thisparm if ($thisparm);      return $thisparm if (defined($thisparm));
     # first, check default      # first, check default
     return &Apache::lonnet::metadata($fn,$rwhat.'.default');      return &Apache::lonnet::metadata($fn,$rwhat.'.default');
 }  }
   
   
   ##################################################################
   ##                  Row label formatting routines               ##
   ##################################################################
   sub format_html_rowlabel {
       my $rowlabel = shift;
       return '' if ($rowlabel eq '');
       my ($type,$labeldata) = split(':',$rowlabel,2);
       my $result = '';
       if ($type eq 'symb') {
           my ($symb,$uname,$udom,$mapid,$resid,$title) = split(':',$labeldata);
           $symb = &Apache::lonnet::unescape($symb);
           $result = '<a href="/adm/assesscalc?usymb='.$symb.
               '&uname='.$uname.'&udom='.$udom.
                   '&mapid='.$mapid.'&resid='.$resid.'">'.$title.'</a>';
       } elsif ($type eq 'student') {
           my ($sname,$sdom,$fullname,$section,$id) = split(':',$labeldata);
           $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 $rowlabel = shift;
       return '' if ($rowlabel eq '');
       my ($type,$labeldata) = split(':',$rowlabel,2);
       my $result = '';
       if ($type eq 'symb') {
           my ($symb,$uname,$udom,$mapid,$resid,$title) = split(':',$labeldata);
           $symb = &Apache::lonnet::unescape($symb);
           $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 $rowlabel = shift;
       return '' if ($rowlabel eq '');
       my ($type,$labeldata) = split(':',$rowlabel,2);
       my $result = '';
       if ($type eq 'symb') {
           my ($symb,$uname,$udom,$mapid,$resid,$title) = split(':',$labeldata);
           $symb = &Apache::lonnet::unescape($symb);
           $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'};
       #
       %Section = ();
     #      #
     # Read class list and row labels      # Read class list and row labels
     my %classlist;      my $classlist = &Apache::loncoursedata::get_classlist();
     my @tmp = &Apache::lonnet::dump('classlist',$cdom,$cnum);      if (! defined($classlist)) {
     if ($tmp[0] !~ /^error/) {          return 'Could not access course classlist';
         %classlist = @tmp;      } 
     } else {  
         return 'Could not access course data';  
     }  
     undef @tmp;  
     #      #
     my %currentlist=();      my %currentlist=();
     my $now=time;      foreach my $student (keys(%$classlist)) {
     foreach my $student (keys(%classlist)) {          my ($studentDomain,$studentName,$end,$start,$id,$studentSection,
         my ($end,$start)=split(/\:/,$classlist{$student});              $fullname,$status)   =   @{$classlist->{$student}};
         my $active=1;          $Section{$studentName.':'.$studentDomain} = $studentSection;
         $active = 0 if (($end) && ($now>$end));          if ($ENV{'form.Status'} eq $status || $ENV{'form.Status'} eq 'Any') {
         $active = 1 if ($ENV{'form.Status'} eq 'Any');              $currentlist{$student}=join(':',('student',$studentName,
         $active = !$active if ($ENV{'form.Status'} eq 'Expired');                                               $studentDomain,$fullname,
         if ($active) {                                               $studentSection,$id));
             my $rowlabel='';          }
             my ($studentName,$studentDomain)=split(/\:/,$student);      }
             my $studentSection=&Apache::lonnet::usection($studentDomain,  
                                                          $studentName,$cid);  
             if ($studentSection==-1) {  
                 unless ($ENV{'form.showcsv'}) {  
                     $rowlabel='<font color=red>Data not available: '.  
                         $studentName.'</font>';  
                 } else {  
                     $rowlabel='ERROR","'.$studentName.  
                         '","Data not available","","","';  
                 }  
             } else {  
                 my %reply=&Apache::lonnet::idrget($studentDomain,$studentName);  
                 my %studentInformation=&Apache::lonnet::get  
                     ('environment',  
                      ['lastname','generation','firstname','middlename','id'],  
                      $studentDomain,$studentName);  
                 if (! $ENV{'form.showcsv'}) {  
                     $rowlabel='<a href="/adm/studentcalc?uname='.$studentName.  
                         '&udom='.$studentDomain.'">'.  
                             $studentSection.'&nbsp;';  
                     foreach ('id','firstname','middlename',  
                              'lastname','generation'){  
                         $rowlabel.=$studentInformation{$_}."&nbsp;";  
                     }  
                     $rowlabel.='</a>';  
                 } else {  
                     $rowlabel= '"'.join('","',  
                                         ($studentSection,  
                                          $studentInformation{'id'},  
                                          $studentInformation{'firstname'},  
                                          $studentInformation{'middlename'},  
                                          $studentInformation{'lastname'},  
                                          $studentInformation{'generation'})  
                                         ).'"';  
                 }  
             }  
             $currentlist{$student}=$rowlabel;  
         } # end of if ($active)  
     } # end of foreach my $student (keys(%classlist))  
     #      #
     # Find discrepancies between the course row table and this      # Find discrepancies between the course row table and this
     #      #
     my %f=&getformulas($safeeval);      my %f=&getformulas($sheet);
     my $changed=0;      my $changed=0;
     #      #
     my $maxrow=0;      $sheet->{'maxrow'}=0;
     my %existing=();      my %existing=();
     #      #
     # Now obsolete rows      # Now obsolete rows
     foreach (keys(%f)) {      foreach (keys(%f)) {
         if ($_=~/^A(\d+)/) {          if ($_=~/^A(\d+)/) {
             $maxrow=($1>$maxrow)?$1:$maxrow;              if ($1 > $sheet->{'maxrow'}) {
                   $sheet->{'maxrow'}= $1;
               }
             $existing{$f{$_}}=1;              $existing{$f{$_}}=1;
             unless ((defined($currentlist{$f{$_}})) || (!$1) ||              unless ((defined($currentlist{$f{$_}})) || (!$1) ||
                     ($f{$_}=~/^(\~\~\~|\-\-\-)/)) {                      ($f{$_}=~/^(~~~|---)/)) {
                 $f{$_}='!!! Obsolete';                  $f{$_}='!!! Obsolete';
                 $changed=1;                  $changed=1;
             }              }
Line 1756  sub updateclasssheet { Line 2173  sub updateclasssheet {
     }      }
     #      #
     # 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); }      if ($changed) { 
           $sheet->{'f'} = \%f;
           &setformulas($sheet,%f); 
       }
     #      #
     &setmaxrow($safeeval,$maxrow);      &setrowlabels($sheet,\%currentlist);
     &setrowlabels($safeeval,%currentlist);  
 }  }
   
 # ----------------------------------- Update rows for student and assess sheets  # ----------------------------------- Update rows for student and assess sheets
 sub updatestudentassesssheet {  sub get_student_rowlabels {
     my $safeeval=shift;      my ($sheet) = @_;
     my %bighash;      #
     my $stype=&gettype($safeeval);      my %course_db;
     my %current=();      #
     if  ($updatedata{$ENV{'request.course.fn'}.'_'.$stype}) {      my $stype = $sheet->{'sheettype'};
         %current=split(/\_\_\_\;\_\_\_/,      my $uname = $sheet->{'uname'};
        $updatedata{$ENV{'request.course.fn'}.'_'.$stype});      my $udom  = $sheet->{'udom'};
       #
       $sheet->{'rowlabel'} = {};
       #
       my $identifier =$sheet->{'coursefilename'}.'_'.$stype;
       if  ($rowlabel_cache{$identifier}) {
           %{$sheet->{'rowlabel'}}=split(/___;___/,$rowlabel_cache{$identifier});
     } else {      } else {
           # Get the data and store it in the cache
         # Tie hash          # Tie hash
         tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db',          tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db',
             &GDBM_READER(),0640);              &GDBM_READER(),0640);
         if (! tied(%bighash)) {          if (! tied(%course_db)) {
             return 'Could not access course data';              return 'Could not access course data';
         }          }
         # Get all assessments          #
         my %allkeys=('timestamp' =>           my %assesslist;
                      'Timestamp of Last Transaction<br>timestamp',          foreach ('Feedback','Evaluation','Tutoring','Discussion') {
                      'subnumber' =>              my $symb = '_'.lc($_);
                      'Number of Submissions<br>subnumber',              $assesslist{$symb} = join(':',('symb',$symb,$uname,$udom,0,0,$_));
                      'tutornumber' =>          }
                      'Number of Tutor Responses<br>tutornumber',          #
                      'totalpoints' =>          while (my ($key,$srcf) = each(%course_db)) {
                      'Total Points Granted<br>totalpoints');              next if ($key !~ /^src_(\d+)\.(\d+)$/);
         my $adduserstr='';              my $mapid = $1;
         if ((&getuname($safeeval) ne $ENV{'user.name'}) ||              my $resid = $2;
             (&getudom($safeeval) ne $ENV{'user.domain'})) {              my $id   = $mapid.'.'.$resid;
             $adduserstr='&uname='.&getuname($safeeval).  
                 '&udom='.&getudom($safeeval);  
         }  
         my %allassess =  
             ('_feedback' =>'<a href="/adm/assesscalc?usymb=_feedback'.  
              $adduserstr.'">Feedback</a>',  
              '_evaluation' =>'<a href="/adm/assesscalc?usymb=_evaluation'.  
              $adduserstr.'">Evaluation</a>',  
              '_tutoring' =>'<a href="/adm/assesscalc?usymb=_tutoring'.  
              $adduserstr.'">Tutoring</a>',  
              '_discussion' =>'<a href="/adm/assesscalc?usymb=_discussion'.  
              $adduserstr.'">Discussion</a>'  
              );  
         foreach (keys(%bighash)) {  
             next if ($_!~/^src\_(\d+)\.(\d+)$/);  
             my $mapid=$1;  
             my $resid=$2;  
             my $id=$mapid.'.'.$resid;  
             my $srcf=$bighash{$_};  
             if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) {              if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) {
                 my $symb=                  my $symb=
                     &Apache::lonnet::declutter($bighash{'map_id_'.$mapid}).                      &Apache::lonnet::declutter($course_db{'map_id_'.$mapid}).
                         '___'.$resid.'___'.&Apache::lonnet::declutter($srcf);                          '___'.$resid.'___'.&Apache::lonnet::declutter($srcf);
                 $allassess{$symb}=                  $assesslist{$symb}='symb:'.&Apache::lonnet::escape($symb).':'
                     '<a href="/adm/assesscalc?usymb='.$symb.$adduserstr.'">'.                      .$uname.':'.$udom.':'.$mapid.':'.$resid.':'.
                         $bighash{'title_'.$id}.'</a>';                          $course_db{'title_'.$id};
                 next if ($stype ne 'assesscalc');              }
                 foreach my $key (split(/\,/,          }
                                        &Apache::lonnet::metadata($srcf,'keys')          untie(%course_db);
                                        )) {          # Store away the data
           $sheet->{'rowlabel'} = \%assesslist;
           $rowlabel_cache{$identifier}=join('___;___',%{$sheet->{'rowlabel'}});
       }
   
   }
   
   sub get_assess_rowlabels {
       my ($sheet) = @_;
       #
       my %course_db;
       #
       my $stype = $sheet->{'sheettype'};
       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)_/);                      next if ($key !~ /^(stores|parameter)_/);
                     my $display=                      my $display=
                         &Apache::lonnet::metadata($srcf,$key.'.display');                          &Apache::lonnet::metadata($srcf,$key.'.display');
Line 1835  sub updatestudentassesssheet { Line 2291  sub updatestudentassesssheet {
                             &Apache::lonnet::metadata($srcf,$key.'.name');                              &Apache::lonnet::metadata($srcf,$key.'.name');
                     }                      }
                     $display.='<br>'.$key;                      $display.='<br>'.$key;
                     $allkeys{$key}=$display;                      $parameter_labels{$key}='parameter:'.$display;
                 } # end of foreach                  } # end of foreach
             }              }
         } # end of foreach (keys(%bighash))          }
         untie(%bighash);          untie(%course_db);
         #          # Store away the results
         # %allkeys has a list of storage and parameter displays by unikey          $sheet->{'rowlabel'} = \%parameter_labels;
         # %allassess has a list of all resource displays by symb          $rowlabel_cache{$identifier}=join('___;___',%{$sheet->{'rowlabel'}});
         #  
         if ($stype eq 'assesscalc') {  
             %current=%allkeys;  
         } elsif ($stype eq 'studentcalc') {  
             %current=%allassess;  
         }  
         $updatedata{$ENV{'request.course.fn'}.'_'.$stype}=  
             join('___;___',%current);  
         # Get current from cache  
     }      }
     # Find discrepancies between the course row table and this          
     #  }
     my %f=&getformulas($safeeval);  
   sub updatestudentassesssheet {
       my $sheet = shift;
       if ($sheet->{'sheettype'} eq 'studentcalc') {
           &get_student_rowlabels($sheet);
       } else {
           &get_assess_rowlabels($sheet);
       }
       # Determine if any of the information has changed
       my %f=&getformulas($sheet);
     my $changed=0;      my $changed=0;
           
     my $maxrow=0;      $sheet->{'maxrow'} = 0;
     my %existing=();      my %existing=();
     # Now obsolete rows      # Now obsolete rows
     foreach (keys(%f)) {      while (my ($cell, $formula) = each (%f)) {
         next if ($_!~/^A(\d+)/);          next if ($cell !~ /^A(\d+)/);
         $maxrow=($1>$maxrow)?$1:$maxrow;          $sheet->{'maxrow'} = $1 if ($1 > $sheet->{'maxrow'});
         my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{$_});          my ($usy,$ufn)=split(/__&&&\__/,$formula);
         $existing{$usy}=1;          $existing{$usy}=1;
         unless ((defined($current{$usy})) || (!$1) ||          unless ((exists($sheet->{'rowlabel'}->{$usy}) && 
                 ($f{$_}=~/^(\~\~\~|\-\-\-)/)){                   (defined($sheet->{'rowlabel'}->{$usy})) || (!$1) ||
                    ($formula =~ /^(~~~|---)/) )) {
             $f{$_}='!!! Obsolete';              $f{$_}='!!! Obsolete';
             $changed=1;              $changed=1;
         } elsif ($ufn) {          } elsif ($ufn) {
             $current{$usy}              # I do not think this works any more
             =~s/assesscalc\?usymb\=/assesscalc\?ufn\=$ufn\&usymb\=/;              $sheet->{'rowlabel'}->{$usy}
                   =~s/assesscalc\?usymb\=/assesscalc\?ufn\=$ufn&\usymb\=/;
         }          }
     }      }
     # New and unknown keys      # New and unknown keys
     foreach (keys(%current)) {      foreach (keys(%{$sheet->{'rowlabel'}})) {
         unless ($existing{$_}) {          unless ($existing{$_}) {
             $changed=1;              $changed=1;
             $maxrow++;              $sheet->{'maxrow'}++;
             $f{'A'.$maxrow}=$_;              $f{'A'.$sheet->{'maxrow'}}=$_;
         }          }
     }      }
     if ($changed) { &setformulas($safeeval,%f); }      if ($changed) { 
     &setmaxrow($safeeval,$maxrow);          $sheet->{'f'} = \%f;
     &setrowlabels($safeeval,%current);          &setformulas($sheet); 
     #      }
     undef %current;  
     undef %existing;  
 }  }
   
 # ------------------------------------------------ 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'};
     # Get ALL the student preformance data      # Get ALL the student preformance data
     my @tmp = &Apache::lonnet::dump(&getcid($safeeval),      my @tmp = &Apache::lonnet::dump($sheet->{'cid'},
                                     &getudom($safeeval),                                      $sheet->{'udom'},
                                     &getuname($safeeval),                                      $sheet->{'uname'},
                                     undef);                                      undef);
     if ($tmp[0] !~ /^error:/) {      if ($tmp[0] !~ /^error:/) {
         %cachedstores = @tmp;          %cachedstores = @tmp;
Line 1909  sub loadstudent { Line 2365  sub loadstudent {
     undef @tmp;      undef @tmp;
     #       # 
     my @assessdata=();      my @assessdata=();
     foreach (keys(%f)) {      while (my ($cell,$value) = each (%formulas)) {
  next if ($_!~/^A(\d+)/);          if(defined($c) && ($c->aborted())) {
               last;
           }
    next if ($cell !~ /^A(\d+)/);
         my $row=$1;          my $row=$1;
         next if (($f{$_}=~/^[\!\~\-]/) || ($row==0));          next if (($value =~ /^[!~-]/) || ($row==0));
         my ($usy,$ufn)=split(/__&&&\__/,$f{$_});          my ($usy,$ufn)=split(/__&&&\__/,$value);
         @assessdata=&exportsheet(&getuname($safeeval),          @assessdata=&exportsheet($sheet,$sheet->{'uname'},
                                  &getudom($safeeval),                                   $sheet->{'udom'},
                                  'assesscalc',$usy,$ufn);                                   'assesscalc',$usy,$ufn,$r);
         my $index=0;          my $index=0;
         foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',          foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',
                  'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') {                   'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') {
             if ($assessdata[$index]) {              if (defined($assessdata[$index])) {
                 my $col=$_;                  my $col=$_;
                 if ($assessdata[$index]=~/\D/) {                  if ($assessdata[$index]=~/\D/) {
                     $c{$col.$row}="'".$assessdata[$index]."'";                      $constants{$col.$row}="'".$assessdata[$index]."'";
                 } else {                  } else {
                     $c{$col.$row}=$assessdata[$index];                      $constants{$col.$row}=$assessdata[$index];
                 }                  }
                 unless ($col eq 'A') {                   unless ($col eq 'A') { 
                     $f{$col.$row}='import';                      $formulas{$col.$row}='import';
                 }                  }
             }              }
             $index++;              $index++;
Line 1936  sub loadstudent { Line 2395  sub loadstudent {
     }      }
     $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 1966  sub loadcourse { Line 2428  sub loadcourse {
 </script>  </script>
 ENDPOP  ENDPOP
     $r->rflush();      $r->rflush();
     foreach (keys(%f)) {      foreach (keys(%formulas)) {
           if(defined($c) && ($c->aborted())) {
               last;
           }
  next if ($_!~/^A(\d+)/);   next if ($_!~/^A(\d+)/);
         my $row=$1;          my $row=$1;
         next if (($f{$_}=~/^[\!\~\-]/)  || ($row==0));          next if (($formulas{$_}=~/^[\!\~\-]/)  || ($row==0));
         my @studentdata=&exportsheet(split(/\:/,$f{$_}),          my ($sname,$sdom) = split(':',$formulas{$_});
                                      'studentcalc');          my @studentdata=&exportsheet($sheet,$sname,$sdom,'studentcalc',
                                        undef,undef,$r);
         undef %userrdatas;          undef %userrdatas;
         $now++;          $now++;
         $r->print('<script>popwin.document.popremain.remaining.value="'.          $r->print('<script>popwin.document.popremain.remaining.value="'.
Line 1982  ENDPOP Line 2448  ENDPOP
         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 ''; }
Line 2035  sub loadassessment { Line 2502  sub loadassessment {
         #          #
         # restore individual          # restore individual
         #          #
         my $answer=&Apache::lonnet::reply(          %returnhash = &Apache::lonnet::restore($symb,$namespace,$udom,$uname);
                                           "restore:$udom:$uname:".          for (my $version=1;$version<=$returnhash{'version'};$version++) {
                                           &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'})) {              foreach (split(/\:/,$returnhash{$version.':keys'})) {
                 $returnhash{$_}=$returnhash{$version.':'.$_};                  $returnhash{$_}=$returnhash{$version.':'.$_};
             }               } 
         }          }
     }      }
       #
     # returnhash now has all stores for this resource      # returnhash now has all stores for this resource
     # convert all "_" to "." to be able to use libraries, multiparts, etc      # convert all "_" to "." to be able to use libraries, multiparts, etc
       #
       # This is dumb.  It is also necessary :(
     my @oldkeys=keys %returnhash;      my @oldkeys=keys %returnhash;
       #
     foreach (@oldkeys) {      foreach my $name (@oldkeys) {
         my $name=$_;          my $value=$returnhash{$name};
         my $value=$returnhash{$_};          delete $returnhash{$name};
         delete $returnhash{$_};  
         $name=~s/\_/\./g;          $name=~s/\_/\./g;
         $returnhash{$name}=$value;          $returnhash{$name}=$value;
     }      }
Line 2067  sub loadassessment { Line 2527  sub loadassessment {
     undef %useropt;      undef %useropt;
   
     my $userprefix=$uname.'_'.$udom.'_';      my $userprefix=$uname.'_'.$udom.'_';
       
     unless ($uhome eq 'no_host') {       unless ($uhome eq 'no_host') { 
         # Get coursedata          # Get coursedata
         unless          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);              $courserdatas{$cid.'.last_cache'}=time;
                 if ($reply!~/^error\:/) {          }
                     $courserdatas{$cid}=$reply;          while (my ($name,$value) = each(%{$courserdatas{$cid}})) {
                     $courserdatas{$cid.'.last_cache'}=time;              $courseopt{$userprefix.$name}=$value;
                 }  
             }  
         foreach (split(/\&/,$courserdatas{$cid})) {  
             my ($name,$value)=split(/\=/,$_);  
             $courseopt{$userprefix.&Apache::lonnet::unescape($name)}=  
                 &Apache::lonnet::unescape($value);    
         }          }
         # Get userdata (if present)          # Get userdata (if present)
         unless          unless ((time-$userrdatas{$uname.'@'.$udom.'.last_cache'})<240) {
             ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) {              my %Tmp = &Apache::lonnet::dump('resourcedata',$udom,$uname);
                 my $reply=              $userrdatas{$cid} = \%Tmp;
                     &Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome);              # Most of the time the user does not have a 'resourcedata.db' 
                 if ($reply!~/^error\:/) {              # file.  We need to cache that we got nothing instead of bothering
                     $userrdatas{$uname.'___'.$udom}=$reply;              # with requesting it every time.
                     $userrdatas{$uname.'___'.$udom.'.last_cache'}=time;              $userrdatas{$uname.'@'.$udom.'.last_cache'}=time;
                 }          }
             }          while (my ($name,$value) = each(%{$userrdatas{$cid}})) {
         foreach (split(/\&/,$userrdatas{$uname.'___'.$udom})) {              $useropt{$userprefix.$name}=$value;
             my ($name,$value)=split(/\=/,$_);  
             $useropt{$userprefix.&Apache::lonnet::unescape($name)}=  
                 &Apache::lonnet::unescape($value);  
         }          }
     }      }
     # now courseopt, useropt initialized for this user and course      # now courseopt, useropt initialized for this user and course
Line 2115  sub loadassessment { Line 2566  sub loadassessment {
     #      #
     my %c=();      my %c=();
     if (tie(%parmhash,'GDBM_File',      if (tie(%parmhash,'GDBM_File',
             &getcfn($safeeval).'_parms.db',&GDBM_READER(),0640)) {              $sheet->{'coursefilename'}.'_parms.db',&GDBM_READER(),0640)) {
         my %f=&getformulas($safeeval);          my %f=&getformulas($sheet);
         foreach (keys(%f))  {          foreach my $cell (keys(%f))  {
             next if ($_!~/^A/);              next if ($cell !~ /^A/);
             next if  ($f{$_}=~/^[\!\~\-]/);              next if  ($f{$cell} =~/^[\!\~\-]/);
             if ($f{$_}=~/^parameter/) {              if ($f{$cell}=~/^parameter/) {
                 if ($thisassess{$f{$_}}) {                  if (defined($thisassess{$f{$cell}})) {
                     my $val=&parmval($f{$_},$safeeval);                      my $val       = &parmval($f{$cell},$sheet);
                     $c{$_}=$val;                      $c{$cell}     = $val;
                     $c{$f{$_}}=$val;                      $c{$f{$cell}} = $val;
                 }                  }
             } else {              } else {
                 my $key=$f{$_};                  my $key=$f{$cell};
                 my $ckey=$key;                  my $ckey=$key;
                 $key=~s/^stores\_/resource\./;                  $key=~s/^stores\_/resource\./;
                 $key=~s/\_/\./g;                  $key=~s/\_/\./g;
                 $c{$_}=$returnhash{$key};                  $c{$cell}=$returnhash{$key};
                 $c{$ckey}=$returnhash{$key};                  $c{$ckey}=$returnhash{$key};
             }              }
         }          }
         untie(%parmhash);          untie(%parmhash);
     }      }
     &setconstants($safeeval,%c);      &setconstants($sheet,\%c);
 }  }
   
 # --------------------------------------------------------- Various form fields  # --------------------------------------------------------- Various form fields
Line 2170  sub selectbox { Line 2621  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 2185  sub updatesheet { Line 2635  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 2200  sub loadrows { Line 2651  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 {
Line 2210  sub forcedrecalc { Line 2665  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 2229  sub forcedrecalc { Line 2684  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)=@_;
       $uname = $uname || $sheet->{'uname'};
       $udom  = $udom  || $sheet->{'udom'};
       $stype = $stype || $sheet->{'sheettype'};
     my @exportarr=();      my @exportarr=();
     if (($usymb=~/^\_(\w+)/) && (!$fn)) {      if (defined($usymb) && ($usymb=~/^\_(\w+)/) && 
           (!defined($fn) || $fn eq '')) {
         $fn='default_'.$1;          $fn='default_'.$1;
     }      }
     #      #
Line 2245  sub exportsheet { Line 2703  sub exportsheet {
     my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;      my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;
     my $found='';      my $found='';
     if ($oldsheets{$key}) {      if ($oldsheets{$key}) {
         foreach (split(/\_\_\_\&\_\_\_/,$oldsheets{$key})) {          foreach (split(/___&\___/,$oldsheets{$key})) {
             my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);              my ($name,$value)=split(/___=___/,$_);
             if ($name eq $fn) {              if ($name eq $fn) {
                 $found=$value;                  $found=$value;
             }              }
         }          }
     }      }
     unless ($found) {      unless ($found) {
         &cachedssheets($uname,$udom,&Apache::lonnet::homeserver($uname,$udom));          &cachedssheets($sheet,$uname,$udom);
         if ($oldsheets{$key}) {          if ($oldsheets{$key}) {
             foreach (split(/\_\_\_\&\_\_\_/,$oldsheets{$key})) {              foreach (split(/___&\___/,$oldsheets{$key})) {
                 my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);                  my ($name,$value)=split(/___=___/,$_);
                 if ($name eq $fn) {                  if ($name eq $fn) {
                     $found=$value;                      $found=$value;
                 }                  }
Line 2275  sub exportsheet { Line 2733  sub exportsheet {
         #          #
         # Return what was cached          # Return what was cached
         #          #
         @exportarr=split(/\_\_\_\;\_\_\_/,$found);          @exportarr=split(/___;___/,$found);
     } else {          return @exportarr;
         #      }
         # Not cached      #
         #              # Not cached
         my $thissheet=&makenewsheet($uname,$udom,$stype,$usymb);      #
         &readsheet($thissheet,$fn);      my ($newsheet)=&makenewsheet($uname,$udom,$stype,$usymb);
         &updatesheet($thissheet);      &readsheet($newsheet,$fn);
         &loadrows($thissheet);      &updatesheet($newsheet);
         &calcsheet($thissheet);       &loadrows($newsheet,$r);
         @exportarr=&exportdata($thissheet);      &calcsheet($newsheet); 
         #      @exportarr=&exportdata($newsheet);
         # Store now      ##
         #      ## Store now
         my $cid=$ENV{'request.course.id'};       ##
         my $current='';      #
         if ($stype eq 'studentcalc') {      # load in the old value
             $current=&Apache::lonnet::reply('get:'.      #
                                             $ENV{'course.'.$cid.'.domain'}.':'.      my %currentlystored=();
                                             $ENV{'course.'.$cid.'.num'}.      if ($stype eq 'studentcalc') {
                                             ':nohist_calculatedsheets:'.          my @tmp = &Apache::lonnet::get('nohist_calculatedsheets',
                                             &Apache::lonnet::escape($key),                                         [$key],
                                             $ENV{'course.'.$cid.'.home'});                                         $sheet->{'cdom'},$sheet->{'cnum'});
         } else {          if ($tmp[0]!~/^error/) {
             $current=&Apache::lonnet::reply('get:'.              %currentlystored = @tmp;
                                             &getudom($thissheet).':'.  
                                             &getuname($thissheet).  
                                             ':nohist_calculatedsheets_'.  
                                             $ENV{'request.course.id'}.':'.  
                                             &Apache::lonnet::escape($key),  
                                             &getuhome($thissheet));  
         }  
         my %currentlystored=();  
         unless ($current=~/^error\:/) {  
             foreach (split(/___&\___/,&Apache::lonnet::unescape($current))) {  
                 my ($name,$value)=split(/___=___/,$_);  
                 $currentlystored{$name}=$value;  
             }  
         }          }
         $currentlystored{$fn}=join('___;___',@exportarr);      } else {
         #          my @tmp = &Apache::lonnet::get('nohist_calculatedsheets_'.
         my $newstore='';                                         $sheet->{'cid'},[$key],
         foreach (keys(%currentlystored)) {                                         $sheet->{'udom'},$sheet->{'uname'});
             if ($newstore) { $newstore.='___&___'; }          if ($tmp[0]!~/^error/) {
             $newstore.=$_.'___=___'.$currentlystored{$_};              %currentlystored = @tmp;
         }  
         my $now=time;  
         if ($stype eq 'studentcalc') {  
             &Apache::lonnet::reply('put:'.  
                                    $ENV{'course.'.$cid.'.domain'}.':'.  
                                    $ENV{'course.'.$cid.'.num'}.  
                                    ':nohist_calculatedsheets:'.  
                                    &Apache::lonnet::escape($key).'='.  
                                    &Apache::lonnet::escape($newstore).'&'.  
                                    &Apache::lonnet::escape($key).'.time='.$now,  
                                    $ENV{'course.'.$cid.'.home'});  
         } else {  
             &Apache::lonnet::reply('put:'.  
                                    &getudom($thissheet).':'.  
                                    &getuname($thissheet).  
                                    ':nohist_calculatedsheets_'.  
                                    $ENV{'request.course.id'}.':'.  
                                    &Apache::lonnet::escape($key).'='.  
                                    &Apache::lonnet::escape($newstore).'&'.  
                                    &Apache::lonnet::escape($key).'.time='.$now,  
                                    &getuhome($thissheet));  
         }          }
     }      }
       #
       # Add the new line
       #
       $currentlystored{$fn}=join('___;___',@exportarr);
       #
       # Stick everything back together
       #
       my $newstore='';
       foreach (keys(%currentlystored)) {
           if ($newstore) { $newstore.='___&___'; }
           $newstore.=$_.'___=___'.$currentlystored{$_};
       }
       my $now=time;
       #
       # Store away the new value
       #
       my $timekey = $key.'.time';
       if ($stype eq 'studentcalc') {
           my $result = &Apache::lonnet::put('nohist_calculatedsheets',
                                             { $key     => $newstore,
                                               $timekey => $now },
                                             $sheet->{'cdom'},
                                             $sheet->{'cnum'});
       } else {
           my $result = &Apache::lonnet::put('nohist_calculatedsheets_'.$sheet->{'cid'},
                                             { $key     => $newstore,
                                               $timekey => $now },
                                             $sheet->{'udom'},
                                             $sheet->{'uname'});
       }
     return @exportarr;      return @exportarr;
 }  }
   
Line 2350  sub exportsheet { Line 2804  sub exportsheet {
 #  #
 # Load previously cached student spreadsheets for this course  # Load previously cached student spreadsheets for this course
 #  #
 sub expirationdates {  sub load_spreadsheet_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 2374  sub expirationdates { Line 2822  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 2394  sub cachedcsheets { Line 2839  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'},                                          $sheet->{'udom'},
                                      $shome);                                          $sheet->{'uname'});
     unless ($reply=~/^error\:/) {          if ($tmp[0] !~ /^error/) {
  foreach ( split(/\&/,$reply)) {              my %TempHash = @tmp;
             my ($name,$value)=split(/\=/,$_);              my $count = 0;
             $oldsheets{&Apache::lonnet::unescape($name)}              while (my ($key,$value) = each %TempHash) {
                       =&Apache::lonnet::unescape($value);                  $oldsheets{$key} = $value;
                   $count++;
               }
               $loadedcaches{$sheet->{'uname'}.'_'.$sheet->{'udom'}}=1;
         }          }
     }      }
     $loadedcaches{$sname.'_'.$sdom}=1;      
   }  
 }  }
   
 # ===================================================== Calculated sheets cache  # ===================================================== Calculated sheets cache
Line 2422  sub cachedssheets { Line 2869  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      # Global directory configs
     $includedir=$r->dir_config('lonIncludes');      #
     $tmpdir=$r->dir_config('lonDaemons').'/tmp/';      $includedir = $r->dir_config('lonIncludes');
       $tmpdir = $r->dir_config('lonDaemons').'/tmp/';
       #
       # Roles Checking
       #
     # Needs to be in a course      # Needs to be in a course
     if ($ENV{'request.course.fn'}) {       if (! $ENV{'request.course.fn'}) { 
         # Get query string for limited number of parameters          # Not in a course, or not allowed to modify parms
         &Apache::loncommon::get_unprocessed_cgi          $ENV{'user.error.msg'}=
             ($ENV{'QUERY_STRING'},['uname','udom','usymb','ufn']);              $r->uri.":opa:0:0:Cannot modify spreadsheet";
         if (($ENV{'form.usymb'}=~/^\_(\w+)/) && (!$ENV{'form.ufn'})) {          return HTTP_NOT_ACCEPTABLE; 
             $ENV{'form.ufn'}='default_'.$1;      }
         }      #
         # Interactive loading of specific sheet?      # Get query string for limited number of parameters
         if (($ENV{'form.load'}) && ($ENV{'form.loadthissheet'} ne 'Default')) {      #
             $ENV{'form.ufn'}=$ENV{'form.loadthissheet'};      &Apache::loncommon::get_unprocessed_cgi
         }          ($ENV{'QUERY_STRING'},['uname','udom','usymb','ufn','mapid','resid']);
         # Nothing there? Must be login user      #
         my $aname;      # Deal with restricted student permissions 
         my $adom;      #
       if ($ENV{'request.role'} =~ /^st\./) {
         unless ($ENV{'form.uname'}) {          delete $ENV{'form.unewfield'}   if (exists($ENV{'form.unewfield'}));
             $aname=$ENV{'user.name'};          delete $ENV{'form.unewformula'} if (exists($ENV{'form.unewformula'}));
             $adom=$ENV{'user.domain'};      }
         } else {      #
             $aname=$ENV{'form.uname'};      # Clean up symb and spreadsheet filename
             $adom=$ENV{'form.udom'};      #
         }      if (($ENV{'form.usymb'}=~/^\_(\w+)/) && (!$ENV{'form.ufn'})) {
         # Open page          $ENV{'form.ufn'}='default_'.$1;
         $r->content_type('text/html');      }
         $r->header_out('Cache-control','no-cache');      #
         $r->header_out('Pragma','no-cache');      # Interactive loading of specific sheet?
         $r->send_http_header;      #
         # Screen output      if (($ENV{'form.load'}) && ($ENV{'form.loadthissheet'} ne 'Default')) {
         $r->print('<html><head><title>LON-CAPA Spreadsheet</title>');          $ENV{'form.ufn'}=$ENV{'form.loadthissheet'};
       }
       #
       # Determine the user name and domain for the sheet.
       my $aname;
       my $adom;
       unless ($ENV{'form.uname'}) {
           $aname=$ENV{'user.name'};
           $adom=$ENV{'user.domain'};
       } else {
           $aname=$ENV{'form.uname'};
           $adom=$ENV{'form.udom'};
       }
       #
       # Open page, try to prevent browser cache.
       #
       $r->content_type('text/html');
       $r->header_out('Cache-control','no-cache');
       $r->header_out('Pragma','no-cache');
       $r->send_http_header;
       #
       # Header....
       #
       $r->print('<html><head><title>LON-CAPA Spreadsheet</title>');
       my $nothing = "''";
       if ($ENV{'browser.type'} eq 'explorer') {
           $nothing = "'javascript:void(0);'";
       }
   
       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;          edit_text +='<html><head><title>Cell Edit Window</title></head><body>';
             document.sheet.submit();          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 2490  sub handler { Line 3016  sub handler {
   
 </script>  </script>
 ENDSCRIPT  ENDSCRIPT
         $r->print('</head>'.&Apache::loncommon::bodytag('Grades Spreadsheet').      }
                   '<form action="'.$r->uri.'" name=sheet method=post>'.      $r->print('</head>'.&Apache::loncommon::bodytag('Grades Spreadsheet').
                   &hiddenfield('uname',$ENV{'form.uname'}).                '<form action="'.$r->uri.'" name="sheet" method="post">');
                   &hiddenfield('udom',$ENV{'form.udom'}).      $r->print(&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','').
         # Send this out right away                &hiddenfield('unewformula',''));
         $r->rflush();      $r->rflush();
         # Full recalc?      #
         if ($ENV{'form.forcerecalc'}) {      # Full recalc?
             $r->print('<h4>Completely Recalculating Sheet ...</h4>');      #
             undef %spreadsheets;      if ($ENV{'form.forcerecalc'}) {
             undef %courserdatas;          $r->print('<h4>Completely Recalculating Sheet ...</h4>');
             undef %userrdatas;          undef %spreadsheets;
             undef %defaultsheets;          undef %courserdatas;
             undef %updatedata;          undef %userrdatas;
         }          undef %defaultsheets;
         # Read new sheet or modified worksheet          undef %rowlabel_cache;
         $r->uri=~/\/(\w+)$/;      }
         my $asheet=&makenewsheet($aname,$adom,$1,$ENV{'form.usymb'});      # Read new sheet or modified worksheet
         # If a new formula had been entered, go from work copy      my ($sheet)=&makenewsheet($aname,$adom,$sheettype,$ENV{'form.usymb'});
         if ($ENV{'form.unewfield'}) {      #
             $r->print('<h2>Modified Workcopy</h2>');      # Check user permissions
             $ENV{'form.unewformula'}=~s/\'/\"/g;      if (($sheet->{'sheettype'} eq 'classcalc'       ) || 
             $r->print('<p>New formula: '.$ENV{'form.unewfield'}.'='.          ($sheet->{'uname'}     ne $ENV{'user.name'} ) ||
                       $ENV{'form.unewformula'}.'<p>');          ($sheet->{'udom'}      ne $ENV{'user.domain'})) {
             &setfilename($asheet,$ENV{'form.ufn'});          unless (&Apache::lonnet::allowed('vgr',$sheet->{'cid'})) {
             &tmpread($asheet,              $r->print('<h1>Access Permission Denied</h1>'.
                      $ENV{'form.unewfield'},$ENV{'form.unewformula'});                        '</form></body></html>');
                           return OK;
         } elsif ($ENV{'form.saveas'}) {          }
             &setfilename($asheet,$ENV{'form.ufn'});      }
             &tmpread($asheet);      # Print out user information
         } else {      $r->print('<h2>'.$sheet->{'coursedesc'}.'</h2>');
             &readsheet($asheet,$ENV{'form.ufn'});      if ($sheet->{'sheettype'} ne 'classcalc') {
         }          $r->print('<h2>'.&gettitle($sheet).'</h2><p>');
         # Print out user information      }
         unless (&gettype($asheet) eq 'classcalc') {      if ($sheet->{'sheettype'} eq 'assesscalc') {
             $r->print('<p><b>User:</b> '.&getuname($asheet).          $r->print('<b>User:</b> '.$sheet->{'uname'}.
                       '<br><b>Domain:</b> '.&getudom($asheet));                    '<br /><b>Domain:</b> '.$sheet->{'udom'}.'<br />');
             if (&getcsec($asheet) eq '-1') {      }
                 $r->print('<h3><font color=red>'.      if ($sheet->{'sheettype'} eq 'studentcalc' || 
                           'Not a student in this course</font></h3>');          $sheet->{'sheettype'} eq 'assesscalc') {
             } else {          $r->print('<b>Section/Group:</b>'.$sheet->{'csec'}.'</p>');
                 $r->print('<br><b>Section/Group:</b> '.&getcsec($asheet));      } 
             }      #
             if ($ENV{'form.usymb'}) {      # If a new formula had been entered, go from work copy
                 $r->print('<br><b>Assessment:</b> <tt>'.      if ($ENV{'form.unewfield'}) {
                           $ENV{'form.usymb'}.'</tt>');          $r->print('<h2>Modified Workcopy</h2>');
             }          $ENV{'form.unewformula'}=~s/\'/\"/g;
         }          $r->print('<p>New formula: '.$ENV{'form.unewfield'}.'='.
         # See if user can see this                    $ENV{'form.unewformula'}.'<p>');
         if ((&gettype($asheet) eq 'classcalc') ||           $sheet->{'filename'} = $ENV{'form.ufn'};
             (&getuname($asheet) ne $ENV{'user.name'}) ||          &tmpread($sheet,$ENV{'form.unewfield'},$ENV{'form.unewformula'});
             (&getudom($asheet) ne $ENV{'user.domain'})) {      } elsif ($ENV{'form.saveas'}) {
             unless (&Apache::lonnet::allowed('vgr',&getcid($asheet))) {          $sheet->{'filename'} = $ENV{'form.ufn'};
                 $r->print('<h1>Access Permission Denied</h1>'.          &tmpread($sheet);
                           '</form></body></html>');      } else {
                 return OK;          &readsheet($sheet,$ENV{'form.ufn'});
             }      }
         }      # Additional options
         # Additional options      if ($sheet->{'sheettype'} eq 'assesscalc') {
         $r->print(          $r->print('<p><font size=+2>'.
  '<input type=submit name=forcerecalc value="Completely Recalculate Sheet"><p>'                    '<a href="/adm/studentcalc?'.
                   );                    'uname='.$sheet->{'uname'}.
         if (&gettype($asheet) eq 'assesscalc') {                    '&udom='.$sheet->{'udom'}.'">'.
             $r->print ('<p><font size=+2><a href="/adm/studentcalc?uname='.                    'Level up: Student Sheet</a></font></p>');
                        &getuname($asheet).'&udom='.&getudom($asheet).'">'.      }
                        'Level up: Student Sheet</a></font><p>');      if (($sheet->{'sheettype'} eq 'studentcalc') && 
         }          (&Apache::lonnet::allowed('vgr',$sheet->{'cid'}))) {
         if ((&gettype($asheet) eq 'studentcalc') &&           $r->print ('<p><font size=+2><a href="/adm/classcalc">'.
             (&Apache::lonnet::allowed('vgr',&getcid($asheet)))) {                     'Level up: Course Sheet</a></font></p>');
             $r->print (      }
                        '<p><font size=+2><a href="/adm/classcalc">'.      # Recalc button
                        'Level up: Course Sheet</a></font><p>');      $r->print('<br />'.
         }                '<input type="submit" name="forcerecalc" '.
         # Save dialog                'value="Completely Recalculate Sheet"></p>');
         if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {      # Save dialog
             my $fname=$ENV{'form.ufn'};      if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
             $fname=~s/\_[^\_]+$//;          my $fname=$ENV{'form.ufn'};
             if ($fname eq 'default') { $fname='course_default'; }          $fname=~s/\_[^\_]+$//;
             $r->print          if ($fname eq 'default') { $fname='course_default'; }
                 ('<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',&getfilename($asheet)));      $r->print(&hiddenfield('ufn',$sheet->{'filename'}));
         # 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('</select><p>');  
             if (&gettype($asheet) eq 'studentcalc') {  
                 &setothersheets($asheet,&othersheets($asheet,'assesscalc'));  
             }              }
               $r->print('>'.$_.'</option>');
           } 
           $r->print('</select><p>');
           if ($sheet->{'sheettype'} eq 'studentcalc') {
               &setothersheets($sheet,
                               &othersheets($sheet,'assesscalc'));
         }          }
         # Cached sheets      }
         &expirationdates();      #
       # Set up caching mechanisms
       #
       &load_spreadsheet_expirationdates();
       # Clear out old caches if we have not seen this class before.
       if (exists($oldsheets{'course'}) &&
           $oldsheets{'course'} ne $sheet->{'cid'}) {
         undef %oldsheets;          undef %oldsheets;
         undef %loadedcaches;          undef %loadedcaches;
         if (&gettype($asheet) eq 'classcalc') {      }
             $r->print      $oldsheets{'course'} = $sheet->{'cid'};
                 ("Loading previously calculated student sheets ...<br>\n");      #
             $r->rflush();      if ($sheet->{'sheettype'} eq 'classcalc') {
             &cachedcsheets();          $r->print("Loading previously calculated student sheets ...\n");
         } elsif (&gettype($asheet) eq 'studentcalc') {  
             $r->print  
                 ("Loading previously calculated assessment sheets ...<br>\n");  
             $r->rflush();  
             &cachedssheets(&getuname($asheet),&getudom($asheet),  
                            &getuhome($asheet));  
         }  
         # Update sheet, load rows  
         $r->print("Loaded sheet(s), updating rows ...<br>\n");  
         $r->rflush();          $r->rflush();
         #          &cachedcsheets();
         &updatesheet($asheet);      } elsif ($sheet->{'sheettype'} eq 'studentcalc') {
         $r->print("Updated rows, loading row data ...<br>\n");          $r->print("Loading previously calculated assessment sheets ...\n");
         $r->rflush();          $r->rflush();
         #          &cachedssheets($sheet);
         &loadrows($asheet,$r);      }
         $r->print("Loaded row data, calculating sheet ...<br>\n");      # Update sheet, load rows
         $r->rflush();      $r->print("Loaded sheet(s), updating rows ...<br>\n");
         #      $r->rflush();
         my $calcoutput=&calcsheet($asheet);      #
         $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>');      &updatesheet($sheet);
         # See if something to save      $r->print("Updated rows, loading row data ...\n");
         if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {      $r->rflush();
             my $fname='';      #
             if ($ENV{'form.saveas'} && ($fname=$ENV{'form.newfn'})) {      &loadrows($sheet,$r);
                 $fname=~s/\W/\_/g;      $r->print("Loaded row data, calculating sheet ...<br>\n");
                 if ($fname eq 'default') { $fname='course_default'; }      $r->rflush();
                 $fname.='_'.&gettype($asheet);      #
                 &setfilename($asheet,$fname);      my $calcoutput=&calcsheet($sheet);
                 $ENV{'form.ufn'}=$fname;      $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>');
                 $r->print('<p>Saving spreadsheet: '.      # See if something to save
                           &writesheet($asheet,$ENV{'form.makedefufn'}).'<p>');      if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
             }          my $fname='';
         }          if ($ENV{'form.saveas'} && ($fname=$ENV{'form.newfn'})) {
         #Write the modified worksheet              $fname=~s/\W/\_/g;
               if ($fname eq 'default') { $fname='course_default'; }
    $r->print('<b>Current sheet:</b> '.&getfilename($asheet).'<p>');              $fname.='_'.$sheet->{'sheettype'};
               $sheet->{'filename'} = $fname;
    &tmpwrite($asheet);              $ENV{'form.ufn'}=$fname;
               $r->print('<p>Saving spreadsheet: '.
     if (&gettype($asheet) eq 'studentcalc') {                        &writesheet($sheet,$ENV{'form.makedefufn'}).
  $r->print('<br>Show rows with empty A column: ');                        '<p>');
           }
       }
       #
       # Write the modified worksheet
       $r->print('<b>Current sheet:</b> '.$sheet->{'filename'}.'</p>');
       &tmpwrite($sheet);
       if ($sheet->{'sheettype'} eq 'assesscalc') {
           $r->print('<p>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') {      # CSV format checkbox (classcalc sheets only)
        $r->print(      $r->print(' Output as <select name="output" size="1" onClick="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'));      if ($sheet->{'sheettype'} eq 'classcalc') {
           $r->print('<option value="recursive excel"');
    $r->print(<<ENDINSERTBUTTONS);          if ($ENV{'form.output'} eq 'recursive excel') {
 <br>              $r->print(' selected ');
 <input type='button' onClick='insertrow("top");'           } 
 value='Insert Row Top'>          $r->print(">Multi-Sheet Excel</option>\n");
 <input type='button' onClick='insertrow("bottom");'       }
 value='Insert Row Bottom'><br>      $r->print("</select>\n");
 ENDINSERTBUTTONS      #
       if ($sheet->{'sheettype'} eq 'classcalc') {
 # ------------------------------------------------------------- Print out sheet          $r->print('&nbsp;Student Status: '.
                     &Apache::lonhtmlcommon::StatusOptions
     &outsheet($r,$asheet);                    ($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.104  
changed lines
  Added in v.1.144


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