Diff for /loncom/interface/Attic/lonspreadsheet.pm between versions 1.65 and 1.134

version 1.65, 2001/09/17 22:01:23 version 1.134, 2002/11/06 20:00:13
Line 1 Line 1
   #
   # $Id$
   #
   # Copyright Michigan State University Board of Trustees
   #
   # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
   #
   # LON-CAPA is free software; you can redistribute it and/or modify
   # it under the terms of the GNU General Public License as published by
   # the Free Software Foundation; either version 2 of the License, or
   # (at your option) any later version.
   #
   # LON-CAPA is distributed in the hope that it will be useful,
   # but WITHOUT ANY WARRANTY; without even the implied warranty of
   # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   # GNU General Public License for more details.
   #
   # You should have received a copy of the GNU General Public License
   # along with LON-CAPA; if not, write to the Free Software
   # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   #
   # /home/httpd/html/adm/gpl.txt
   #
   # http://www.lon-capa.org/
   #
 # 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,  # POD required stuff:
 # 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,  =head1 NAME
 # 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,  lonspreadsheet
 # 07/09,07/14,07/21,09/01,09/10,9/11,9/12,9/13,9/14,9/17 Gerd Kortemeyer  
   =head1 SYNOPSIS
   
   Spreadsheet interface to internal LON-CAPA data
   
   =head1 DESCRIPTION
   
   Lonspreadsheet provides course coordinators the ability to manage their
   students grades online.  The students are able to view their own grades, but
   not the grades of their peers.  The spreadsheet is highly customizable,
   offering the ability to use Perl code to manipulate data, as well as many
   built-in functions.
   
   =head2 Functions available to user of lonspreadsheet
   
   =over 4
   
   =cut
   
 package Apache::lonspreadsheet;  package Apache::lonspreadsheet;
                           
Line 18  use Apache::lonnet; Line 60  use Apache::lonnet;
 use Apache::Constants qw(:common :http);  use Apache::Constants qw(:common :http);
 use GDBM_File;  use GDBM_File;
 use HTML::TokeParser;  use HTML::TokeParser;
   use Apache::lonhtmlcommon;
   use Apache::loncoursedata;
   use Apache::File();
   use Spreadsheet::WriteExcel;
   #
   # Caches for coursewide information 
   #
   my %Section;
   
 #  #
 # Caches for previously calculated spreadsheets  # Caches for previously calculated spreadsheets
Line 53  my %courseopt; Line 103  my %courseopt;
 my %useropt;  my %useropt;
 my %parmhash;  my %parmhash;
   
   #
   # Some hashes for stats on timing and performance
   #
   
   my %starttimes;
   my %usedtimes;
   my %numbertimes;
   
 # Stuff that only the screen handler can know  # Stuff that only the screen handler can know
   
 my $includedir;  my $includedir;
Line 61  my $tmpdir; Line 119  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');  
     my $code=<<'ENDDEFS';  
 # ---------------------------------------------------- Inside of the safe space  
   
 #  
 # f: formulas  
 # t: intermediate format (variable references expanded)  
 # v: output values  
 # c: preloaded constants (A-column)  
 # rl: row label  
 # os: other spreadsheets (for student spreadsheet only)  
   
 undef %v;   
 undef %t;  
 undef %f;  
 undef %c;  
 undef %rl;  
 undef @os;  
   
 $maxrow=0;  
 $sheettype='';  
   
 # filename/reference of the sheet  
   
 $filename='';  
   
 # user data  
 $uname='';  
 $uhome='';  
 $udom='';  
   
 # course data  
   
 $csec='';  
 $chome='';  
 $cnum='';  
 $cdom='';  
 $cid='';  
 $cfn='';  
   
 # symb  
   
 $usymb='';  
   
 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 135  sub mask { Line 142  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 {
         if (length($ld)!=length($ud)) {          if (length($ld)!=length($ud)) {
            $num.='(';             $num.='(';
    map {     foreach ($ld=~m/\d/g) {
               $num.='['.$_.'-9]';                $num.='['.$_.'-9]';
            } ($ld=~m/\d/g);     }
            if (length($ud)-length($ld)>1) {             if (length($ud)-length($ld)>1) {
               $num.='|\d{'.(length($ld)+1).','.(length($ud)-1).'}';                $num.='|\d{'.(length($ld)+1).','.(length($ud)-1).'}';
    }     }
            $num.='|';             $num.='|';
            map {             foreach ($ud=~m/\d/g) {
                $num.='[0-'.$_.']';                 $num.='[0-'.$_.']';
            } ($ud=~m/\d/g);             }
            $num.=')';             $num.=')';
        } 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 179  sub mask { Line 187  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 188  sub mask { Line 196  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) 
   
   returns the proportion of the module 
   weights not previously completed by the student.
   
   =over 4
   
   =item hashname 
   
   name of the hash the module dates have been inserted into
   
   =item modules 
   
   reference to a cell which contains a comma deliminated list of modules 
   covered by the assignment.
   
   =item units 
   
   reference to a cell which contains a comma deliminated list of module 
   weights with respect to the assignment
   
   =item date 
   
   reference to a cell which contains the date the assignment was completed.
   
   =back 
   
   =cut
   
   #-------------------------------------------------------
   sub UWCALC {
       my ($hashname,$modules,$units,$date) = @_;
       my @Modules = split(/,/,$modules);
       my @Units   = split(/,/,$units);
       my $total_weight;
       foreach (@Units) {
    $total_weight += $_;
       }
       my $usum=0;
       for (my $i=0; $i<=$#Modules; $i++) {
    if (&HASH($hashname,$Modules[$i]) eq $date) {
       $usum += $Units[$i];
    }
       }
       return $usum/$total_weight;
   }
   
   #-------------------------------------------------------
   
   =item CDLSUM(list) 
   
   returns the sum of the elements in a cell which contains
   a Comma Deliminate List of numerical values.
   'list' is a reference to a cell which contains a comma deliminated list.
   
   =cut
   
   #-------------------------------------------------------
   sub CDLSUM {
       my ($list)=@_;
       my $sum;
       foreach (split/,/,$list) {
    $sum += $_;
       }
       return $sum;
   }
   
   #-------------------------------------------------------
   
   =item CDLITEM(list,index) 
   
   returns the item at 'index' in a Comma Deliminated List.
   
   =over 4
   
   =item list
   
   reference to a cell which contains a comma deliminated list.
   
   =item index 
   
   the Perl index of the item requested (first element in list has
   an index of 0) 
   
   =back
   
   =cut
   
   #-------------------------------------------------------
   sub CDLITEM {
       my ($list,$index)=@_;
       my @Temp = split/,/,$list;
       return $Temp[$index];
   }
   
   #-------------------------------------------------------
   
   =item CDLHASH(name,key,value) 
   
   loads a comma deliminated list of keys into
   the hash 'name', all with a value of 'value'.
   
   =over 4
   
   =item name  
   
   name of the hash.
   
   =item key
   
   (a pointer to) a comma deliminated list of keys.
   
   =item value
   
   a single value to be entered for each key.
   
   =back
   
   =cut
   
   #-------------------------------------------------------
   sub CDLHASH {
       my ($name,$key,$value)=@_;
       my @Keys;
       my @Values;
       # Check to see if we have multiple $key values
       if ($key =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) {
    my $keymask = &mask($key);
    # Assume the keys are addresses
    my @Temp = grep /$keymask/,keys(%sheet_values);
    @Keys = $sheet_values{@Temp};
       } else {
    $Keys[0]= $key;
       }
       my @Temp;
       foreach $key (@Keys) {
    @Temp = (@Temp, split/,/,$key);
       }
       @Keys = @Temp;
       if ($value =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) {
    my $valmask = &mask($value);
    my @Temp = grep /$valmask/,keys(%sheet_values);
    @Values =$sheet_values{@Temp};
       } else {
    $Values[0]= $value;
       }
       $value = $Values[0];
       # Add values to hash
       for (my $i = 0; $i<=$#Keys; $i++) {
    my $key   = $Keys[$i];
    if (! exists ($hashes{$name}->{$key})) {
       $hashes{$name}->{$key}->[0]=$value;
    } else {
       my @Temp = sort(@{$hashes{$name}->{$key}},$value);
       $hashes{$name}->{$key} = \@Temp;
    }
       }
       return "hash '$name' updated";
   }
   
   #-------------------------------------------------------
   
   =item GETHASH(name,key,index) 
   
   returns the element in hash 'name' 
   reference by the key 'key', at index 'index' in the values list.
   
   =cut
   
   #-------------------------------------------------------
   sub GETHASH {
       my ($name,$key,$index)=@_;
       if (! defined($index)) {
    $index = 0;
       }
       if ($key =~ /^[A-z]\d+$/) {
    $key = $sheet_values{$key};
       }
       return $hashes{$name}->{$key}->[$index];
   }
   
   #-------------------------------------------------------
   
   =item CLEARHASH(name) 
   
   clears all the values from the hash 'name'
   
   =item CLEARHASH(name,key) 
   
   clears all the values from the hash 'name' associated with the given key.
   
   =cut
   
   #-------------------------------------------------------
   sub CLEARHASH {
       my ($name,$key)=@_;
       if (defined($key)) {
    if (exists($hashes{$name}->{$key})) {
       $hashes{$name}->{$key}=undef;
       return "hash '$name' key '$key' cleared";
    }
       } else {
    if (exists($hashes{$name})) {
       $hashes{$name}=undef;
       return "hash '$name' cleared";
    }
       }
       return "Error in clearing hash";
   }
   
   #-------------------------------------------------------
   
   =item HASH(name,key,value) 
   
   loads values into an internal hash.  If a key 
   already has a value associated with it, the values are sorted numerically.  
   
   =item HASH(name,key) 
   
   returns the 0th value in the hash 'name' associated with 'key'.
   
   =cut
   
   #-------------------------------------------------------
   sub HASH {
       my ($name,$key,$value)=@_;
       my @Keys;
       undef @Keys;
       my @Values;
       # Check to see if we have multiple $key values
       if ($key =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) {
    my $keymask = &mask($key);
    # Assume the keys are addresses
    my @Temp = grep /$keymask/,keys(%sheet_values);
    @Keys = $sheet_values{@Temp};
       } else {
    $Keys[0]= $key;
       }
       # If $value is empty, return the first value associated 
       # with the first key.
       if (! $value) {
    return $hashes{$name}->{$Keys[0]}->[0];
       }
       # Check to see if we have multiple $value(s) 
       if ($value =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) {
    my $valmask = &mask($value);
    my @Temp = grep /$valmask/,keys(%sheet_values);
    @Values =$sheet_values{@Temp};
       } else {
    $Values[0]= $value;
       }
       # Add values to hash
       for (my $i = 0; $i<=$#Keys; $i++) {
    my $key   = $Keys[$i];
    my $value = ($i<=$#Values ? $Values[$i] : $Values[0]);
    if (! exists ($hashes{$name}->{$key})) {
       $hashes{$name}->{$key}->[0]=$value;
    } else {
       my @Temp = sort(@{$hashes{$name}->{$key}},$value);
       $hashes{$name}->{$key} = \@Temp;
    }
       }
       return $Values[-1];
   }
   
   #-------------------------------------------------------
   
   =item NUM(range)
   
   returns the number of items in the range.
   
   =cut
   
   #-------------------------------------------------------
 sub NUM {  sub NUM {
     my $mask=mask(@_);      my $mask=mask(@_);
     my $num=0;      my $num= $#{@{grep(/$mask/,keys(%sheet_values))}}+1;
     map {  
         $num++;  
     } grep /$mask/,keys %v;  
     return $num;         return $num;   
 }  }
   
Line 201  sub BIN { Line 554  sub BIN {
     my ($low,$high,$lower,$upper)=@_;      my ($low,$high,$lower,$upper)=@_;
     my $mask=mask($lower,$upper);      my $mask=mask($lower,$upper);
     my $num=0;      my $num=0;
     map {      foreach (grep /$mask/,keys(%sheet_values)) {
         if (($v{$_}>=$low) && ($v{$_}<=$high)) {          if (($sheet_values{$_}>=$low) && ($sheet_values{$_}<=$high)) {
             $num++;              $num++;
         }          }
     } grep /$mask/,keys %v;      }
     return $num;         return $num;   
 }  }
   
   
   #-------------------------------------------------------
   
   =item SUM(range)
   
   returns the sum of items in the range.
   
   =cut
   
   #-------------------------------------------------------
 sub SUM {  sub SUM {
     my $mask=mask(@_);      my $mask=mask(@_);
     my $sum=0;      my $sum=0;
     map {      foreach (grep /$mask/,keys(%sheet_values)) {
         $sum+=$v{$_};          $sum+=$sheet_values{$_};
     } grep /$mask/,keys %v;      }
     return $sum;         return $sum;   
 }  }
   
   #-------------------------------------------------------
   
   =item MEAN(range)
   
   compute the average of the items in the range.
   
   =cut
   
   #-------------------------------------------------------
 sub MEAN {  sub MEAN {
     my $mask=mask(@_);      my $mask=mask(@_);
     my $sum=0; my $num=0;      my $sum=0; 
     map {      my $num=0;
         $sum+=$v{$_};      foreach (grep /$mask/,keys(%sheet_values)) {
           $sum+=$sheet_values{$_};
         $num++;          $num++;
     } grep /$mask/,keys %v;      }
     if ($num) {      if ($num) {
        return $sum/$num;         return $sum/$num;
     } else {      } else {
Line 233  sub MEAN { Line 605  sub MEAN {
     }         }   
 }  }
   
   #-------------------------------------------------------
   
   =item STDDEV(range)
   
   compute the standard deviation of the items in the range.
   
   =cut
   
   #-------------------------------------------------------
 sub STDDEV {  sub STDDEV {
     my $mask=mask(@_);      my $mask=mask(@_);
     my $sum=0; my $num=0;      my $sum=0; my $num=0;
     map {      foreach (grep /$mask/,keys(%sheet_values)) {
         $sum+=$v{$_};          $sum+=$sheet_values{$_};
         $num++;          $num++;
     } grep /$mask/,keys %v;      }
     unless ($num>1) { return undef; }      unless ($num>1) { return undef; }
     my $mean=$sum/$num;      my $mean=$sum/$num;
     $sum=0;      $sum=0;
     map {      foreach (grep /$mask/,keys(%sheet_values)) {
         $sum+=($v{$_}-$mean)**2;          $sum+=($sheet_values{$_}-$mean)**2;
     } grep /$mask/,keys %v;      }
     return sqrt($sum/($num-1));          return sqrt($sum/($num-1));    
 }  }
   
   #-------------------------------------------------------
   
   =item PROD(range)
   
   compute the product of the items in the range.
   
   =cut
   
   #-------------------------------------------------------
 sub PROD {  sub PROD {
     my $mask=mask(@_);      my $mask=mask(@_);
     my $prod=1;      my $prod=1;
     map {      foreach (grep /$mask/,keys(%sheet_values)) {
         $prod*=$v{$_};          $prod*=$sheet_values{$_};
     } grep /$mask/,keys %v;      }
     return $prod;         return $prod;   
 }  }
   
   #-------------------------------------------------------
   
   =item MAX(range)
   
   compute the maximum of the items in the range.
   
   =cut
   
   #-------------------------------------------------------
 sub MAX {  sub MAX {
     my $mask=mask(@_);      my $mask=mask(@_);
     my $max='-';      my $max='-';
     map {      foreach (grep /$mask/,keys(%sheet_values)) {
         unless ($max) { $max=$v{$_}; }          unless ($max) { $max=$sheet_values{$_}; }
         if (($v{$_}>$max) || ($max eq '-')) { $max=$v{$_}; }          if (($sheet_values{$_}>$max) || ($max eq '-')) { $max=$sheet_values{$_}; }
     } grep /$mask/,keys %v;      } 
     return $max;         return $max;   
 }  }
   
   #-------------------------------------------------------
   
   =item MIN(range)
   
   compute the minimum of the items in the range.
   
   =cut
   
   #-------------------------------------------------------
 sub MIN {  sub MIN {
     my $mask=mask(@_);      my $mask=mask(@_);
     my $min='-';      my $min='-';
     map {      foreach (grep /$mask/,keys(%sheet_values)) {
         unless ($max) { $max=$v{$_}; }          unless ($max) { $max=$sheet_values{$_}; }
         if (($v{$_}<$min) || ($min eq '-')) { $min=$v{$_}; }          if (($sheet_values{$_}<$min) || ($min eq '-')) { 
     } grep /$mask/,keys %v;              $min=$sheet_values{$_}; 
           }
       }
     return $min;         return $min;   
 }  }
   
   #-------------------------------------------------------
   
   =item SUMMAX(num,lower,upper)
   
   compute the sum of the largest 'num' items in the range from
   'lower' to 'upper'
   
   =cut
   
   #-------------------------------------------------------
 sub SUMMAX {  sub SUMMAX {
     my ($num,$lower,$upper)=@_;      my ($num,$lower,$upper)=@_;
     my $mask=mask($lower,$upper);      my $mask=mask($lower,$upper);
     my @inside=();      my @inside=();
     map {      foreach (grep /$mask/,keys(%sheet_values)) {
  $inside[$#inside+1]=$v{$_};   push (@inside,$sheet_values{$_});
     } grep /$mask/,keys %v;      }
     @inside=sort(@inside);      @inside=sort(@inside);
     my $sum=0; my $i;      my $sum=0; my $i;
     for ($i=$#inside;(($i>$#inside-$num) && ($i>=0));$i--) {       for ($i=$#inside;(($i>$#inside-$num) && ($i>=0));$i--) { 
Line 293  sub SUMMAX { Line 713  sub SUMMAX {
     return $sum;         return $sum;   
 }  }
   
   #-------------------------------------------------------
   
   =item SUMMIN(num,lower,upper)
   
   compute the sum of the smallest 'num' items in the range from
   'lower' to 'upper'
   
   =cut
   
   #-------------------------------------------------------
 sub SUMMIN {  sub SUMMIN {
     my ($num,$lower,$upper)=@_;      my ($num,$lower,$upper)=@_;
     my $mask=mask($lower,$upper);      my $mask=mask($lower,$upper);
     my @inside=();      my @inside=();
     map {      foreach (grep /$mask/,keys(%sheet_values)) {
  $inside[$#inside+1]=$v{$_};   $inside[$#inside+1]=$sheet_values{$_};
     } grep /$mask/,keys %v;      }
     @inside=sort(@inside);      @inside=sort(@inside);
     my $sum=0; my $i;      my $sum=0; my $i;
     for ($i=0;(($i<$num) && ($i<=$#inside));$i++) {       for ($i=0;(($i<$num) && ($i<=$#inside));$i++) { 
Line 308  sub SUMMIN { Line 738  sub SUMMIN {
     return $sum;         return $sum;   
 }  }
   
   #-------------------------------------------------------
   
   =item MINPARM(parametername)
   
   Returns the minimum value of the parameters matching the parametername.
   parametername should be a string such as 'duedate'.
   
   =cut
   
   #-------------------------------------------------------
   sub MINPARM {
       my ($expression) = @_;
       my $min = undef;
       study($expression);
       foreach $parameter (keys(%c)) {
           next if ($parameter !~ /$expression/);
           if ((! defined($min)) || ($min > $c{$parameter})) {
               $min = $c{$parameter} 
           }
       }
       return $min;
   }
   
   #-------------------------------------------------------
   
   =item MAXPARM(parametername)
   
   Returns the maximum value of the parameters matching the input parameter name.
   parametername should be a string such as 'duedate'.
   
   =cut
   
   #-------------------------------------------------------
   sub MAXPARM {
       my ($expression) = @_;
       my $max = undef;
       study($expression);
       foreach $parameter (keys(%c)) {
           next if ($parameter !~ /$expression/);
           if ((! defined($min)) || ($max < $c{$parameter})) {
               $max = $c{$parameter} 
           }
       }
       return $max;
   }
   
   #--------------------------------------------------------
 sub expandnamed {  sub expandnamed {
     my $expression=shift;      my $expression=shift;
     if ($expression=~/^\&/) {      if ($expression=~/^\&/) {
Line 315  sub expandnamed { Line 792  sub expandnamed {
  my @vars=split(/\W+/,$formula);   my @vars=split(/\W+/,$formula);
         my %values=();          my %values=();
         undef %values;          undef %values;
         map {   foreach ( @vars ) {
             my $varname=$_;              my $varname=$_;
             if ($varname=~/\D/) {              if ($varname=~/\D/) {
                $formula=~s/$varname/'$c{\''.$varname.'\'}'/ge;                 $formula=~s/$varname/'$c{\''.$varname.'\'}'/ge;
                $varname=~s/$var/\(\\w\+\)/g;                 $varname=~s/$var/\(\\w\+\)/g;
        map {         foreach (keys(%c)) {
   if ($_=~/$varname/) {    if ($_=~/$varname/) {
       $values{$1}=1;        $values{$1}=1;
                   }                    }
                } keys %c;                 }
     }      }
         } @vars;          }
         if ($func eq 'EXPANDSUM') {          if ($func eq 'EXPANDSUM') {
             my $result='';              my $result='';
     map {      foreach (keys(%values)) {
                 my $thissum=$formula;                  my $thissum=$formula;
                 $thissum=~s/$var/$_/g;                  $thissum=~s/$var/$_/g;
                 $result.=$thissum.'+';                  $result.=$thissum.'+';
             } keys %values;              } 
             $result=~s/\+$//;              $result=~s/\+$//;
             return $result;              return $result;
         } else {          } else {
     return 0;      return 0;
         }          }
     } else {      } else {
         return '$c{\''.$expression.'\'}';          # it is not a function, so it is a parameter name
           # We should do the following:
           #    1. Take the list of parameter names
           #    2. look through the list for ones that match the parameter we want
           #    3. If there are no collisions, return the one that matches
           #    4. If there is a collision, return 'bad parameter name error'
           my $returnvalue = '';
           my @matches = ();
           $#matches = -1;
           study $expression;
           foreach $parameter (keys(%c)) {
               push @matches,$parameter if ($parameter =~ /$expression/);
           }
           if (scalar(@matches) == 0) {
               $returnvalue = 'unmatched parameter: '.$parameter;
           } elsif (scalar(@matches) == 1) {
               $returnvalue = '$c{\''.$matches[0].'\'}';
           } elsif (scalar(@matches) > 0) {
               # more than one match.  Look for a concise one
               $returnvalue =  "'non-unique parameter name : $expression'";
               foreach (@matches) {
                   if (/^$expression$/) {
                       $returnvalue = '$c{\''.$_.'\'}';
                   }
               }
           } else {
               # There was a negative number of matches, which indicates 
               # something is wrong with reality.  Better warn the user.
               $returnvalue = 'bizzare parameter: '.$parameter;
           }
           return $returnvalue;
     }      }
 }  }
   
Line 352  sub sett { Line 859  sub sett {
     } else {      } else {
         $pattern='[A-Z]';          $pattern='[A-Z]';
     }      }
     map {      # Deal with the template row
  if ($_=~/template\_(\w)/) {      foreach (keys(%f)) {
   my $col=$1;   next if ($_!~/template\_(\w)/);
           unless ($col=~/^$pattern/) {          my $col=$1;
             map {          next if ($col=~/^$pattern/);
       if ($_=~/A(\d+)/) {          foreach (keys(%f)) {
  my $trow=$1;              next if ($_!~/A(\d+)/);
                 if ($trow) {              my $trow=$1;
     my $lb=$col.$trow;              next if (! $trow);
                     $t{$lb}=$f{'template_'.$col};              # Get the name of this cell
                     $t{$lb}=~s/\#/$trow/g;              my $lb=$col.$trow;
                     $t{$lb}=~s/\.\.+/\,/g;              # Grab the template declaration
                     $t{$lb}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g;              $t{$lb}=$f{'template_'.$col};
                     $t{$lb}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge;              # Replace '#' with the row number
                 }              $t{$lb}=~s/\#/$trow/g;
       }              # Replace '....' with ','
             } keys %f;              $t{$lb}=~s/\.\.+/\,/g;
   }              # Replace 'A0' with the value from 'A0'
       }              $t{$lb}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
     } keys %f;              # Replace parameters
     map {              $t{$lb}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge;
  if (($f{$_}) && ($_!~/template\_/)) {          }
       }
       # Deal with the normal cells
       foreach (keys(%f)) {
    if (exists($f{$_}) && ($_!~/template\_/)) {
             my $matches=($_=~/^$pattern(\d+)/);              my $matches=($_=~/^$pattern(\d+)/);
             if  (($matches) && ($1)) {              if  (($matches) && ($1)) {
         unless ($f{$_}=~/^\!/) {          unless ($f{$_}=~/^\!/) {
Line 382  sub sett { Line 893  sub sett {
             } else {              } else {
        $t{$_}=$f{$_};         $t{$_}=$f{$_};
                $t{$_}=~s/\.\.+/\,/g;                 $t{$_}=~s/\.\.+/\,/g;
                $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g;                 $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
                $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge;                 $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge;
             }              }
         }          }
     } keys %f;      }
       # For inserted lines, [B-Z] is also valid
       unless ($sheettype eq 'assesscalc') {
          foreach (keys(%f)) {
      if ($_=~/[B-Z](\d+)/) {
          if ($f{'A'.$1}=~/^[\~\-]/) {
              $t{$_}=$f{$_};
                     $t{$_}=~s/\.\.+/\,/g;
                     $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
                     $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge;
                  }
              }
          }
       }
       # For some reason 'A0' gets special treatment...  This seems superfluous
       # but I imagine it is here for a reason.
     $t{'A0'}=$f{'A0'};      $t{'A0'}=$f{'A0'};
     $t{'A0'}=~s/\.\.+/\,/g;      $t{'A0'}=~s/\.\.+/\,/g;
     $t{'A0'}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g;      $t{'A0'}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
     $t{'A0'}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge;      $t{'A0'}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge;
 }  }
   
 sub calc {  sub calc {
     %v=();      undef %sheet_values;
     &sett();      &sett();
     my $notfinished=1;      my $notfinished=1;
       my $lastcalc='';
     my $depth=0;      my $depth=0;
     while ($notfinished) {      while ($notfinished) {
  $notfinished=0;   $notfinished=0;
         map {          foreach (keys(%t)) {
             my $old=$v{$_};              #$errorlog .= "$_:".$t{$_};
             $v{$_}=eval($t{$_});              my $old=$sheet_values{$_};
               $sheet_values{$_}=eval $t{$_};
     if ($@) {      if ($@) {
  %v=();   undef %sheet_values;
                 return $@;                  return $_.': '.$@;
             }              }
     if ($v{$_} ne $old) { $notfinished=1; }      if ($sheet_values{$_} ne $old) { $notfinished=1; $lastcalc=$_; }
         } keys %t;              #$errorlog .= ":".$sheet_values{$_}."\n";
           }
         $depth++;          $depth++;
         if ($depth>100) {          if ($depth>100) {
     %v=();      undef %sheet_values;
             return 'Maximum calculation depth exceeded';              return $lastcalc.': Maximum calculation depth exceeded';
         }          }
     }      }
     return '';      return '';
 }  }
   
   # ------------------------------------------- End of "Inside of the safe space"
   ENDDEFS
       $safeeval->reval($code);
       return $safeeval;
   }
   
   #
   # 
   #
 sub templaterow {  sub templaterow {
       my $sheet = shift;
     my @cols=();      my @cols=();
     $cols[0]='<b><font size=+1>Template</font></b>';      my $rowlabel = 'Template';
     map {      foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',
         my $fm=$f{'template_'.$_};       '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') {
           my $fm=$sheet->{'f'}->{'template_'.$_};
         $fm=~s/[\'\"]/\&\#34;/g;          $fm=~s/[\'\"]/\&\#34;/g;
         $cols[$#cols+1]="'template_$_','$fm'".'___eq___'.$fm;          push(@cols,{ name    => 'template_'.$_,
     } ('A','B','C','D','E','F','G','H','I','J','K','L','M',                       formula => $fm,
        'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',                       value   => $fm });
        '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');      return ($rowlabel,@cols);
     return @cols;  
 }  }
   
 sub outrowassess {  sub outrowassess {
     my $n=shift;      # $n is the current row number
       my ($sheet,$n) = @_;
     my @cols=();      my @cols=();
       my $rowlabel='';
     if ($n) {      if ($n) {
        my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{'A'.$n});          my ($usy,$ufn)=split(/__&&&\__/,$sheet->{'f'}->{'A'.$n});
        $cols[0]=$rl{$usy}.'<br>'.          if (exists($sheet->{'rowlabel'}->{$usy})) {
                 '<select name="sel_'.$n.'" onChange="changesheet('.$n.              $rowlabel = $sheet->{'rowlabel'}->{$usy};
                 ')"><option name="default">Default</option>';          } else { 
        map {              $rowlabel = '';
            $cols[0].='<option name="'.$_.'"';          }
             if ($ufn eq $_) {  
                $cols[0].=' selected';  
             }  
             $cols[0].='>'.$_.'</option>';  
        } @os;  
        $cols[0].='</select>';  
     } else {      } else {
        $cols[0]='<b><font size=+1>Export</font></b>';          $rowlabel = 'Export';
     }      }
     map {      foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',
         my $fm=$f{$_.$n};       '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') {
           my $fm=$sheet->{'f'}->{$_.$n};
         $fm=~s/[\'\"]/\&\#34;/g;          $fm=~s/[\'\"]/\&\#34;/g;
         $cols[$#cols+1]="'$_$n','$fm'".'___eq___'.$v{$_.$n};          push(@cols,{ name    => $_.$n,
     } ('A','B','C','D','E','F','G','H','I','J','K','L','M',                       formula => $fm,
        'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',                       value   => $sheet->{'values'}->{$_.$n}});
        'a','b','c','d','e','f','g','h','i','j','k','l','m',      }
        'n','o','p','q','r','s','t','u','v','w','x','y','z');      return ($rowlabel,@cols);
     return @cols;  
 }  }
   
 sub outrow {  sub outrow {
     my $n=shift;      my ($sheet,$n)=@_;
     my @cols=();      my @cols=();
       my $rowlabel;
     if ($n) {      if ($n) {
        $cols[0]=$rl{$f{'A'.$n}};          $rowlabel = $sheet->{'rowlabel'}->{$sheet->{'f'}->{'A'.$n}};
     } else {      } else {
        $cols[0]='<b><font size=+1>Export</font></b>';          if ($sheet->{'sheettype'} eq 'classcalc') {
               $rowlabel = 'Summary';
           } else {
               $rowlabel = 'Export';
           }
     }      }
     map {      foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',
         my $fm=$f{$_.$n};       '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') {
           my $fm=$sheet->{'f'}->{$_.$n};
         $fm=~s/[\'\"]/\&\#34;/g;          $fm=~s/[\'\"]/\&\#34;/g;
         $cols[$#cols+1]="'$_$n','$fm'".'___eq___'.$v{$_.$n};          push(@cols,{ name    => $_.$n,
     } ('A','B','C','D','E','F','G','H','I','J','K','L','M',                       formula => $fm,
        'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',                       value   => $sheet->{'values'}->{$_.$n}});
        'a','b','c','d','e','f','g','h','i','j','k','l','m',      }
        'n','o','p','q','r','s','t','u','v','w','x','y','z');      return ($rowlabel,@cols);
     return @cols;  
 }  
   
 sub exportrowa {  
     my @exportarray=();  
     map {  
  $exportarray[$#exportarray+1]=$v{$_.'0'};  
     } ('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');  
     return @exportarray;  
 }  
   
 # ------------------------------------------- End of "Inside of the safe space"  
 ENDDEFS  
     $safeeval->reval($code);  
     return $safeeval;  
 }  }
   
 # ------------------------------------------------ Add or change formula values  # ------------------------------------------------ Add or change formula values
   
 sub setformulas {  sub setformulas {
     my ($safeeval,%f)=@_;      my ($sheet)=shift;
     %{$safeeval->varglob('f')}=%f;      %{$sheet->{'safe'}->varglob('f')}=%{$sheet->{'f'}};
 }  }
   
 # ------------------------------------------------ Add or change formula values  # ------------------------------------------------ Add or change formula values
   
 sub setconstants {  sub setconstants {
     my ($safeeval,%c)=@_;      my ($sheet)=shift;
     %{$safeeval->varglob('c')}=%c;      my ($constants) = @_;
       if (! ref($constants)) {
           my %tmp = @_;
           $constants = \%tmp;
       }
       $sheet->{'constants'} = $constants;
       return %{$sheet->{'safe'}->varglob('c')}=%{$sheet->{'constants'}};
 }  }
   
 # --------------------------------------------- Set names of other spreadsheets  # --------------------------------------------- Set names of other spreadsheets
   
 sub setothersheets {  sub setothersheets {
     my ($safeeval,@os)=@_;      my $sheet = shift;
     @{$safeeval->varglob('os')}=@os;      my @othersheets = @_;
       $sheet->{'othersheets'} = \@othersheets;
       @{$sheet->{'safe'}->varglob('os')}=@othersheets;
       return;
 }  }
   
 # ------------------------------------------------ Add or change formula values  # ------------------------------------------------ Add or change formula values
   
 sub setrowlabels {  sub setrowlabels {
     my ($safeeval,%rl)=@_;      my $sheet=shift;
     %{$safeeval->varglob('rl')}=%rl;      my ($rowlabel) = @_;
       if (! ref($rowlabel)) {
           my %tmp = @_;
           $rowlabel = \%tmp;
       }
       $sheet->{'rowlabel'}=$rowlabel;
 }  }
   
 # ------------------------------------------------------- Calculate spreadsheet  # ------------------------------------------------------- Calculate spreadsheet
   
 sub calcsheet {  sub calcsheet {
     my $safeeval=shift;      my $sheet=shift;
     $safeeval->reval('&calc();');      my $result =  $sheet->{'safe'}->reval('&calc();');
 }      %{$sheet->{'values'}} = %{$sheet->{'safe'}->varglob('sheet_values')};
       return $result;
 # ------------------------------------------------------------------ Get values  
   
 sub getvalues {  
     my $safeeval=shift;  
     return $safeeval->reval('%v');  
 }  }
   
 # ---------------------------------------------------------------- Get formulas  # ---------------------------------------------------------------- Get formulas
   # Return a copy of the formulas
 sub getformulas {  sub getformulas {
     my $safeeval=shift;      my $sheet = shift;
     return %{$safeeval->varglob('f')};      return %{$sheet->{'safe'}->varglob('f')};
 }  
   
 # -------------------------------------------------------------------- Get type  
   
 sub gettype {  
     my $safeeval=shift;  
     return $safeeval->reval('$sheettype');  
 }  }
   
 # ------------------------------------------------------------------ Set maxrow  sub geterrorlog {
       my $sheet = shift;
 sub setmaxrow {      return ${$sheet->{'safe'}->varglob('errorlog')};    
     my ($safeeval,$row)=@_;  
     $safeeval->reval('$maxrow='.$row.';');  
 }  
   
 # ------------------------------------------------------------------ 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  # ----------------------------------------------------- Get value of $f{'A'.$n}
   sub getfa {
 sub getcnum {      my $sheet = shift;
     my $safeeval=shift;      my ($n)=@_;
     return $safeeval->reval('$cnum');      return $sheet->{'safe'}->reval('$f{"A'.$n.'"}');
 }  
   
 # ------------------------------------------------------------- Get course home  
   
 sub getchome {  
     my $safeeval=shift;  
     return $safeeval->reval('$chome');  
 }  
   
 # ----------------------------------------------------------- Get course domain  
   
 sub getcdom {  
     my $safeeval=shift;  
     return $safeeval->reval('$cdom');  
 }  
   
 # ---------------------------------------------------------- Get course section  
   
 sub getcsec {  
     my $safeeval=shift;  
     return $safeeval->reval('$csec');  
 }  
   
 # --------------------------------------------------------------- Get user name  
   
 sub getuname {  
     my $safeeval=shift;  
     return $safeeval->reval('$uname');  
 }  
   
 # ------------------------------------------------------------- Get user domain  
   
 sub getudom {  
     my $safeeval=shift;  
     return $safeeval->reval('$udom');  
 }  
   
 # --------------------------------------------------------------- Get user home  
   
 sub getuhome {  
     my $safeeval=shift;  
     return $safeeval->reval('$uhome');  
 }  
   
 # -------------------------------------------------------------------- Get symb  
   
 sub getusymb {  
     my $safeeval=shift;  
     return $safeeval->reval('$usymb');  
 }  }
   
 # ------------------------------------------------------------- Export of A-row  # ------------------------------------------------------------- Export of A-row
   
 sub exportdata {  sub exportdata {
     my $safeeval=shift;      my $sheet=shift;
     return $safeeval->reval('&exportrowa()');      my @exportarray=();
       foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',
        'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') {
           if (exists($sheet->{'values'}->{$_.'0'})) {
               push(@exportarray,$sheet->{'values'}->{$_.'0'});
           } else {
               push(@exportarray,'');
           }
       } 
       return @exportarray;
 }  }
   
   
 # ========================================================== End of Spreadsheet  # ========================================================== End of Spreadsheet
 # =============================================================================  # =============================================================================
   
Line 666  sub exportdata { Line 1115  sub exportdata {
 #  #
 # --------------------------------------------- Produce output row n from sheet  # --------------------------------------------- Produce output row n from sheet
   
 sub rown {  sub get_row {
     my ($safeeval,$n)=@_;      my ($sheet,$n) = @_;
     my $defaultbg;      my ($rowlabel,@rowdata);
     my $rowdata='';      if ($n eq '-') { 
     my $dataflag=0;          ($rowlabel,@rowdata) = &templaterow($sheet);
     unless ($n eq '-') {      } elsif ($sheet->{'sheettype'} eq 'studentcalc') {
        $defaultbg=((($n-1)/5)==int(($n-1)/5))?'#E0E0':'#FFFF';          ($rowlabel,@rowdata) = &outrowassess($sheet,$n);
     } else {      } else {
        $defaultbg='#E0FF';          ($rowlabel,@rowdata) = &outrow($sheet,$n);
     }      }
     $rowdata.="\n<tr><td><b><font size=+1>$n</font></b></td>";      return ($rowlabel,@rowdata);
     my $showf=0;  }
     my $proc;  
     my $maxred;  ########################################################################
     my $sheettype=&gettype($safeeval);  ########################################################################
     if ($sheettype eq 'studentcalc') {  sub sort_indicies {
         $proc='&outrowassess';      my $sheet = shift;
         $maxred=26;      #
     } else {      # Sort the rows in some manner
         $proc='&outrow';      #
     }      my @sortby=();
     if ($sheettype eq 'assesscalc') {      my @sortidx=();
         $maxred=1;      for (my $row=1;$row<=$sheet->{'maxrow'};$row++) {
     } else {          push (@sortby, $sheet->{'safe'}->reval('$f{"A'.$row.'"}'));
         $maxred=26;          push (@sortidx, $row);
       }
       @sortidx=sort { lc($sortby[$a]) cmp lc($sortby[$b]); } @sortidx;
       return @sortidx;
   }
   
   ########################################################################
   ########################################################################
   
   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 ($n eq '-') { $proc='&templaterow'; $n=-1; $dataflag=1; }      if ($value =~ /^\s*$/ ) {
     map {          $value = '<font color="'.$bgcolor.'">#</font>';
        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=$_; }  
        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>';  
        }  
        $showf++;  
     } $safeeval->reval($proc.'('.$n.')');  
     if ($ENV{'form.showall'} || ($dataflag)) {  
        return $rowdata.'</tr>';  
     } else {  
        return '';  
     }      }
 }      $result .= '<a href="javascript:celledit(\''.
           $name.'\',\''.$formula.'\');">'.$value.'</a>';
 # ------------------------------------------------------------- Print out sheet      return $result;
   }
 sub outsheet {  
     my ($r,$safeeval)=@_;  sub html_uneditable_cell {
     my $maxred;      my ($cell,$bgcolor) = @_;
     my $realm;      my $value = (defined($cell) ? $cell->{'value'} : '');
     if (&gettype($safeeval) eq 'assesscalc') {      return '&nbsp;'.$value.'&nbsp;';
         $maxred=1;  }
         $realm='Assessment';  
     } elsif (&gettype($safeeval) eq 'studentcalc') {  ########################################################################
         $maxred=26;  ########################################################################
         $realm='User';  
     } else {  sub outsheet_html  {
         $maxred=26;      my ($sheet,$r) = @_;
         $realm='Course';      my ($num_uneditable,$realm,$row_type);
     }      if ($sheet->{'sheettype'} eq 'assesscalc') {
     my $maxyellow=52-$maxred;          $num_uneditable = 1;
     my $tabledata=          $realm = 'Assessment';
         '<table border=2><tr><th colspan=2 rowspan=2><font size=+2>'.          $row_type = 'Item';
                   $realm.'</font></th>'.      } elsif ($sheet->{'sheettype'} eq 'studentcalc') {
                   '<td bgcolor=#FFDDDD colspan='.$maxred.          $num_uneditable = 26;
                   '><b><font size=+1>Import</font></b></td>'.          $realm = 'User';
                   '<td colspan='.$maxyellow.          $row_type = 'Assessment';
   '><b><font size=+1>Calculations</font></b></td></tr><tr>';      } elsif ($sheet->{'sheettype'} eq 'classcalc') {
     my $showf=0;          $num_uneditable = 26;
     map {          $realm = 'Course';
         $showf++;          $row_type = 'Student';
         if ($showf<=$maxred) {       } else {
            $tabledata.='<td bgcolor="#FFDDDD">';           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 {          } else {
            $tabledata.='<td>';              $tabledata.='<td>';
         }          }
         $tabledata.="<b><font size=+1>$_</font></b></td>";          $tabledata.="<b><font size=+1>$_</font></b></td>";
     } ('A','B','C','D','E','F','G','H','I','J','K','L','M',          $label_num++;
        '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',      $tabledata.="</tr>\n";
        'n','o','p','q','r','s','t','u','v','w','x','y','z');  
     $tabledata.='</tr>';  
     my $row;  
     my $maxrow=&getmaxrow($safeeval);  
     $tabledata.=&rown($safeeval,'-').&rown($safeeval,0);  
     $r->print($tabledata);      $r->print($tabledata);
       ####################################
     my @sortby=();      # Print out template row
     my @sortidx=();      ####################################
     for ($row=1;$row<=$maxrow;$row++) {      my ($rowlabel,@rowdata) = &get_row($sheet,'-');
        $sortby[$row-1]=$safeeval->reval('$f{"A'.$row.'"}');      my $row_html = '<tr><td>'.&format_html_rowlabel($rowlabel).'</td>';
        $sortidx[$row-1]=$row-1;      my $num_cols_output = 0;
     }      foreach my $cell (@rowdata) {
     @sortidx=sort { $sortby[$a] cmp $sortby[$b]; } @sortidx;          if ($num_cols_output++ < $num_uneditable) {
               $row_html .= '<td bgcolor="#FFDDDD">';
         my $what='Student';              $row_html .= &html_uneditable_cell($cell,'#FFDDDD');
         if (&gettype($safeeval) eq 'assesscalc') {          } else {
     $what='Item';              $row_html .= '<td bgcolor="#EOFFDD">';
  } elsif (&gettype($safeeval) eq 'studentcalc') {              $row_html .= &html_editable_cell($cell,'#E0FFDD');
             $what='Assessment';          }
         }          $row_html .= '</td>';
       }
     my $n=0;      $row_html.= "</tr>\n";
     for ($row=0;$row<$maxrow;$row++) {      $r->print($row_html);
      my $thisrow=&rown($safeeval,$sortidx[$row]+1);      ####################################
      if ($thisrow) {      # Print out summary/export row
        if ($n/25==int($n/25)) {      ####################################
  $r->print("</table>\n<br>\n");      my ($rowlabel,@rowdata) = &get_row($sheet,'0');
         $r->rflush();      my $rowcount = 0;
         $r->print('<table border=2><tr><td>&nbsp;<td>'.$what.'</td>');      $row_html = '<tr><td>'.&format_html_rowlabel($rowlabel).'</td>';
         map {      $num_cols_output = 0;
            $r->print('<td>'.$_.'</td>');      foreach my $cell (@rowdata) {
         } ('A','B','C','D','E','F','G','H','I','J','K','L','M',          if ($num_cols_output++ < 26) {
            'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',              $row_html .= '<td bgcolor="#CCCCFF">';
            'a','b','c','d','e','f','g','h','i','j','k','l','m',              $row_html .= &html_editable_cell($cell,'#CCCCFF');
            'n','o','p','q','r','s','t','u','v','w','x','y','z');          } else {
         $r->print('</tr>');              $row_html .= '<td bgcolor="#DDCCFF">';
        }              $row_html .= &html_uneditable_cell(undef,'#CCCCFF');
        $n++;          }
        $r->print($thisrow);          $row_html .= '</td>';
       }  
     }      }
       $row_html.= "</tr>\n";
       $r->print($row_html);
     $r->print('</table>');      $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);
           #
           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->rflush();
                   $r->print('<table border=2>'.
                             '<tr><td>&nbsp;<td>'.$row_type.'</td>'.
                             '<td>'.
                             join('</td><td>',
                                  (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
                                         'abcdefghijklmnopqrstuvwxyz'))).
                             "</td></tr>\n");
               }
               $rows_output++;
               $r->print($row_html);
           }
       }
       #
       $r->print('</table>');
       #
       # Debugging code (be sure to uncomment errorlog code in safe space):
       #
       # $r->print("\n<pre>");
       # $r->print(&geterrorlog($sheet));
       # $r->print("\n</pre>");
       return 1;
   }
   
   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);
           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;
   }
   
   sub outsheet_excel {
       my ($sheet,$r) = @_;
       my $filename = '/prtspool/'.
           $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.
           time.'_'.rand(1000000000).'.xls';
       &Apache::lonnet::logthis("spreadsheet:filename = ".$filename);
       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 0;
       }
       #
       # 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
       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);
       ####################################
       # 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);
           my $cols_output = 0;
           my $label = &format_excel_rowlabel($rowlabel);
           $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++;
       }
       #
       $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 outsheet_xml   {
 # ----------------------------------------------- Read list of available sheets      my ($sheet,$r) = @_;
 #   }
   
 sub othersheets {  
     my ($safeeval,$stype)=@_;  
   
     my $cnum=&getcnum($safeeval);  sub outsheet {
     my $cdom=&getcdom($safeeval);      my ($r,$sheet)=@_;
     my $chome=&getchome($safeeval);      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 'xml' ) {
   #        &outsheet_xml($sheet,$r);
       } else {
           &outsheet_html($sheet,$r);
       }
   }
   
   ########################################################################
   ########################################################################
   sub othersheets {
       my ($sheet,$stype)=@_;
       $stype = $sheet->{'sheettype'} if (! defined($stype));
       #
       my $cnum  = $sheet->{'cnum'};
       my $cdom  = $sheet->{'cdom'};
       my $chome = $sheet->{'chome'};
       #
     my @alternatives=();      my @alternatives=();
     my $result=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.':'.      my %results=&Apache::lonnet::dump($stype.'_spreadsheets',$cdom,$cnum);
                                       $stype.'_spreadsheets',$chome);      my ($tmp) = keys(%results);
     if ($result!~/^error\:/) {      unless ($tmp =~ /^(con_lost|error|no_such_host)/i) {
  map {          @alternatives = sort (keys(%results));
             $alternatives[$#alternatives+1]=      }
             &Apache::lonnet::unescape((split(/\=/,$_))[0]);  
         } split(/\&/,$result);  
     }   
     return @alternatives;       return @alternatives; 
 }  }
   
 #  #
   # -------------------------------------- Parse a spreadsheet
   # 
   sub parse_sheet {
       # $sheetxml is a scalar reference or a scalar
       my ($sheetxml) = @_;
       if (! ref($sheetxml)) {
           my $tmp = $sheetxml;
           $sheetxml = \$tmp;
       }
       my %f;
       my $parser=HTML::TokeParser->new($sheetxml);
       my $token;
       while ($token=$parser->get_token) {
           if ($token->[0] eq 'S') {
               if ($token->[1] eq 'field') {
                   $f{$token->[2]->{'col'}.$token->[2]->{'row'}}=
                       $parser->get_text('/field');
               }
               if ($token->[1] eq 'template') {
                   $f{'template_'.$token->[2]->{'col'}}=
                       $parser->get_text('/template');
               }
           }
       }
       return \%f;
   }
   
   #
 # -------------------------------------- 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'};
 # --------- There is no filename. Look for defaults in course and global, cache      #
       if (! defined($fn)) {
   unless($fn) {          # There is no filename. Look for defaults in course and global, cache
       unless ($fn=$defaultsheets{$cnum.'_'.$cdom.'_'.$stype}) {          unless ($fn=$defaultsheets{$cnum.'_'.$cdom.'_'.$stype}) {
          $fn=&Apache::lonnet::reply('get:'.$cdom.':'.$cnum.              my %tmphash = &Apache::lonnet::get('environment',
                                     ':environment:spreadsheet_default_'.$stype,                                                 ['spreadsheet_default_'.$stype],
                                     $chome);                                                 $cdom,$cnum);
          unless (($fn) && ($fn!~/^error\:/)) {              my ($tmp) = keys(%tmphash);
      $fn='default_'.$stype;              if ($tmp =~ /^(con_lost|error|no_such_host)/i) {
          }                  $fn = 'default_'.$stype;
          $defaultsheets{$cnum.'_'.$cdom.'_'.$stype}=$fn;               } else {
       }                  $fn = $tmphash{'spreadsheet_default_'.$stype};
   }              } 
               unless (($fn) && ($fn!~/^error\:/)) {
 # ---------------------------------------------------------- fn now has a value                  $fn='default_'.$stype;
               }
   &setfilename($safeeval,$fn);              $defaultsheets{$cnum.'_'.$cdom.'_'.$stype}=$fn; 
           }
 # ------------------------------------------------------ see if sheet is cached      }
   my $fstring='';      # $fn now has a value
   if ($fstring=$spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}) {      $sheet->{'filename'} = $fn;
       &setformulas($safeeval,split(/\_\_\_\;\_\_\_/,$fstring));      # see if sheet is cached
   } else {      my $fstring='';
       if ($fstring=$spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}) {
 # ---------------------------------------------------- Not cached, need to read          my %tmp = split(/___;___/,$fstring);
           $sheet->{'f'} = \%tmp;
      my %f=();          &setformulas($sheet);
       } else {
      if ($fn=~/^default\_/) {          # Not cached, need to read
  my $sheetxml='';          my %f=();
        {          if ($fn=~/^default\_/) {
          my $fh;              my $sheetxml='';
          if ($fh=Apache::File->new($includedir.              my $fh;
                          '/default.'.&gettype($safeeval))) {              my $dfn=$fn;
                $sheetxml=join('',<$fh>);              $dfn=~s/\_/\./g;
           }              if ($fh=Apache::File->new($includedir.'/'.$dfn)) {
        }                  $sheetxml=join('',<$fh>);
         my $parser=HTML::TokeParser->new(\$sheetxml);              } else {
         my $token;                  $sheetxml='<field row="0" col="A">"Error"</field>';
         while ($token=$parser->get_token) {              }
           if ($token->[0] eq 'S') {              %f=%{&parse_sheet(\$sheetxml)};
       if ($token->[1] eq 'field') {          } elsif($fn=~/\/*\.spreadsheet$/) {
   $f{$token->[2]->{'col'}.$token->[2]->{'row'}}=              my $sheetxml=&Apache::lonnet::getfile
       $parser->get_text('/field');                  (&Apache::lonnet::filelocation('',$fn));
       }              if ($sheetxml == -1) {
              if ($token->[1] eq 'template') {                  $sheetxml='<field row="0" col="A">"Error loading spreadsheet '
                  $f{'template_'.$token->[2]->{'col'}}=                      .$fn.'"</field>';
                      $parser->get_text('/template');              }
              }              %f=%{&parse_sheet(\$sheetxml)};
           }          } else {
         }              my $sheet='';
       } else {              my %tmphash = &Apache::lonnet::dump($fn,$cdom,$cnum);
           my $sheet='';              my ($tmp) = keys(%tmphash);
           my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.':'.$fn,              unless ($tmp =~ /^(con_lost|error|no_such_host)/i) {
                                          $chome);                  foreach (keys(%tmphash)) {
           unless ($reply=~/^error\:/) {                      $f{$_}=$tmphash{$_};
              $sheet=$reply;                  }
   }              }
           map {          }
              my ($name,$value)=split(/\=/,$_);          # Cache and set
              $f{&Apache::lonnet::unescape($name)}=          $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f);  
         &Apache::lonnet::unescape($value);          $sheet->{'f'}=\%f;
           } split(/\&/,$sheet);          &setformulas($sheet);
        }  
 # --------------------------------------------------------------- Cache and set  
        $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f);    
        &setformulas($safeeval,%f);  
     }      }
 }  }
   
 # -------------------------------------------------------- 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->{'cid'}   = $ENV{'request.course.id'};
       '";$usymb="'.$usymb.      $sheet->{'csec'}  = $Section{$uname.':'.$udom};
       '";$csec="'.&Apache::lonnet::usection($udom,$uname,      $sheet->{'coursefilename'}   = $ENV{'request.course.fn'};
                                             $ENV{'request.course.id'}).      $sheet->{'cnum'}  = $ENV{'course.'.$ENV{'request.course.id'}.'.num'};
       '";$cid="'.$ENV{'request.course.id'}.      $sheet->{'cdom'}  = $ENV{'course.'.$ENV{'request.course.id'}.'.domain'};
       '";$cfn="'.$ENV{'request.course.fn'}.      $sheet->{'chome'} = $ENV{'course.'.$ENV{'request.course.id'}.'.home'};
       '";$cnum="'.$ENV{'course.'.$ENV{'request.course.id'}.'.num'}.      $sheet->{'coursedesc'} = $ENV{'course.'.$ENV{'request.course.id'}.
       '";$cdom="'.$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.                                        'description'};
       '";$chome="'.$ENV{'course.'.$ENV{'request.course.id'}.'.home'}.'";');      $sheet->{'uhome'} = &Apache::lonnet::homeserver($uname,$udom);
     return $safeeval;      #
       #
       $sheet->{'f'} = {};
       $sheet->{'constants'} = {};
       $sheet->{'othersheets'} = [];
       $sheet->{'rowlabel'} = {};
       #
       #
       $sheet->{'safe'}=&initsheet($sheet->{'sheettype'});
       #
       # Place all the %$sheet items into the safe space except the safe space
       # itself
       my $initstring = '';
       foreach (qw/uname udom sheettype usymb cid csec coursefilename
                cnum cdom chome uhome/) {
           $initstring.= qq{\$$_="$sheet->{$_}";};
       }
       $sheet->{'safe'}->reval($initstring);
       return $sheet;
 }  }
   
 # ------------------------------------------------------------ Save spreadsheet  # ------------------------------------------------------------ Save spreadsheet
   
 sub writesheet {  sub writesheet {
   my ($safeeval,$makedef)=@_;      my ($sheet,$makedef)=@_;
   my $cid=&getcid($safeeval);      my $cid=$sheet->{'cid'};
   if (&Apache::lonnet::allowed('opa',$cid)) {      if (&Apache::lonnet::allowed('opa',$cid)) {
     my %f=&getformulas($safeeval);          my %f=&getformulas($sheet);
     my $stype=&gettype($safeeval);          my $stype= $sheet->{'sheettype'};
     my $cnum=&getcnum($safeeval);          my $cnum = $sheet->{'cnum'};
     my $cdom=&getcdom($safeeval);          my $cdom = $sheet->{'cdom'};
     my $chome=&getchome($safeeval);          my $chome= $sheet->{'chome'};
     my $fn=&getfilename($safeeval);          my $fn   = $sheet->{'filename'};
           # Cache new sheet
 # ------------------------------------------------------------- Cache new sheet          $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f);
     $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f);              # Write sheet
 # ----------------------------------------------------------------- Write sheet          foreach (keys(%f)) {
     my $sheetdata='';              delete($f{$_}) if ($f{$_} eq 'import');
     map {          }
      unless ($f{$_} eq 'import') {          my $reply = &Apache::lonnet::put($fn,\%f,$cdom,$cnum);
        $sheetdata.=&Apache::lonnet::escape($_).'='.          if ($reply eq 'ok') {
    &Apache::lonnet::escape($f{$_}).'&';              $reply = &Apache::lonnet::put($stype.'_spreadsheets',
      }                              {$fn => $ENV{'user.name'}.'@'.$ENV{'user.domain'}},
     } keys %f;                                            $cdom,$cnum);
     $sheetdata=~s/\&$//;              if ($reply eq 'ok') {
     my $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'.$fn.':'.                  if ($makedef) { 
               $sheetdata,$chome);                      return &Apache::lonnet::put('environment',
     if ($reply eq 'ok') {                                    {'spreadsheet_default_'.$stype => $fn },
           $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'.                                                  $cdom,$cnum);
               $stype.'_spreadsheets:'.                  } 
               &Apache::lonnet::escape($fn).'='.$ENV{'user.name'}.'@'.                  return $reply;
                                                $ENV{'user.domain'},              } 
               $chome);              return $reply;
           if ($reply eq 'ok') {          } 
               if ($makedef) {           return $reply;
                 return &Apache::lonnet::reply('put:'.$cdom.':'.$cnum.      }
                                 ':environment:spreadsheet_default_'.$stype.'='.      return 'unauthorized';
                                 &Apache::lonnet::escape($fn),  
                                 $chome);  
       } else {  
   return $reply;  
          }  
    } else {  
        return $reply;  
            }  
       } else {  
   return $reply;  
       }  
   }  
   return 'unauthorized';  
 }  }
   
 # ----------------------------------------------- Make a temp copy of the sheet  # ----------------------------------------------- Make a temp copy of the sheet
 # "Modified workcopy" - interactive only  # "Modified workcopy" - interactive only
 #  #
   
 sub tmpwrite {  sub tmpwrite {
     my $safeeval=shift;      my ($sheet) = @_;
     my $fn=$ENV{'user.name'}.'_'.      my $fn=$ENV{'user.name'}.'_'.
            $ENV{'user.domain'}.'_spreadsheet_'.&getusymb($safeeval).'_'.          $ENV{'user.domain'}.'_spreadsheet_'.$sheet->{'usymb'}.'_'.
            &getfilename($safeeval);             $sheet->{'filename'};
     $fn=~s/\W/\_/g;      $fn=~s/\W/\_/g;
     $fn=$tmpdir.$fn.'.tmp';      $fn=$tmpdir.$fn.'.tmp';
     my $fh;      my $fh;
     if ($fh=Apache::File->new('>'.$fn)) {      if ($fh=Apache::File->new('>'.$fn)) {
  print $fh join("\n",&getformulas($safeeval));   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;
     my %fo=();      my %fo=();
       my $countrows=0;
     if ($fh=Apache::File->new($fn)) {      if ($fh=Apache::File->new($fn)) {
         my $name;          my $name;
         while ($name=<$fh>) {          while ($name=<$fh>) {
Line 1012  sub tmpread { Line 1706  sub tmpread {
             my $value=<$fh>;              my $value=<$fh>;
             chomp($value);              chomp($value);
             $fo{$name}=$value;              $fo{$name}=$value;
               if ($name=~/^A(\d+)$/) {
    if ($1>$countrows) {
       $countrows=$1;
                   }
               }
         }          }
     }      }
     if ($nform eq 'changesheet') {      if ($nform eq 'changesheet') {
         $fo{'A'.$nfield}=(split(/\_\_\&\&\&\_\_/,$fo{'A'.$nfield}))[0];          $fo{'A'.$nfield}=(split(/__&&&\__/,$fo{'A'.$nfield}))[0];
         unless ($ENV{'form.sel_'.$nfield} eq 'Default') {          unless ($ENV{'form.sel_'.$nfield} eq 'Default') {
     $fo{'A'.$nfield}.='__&&&__'.$ENV{'form.sel_'.$nfield};      $fo{'A'.$nfield}.='__&&&__'.$ENV{'form.sel_'.$nfield};
         }          }
       } elsif ($nfield eq 'insertrow') {
           $countrows++;
           my $newrow=substr('000000'.$countrows,-7);
           if ($nform eq 'top') {
       $fo{'A'.$countrows}='--- '.$newrow;
           } else {
               $fo{'A'.$countrows}='~~~ '.$newrow;
           }
     } else {      } else {
        if ($nfield) { $fo{$nfield}=$nform; }         if ($nfield) { $fo{$nfield}=$nform; }
     }      }
     &setformulas($safeeval,%fo);      $sheet->{'f'}=\%fo;
       &setformulas($sheet);
 }  }
   
 # ================================================================== Parameters  ##################################################
 # -------------------------------------------- Figure out a cascading parameter  ##################################################
 #  
 # For this function to work  
 #  
 # * parmhash needs to be tied  
 # * courseopt and useropt need to be initialized for this user and course  
 #  
   
 sub parmval {  
     my ($what,$safeeval)=@_;  
     my $cid=&getcid($safeeval);  
     my $csec=&getcsec($safeeval);  
     my $uname=&getuname($safeeval);  
     my $udom=&getudom($safeeval);  
     my $symb=&getusymb($safeeval);  
   
     unless ($symb) { return ''; }  
     my $result='';  
   
     my ($mapname,$id,$fn)=split(/\_\_\_/,$symb);  
 # ----------------------------------------------------- Cascading lookup scheme  
        my $rwhat=$what;  
        $what=~s/^parameter\_//;  
        $what=~s/\_/\./;  
   
        my $symbparm=$symb.'.'.$what;  
        my $mapparm=$mapname.'___(all).'.$what;  
        my $usercourseprefix=$uname.'_'.$udom.'_'.$cid;  
   
        my $seclevel=  
             $usercourseprefix.'.['.  
  $csec.'].'.$what;  
        my $seclevelr=  
             $usercourseprefix.'.['.  
  $csec.'].'.$symbparm;  
        my $seclevelm=  
             $usercourseprefix.'.['.  
  $csec.'].'.$mapparm;  
   
        my $courselevel=  
             $usercourseprefix.'.'.$what;  
        my $courselevelr=  
             $usercourseprefix.'.'.$symbparm;  
        my $courselevelm=  
             $usercourseprefix.'.'.$mapparm;  
   
 # ---------------------------------------------------------- fourth, check user  
         
       if ($uname) {   
   
        if ($useropt{$courselevelr}) { return $useropt{$courselevelr}; }  
   
        if ($useropt{$courselevelm}) { return $useropt{$courselevelm}; }  =pod
   
        if ($useropt{$courselevel}) { return $useropt{$courselevel}; }  =item &parmval()
   
       }  Determine the value of a parameter.
   
 # --------------------------------------------------------- third, check course  Inputs: $what, the parameter needed, $sheet, the safe space
        
        if ($csec) {  
    
         if ($courseopt{$seclevelr}) { return $courseopt{$seclevelr}; }  
   
         if ($courseopt{$seclevelm}) { return $courseopt{$seclevelm}; }    Returns: The value of a parameter, or '' if none.
   
         if ($courseopt{$seclevel}) { return $courseopt{$seclevel}; }  This function cascades through the possible levels searching for a value for
     a parameter.  The levels are checked in the following order:
       }  user, course (at section level and course level), map, and lonnet::metadata.
   This function uses %parmhash, which must be tied prior to calling it.
   This function also requires %courseopt and %useropt to be initialized for
   this user and course.
   
        if ($courseopt{$courselevelr}) { return $courseopt{$courselevelr}; }  =cut
   
        if ($courseopt{$courselevelm}) { return $courseopt{$courselevelm}; }  ##################################################
   ##################################################
        if ($courseopt{$courselevel}) { return $courseopt{$courselevel}; }  sub parmval {
       my ($what,$sheet)=@_;
 # ----------------------------------------------------- second, check map parms      my $symb  = $sheet->{'usymb'};
       unless ($symb) { return ''; }
        my $thisparm=$parmhash{$symbparm};      #
        if ($thisparm) { return $thisparm; }      my $cid   = $sheet->{'cid'};
       my $csec  = $sheet->{'csec'};
 # -------------------------------------------------------- first, check default      my $uname = $sheet->{'uname'};
       my $udom  = $sheet->{'udom'};
        return &Apache::lonnet::metadata($fn,$rwhat.'.default');      my $result='';
               #
       my ($mapname,$id,$fn)=split(/\_\_\_/,$symb);
       # Cascading lookup scheme
       my $rwhat=$what;
       $what =~ s/^parameter\_//;
       $what =~ s/\_([^\_]+)$/\.$1/;
       #
       my $symbparm = $symb.'.'.$what;
       my $mapparm  = $mapname.'___(all).'.$what;
       my $usercourseprefix = $uname.'_'.$udom.'_'.$cid;
       #
       my $seclevel  = $usercourseprefix.'.['.$csec.'].'.$what;
       my $seclevelr = $usercourseprefix.'.['.$csec.'].'.$symbparm;
       my $seclevelm = $usercourseprefix.'.['.$csec.'].'.$mapparm;
       #
       my $courselevel  = $usercourseprefix.'.'.$what;
       my $courselevelr = $usercourseprefix.'.'.$symbparm;
       my $courselevelm = $usercourseprefix.'.'.$mapparm;
       # fourth, check user
       if (defined($uname)) {
           return $useropt{$courselevelr} if (defined($useropt{$courselevelr}));
           return $useropt{$courselevelm} if (defined($useropt{$courselevelm}));
           return $useropt{$courselevel}  if (defined($useropt{$courselevel}));
       }
       # third, check course
       if (defined($csec)) {
           return $courseopt{$seclevelr} if (defined($courseopt{$seclevelr}));
           return $courseopt{$seclevelm} if (defined($courseopt{$seclevelm}));
           return $courseopt{$seclevel}  if (defined($courseopt{$seclevel}));
       }
       #
       return $courseopt{$courselevelr} if (defined($courseopt{$courselevelr}));
       return $courseopt{$courselevelm} if (defined($courseopt{$courselevelm}));
       return $courseopt{$courselevel}  if (defined($courseopt{$courselevel}));
       # second, check map parms
       my $thisparm = $parmhash{$symbparm};
       return $thisparm if (defined($thisparm));
       # first, check default
       return &Apache::lonnet::metadata($fn,$rwhat.'.default');
   }
   
   
   ##################################################################
   ##                  Row label formatting routines               ##
   ##################################################################
   sub format_html_rowlabel {
       my $rowlabel = shift;
       return '' if ($rowlabel eq '');
       my ($type,$labeldata) = split(':',$rowlabel,2);
       my $result = '';
       if ($type eq 'symb') {
           my ($symb,$uname,$udom,$title) = split(':',$labeldata);
           $symb = &Apache::lonnet::unescape($symb);
           $result = '<a href="/adm/assesscalc?usymb='.$symb.
               '&uname='.$uname.'&udom='.$udom.'">'.$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,$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,$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'};
       #
 # ---------------------------------------------- Read class list and row labels      %Section = ();
   
     my $classlst=&Apache::lonnet::reply      #
                                  ('dump:'.$cdom.':'.$cnum.':classlist',$chome);      # Read class list and row labels
       my $classlist = &Apache::loncoursedata::get_classlist();
       if (! defined($classlist)) {
           return 'Could not access course classlist';
       } 
       #
     my %currentlist=();      my %currentlist=();
     my $now=time;      foreach my $student (keys(%$classlist)) {
     unless ($classlst=~/^error\:/) {          my ($studentDomain,$studentName,$end,$start,$id,$studentSection,
         map {              $fullname,$status)   =   @{$classlist->{$student}};
             my ($name,$value)=split(/\=/,$_);          if ($ENV{'form.Status'} eq $status || $ENV{'form.Status'} eq 'Any') {
             my ($end,$start)=split(/\:/,&Apache::lonnet::unescape($value));              $currentlist{$student}=join(':',('student',$studentName,
             my $active=1;                                               $studentDomain,$fullname,
             if (($end) && ($now>$end)) { $active=0; }                                               $studentSection,$id));
             if ($active) {          }
                 my $rowlabel='';      }
                 $name=&Apache::lonnet::unescape($name);      #
                 my ($sname,$sdom)=split(/\:/,$name);      # Find discrepancies between the course row table and this
                 my $ssec=&Apache::lonnet::usection($sdom,$sname,$cid);      #
                 if ($ssec==-1) {      my %f=&getformulas($sheet);
                     $rowlabel='<font color=red>Data not available: '.$name.      my $changed=0;
       '</font>';      #
                 } else {      $sheet->{'maxrow'}=0;
                     my %reply=&Apache::lonnet::idrget($sdom,$sname);      my %existing=();
                     my $reply=&Apache::lonnet::reply('get:'.$sdom.':'.$sname.      #
       ':environment:firstname&middlename&lastname&generation',      # Now obsolete rows
                       &Apache::lonnet::homeserver($sname,$sdom));      foreach (keys(%f)) {
                     $rowlabel='<a href="/adm/studentcalc?uname='.$sname.          if ($_=~/^A(\d+)/) {
                               '&udom='.$sdom.'">'.              if ($1 > $sheet->{'maxrow'}) {
                               $ssec.'&nbsp;'.$reply{$sname}.'<br>';                  $sheet->{'maxrow'}= $1;
                     map {  
                         $rowlabel.=&Apache::lonnet::unescape($_).' ';  
                     } split(/\&/,$reply);  
                     $rowlabel.='</a>';  
                 }  
  $currentlist{&Apache::lonnet::unescape($name)}=$rowlabel;  
             }  
         } split(/\&/,$classlst);  
 #  
 # -------------------- Find discrepancies between the course row table and this  
 #  
         my %f=&getformulas($safeeval);  
         my $changed=0;  
   
         my $maxrow=0;  
         my %existing=();  
   
 # ----------------------------------------------------------- Now obsolete rows  
  map {  
     if ($_=~/^A(\d+)/) {  
                 $maxrow=($1>$maxrow)?$1:$maxrow;  
                 $existing{$f{$_}}=1;  
  unless ((defined($currentlist{$f{$_}})) || (!$1)) {  
    $f{$_}='!!! Obsolete';  
                    $changed=1;  
                 }  
             }              }
         } keys %f;              $existing{$f{$_}}=1;
               unless ((defined($currentlist{$f{$_}})) || (!$1) ||
 # -------------------------------------------------------- New and unknown keys                      ($f{$_}=~/^(~~~|---)/)) {
                        $f{$_}='!!! Obsolete';
         map {                  $changed=1;
             unless ($existing{$_}) {  
  $changed=1;  
                 $maxrow++;  
                 $f{'A'.$maxrow}=$_;  
             }              }
         } sort keys %currentlist;                  }
            }
         if ($changed) { &setformulas($safeeval,%f); }      #
       # New and unknown keys
         &setmaxrow($safeeval,$maxrow);      foreach my $student (sort keys(%currentlist)) {
         &setrowlabels($safeeval,%currentlist);          unless ($existing{$student}) {
               $changed=1;
     } else {              $sheet->{'maxrow'}++;
         return 'Could not access course data';              $f{'A'.$sheet->{'maxrow'}}=$student;
           }
     }      }
       if ($changed) { 
           $sheet->{'f'} = \%f;
           &setformulas($sheet,%f); 
       }
       #
       &setrowlabels($sheet,\%currentlist);
 }  }
   
 # ----------------------------------- Update rows for student and assess sheets  # ----------------------------------- Update rows for student and assess sheets
   
 sub updatestudentassesssheet {  sub updatestudentassesssheet {
     my $safeeval=shift;      my ($sheet) = @_;
       #
     my %bighash;      my %bighash;
     my $stype=&gettype($safeeval);      #
     my %current=();      my $stype = $sheet->{'sheettype'};
     unless ($updatedata{$ENV{'request.course.fn'}.'_'.$stype}) {      my $uname = $sheet->{'uname'};
 # -------------------------------------------------------------------- Tie hash      my $udom  = $sheet->{'udom'};
       if (tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db',      $sheet->{'rowlabel'} = {};
                        &GDBM_READER,0640)) {      my $identifier =$sheet->{'coursefilename'}.'_'.$stype.'_'.$uname.'_'.$udom;
 # --------------------------------------------------------- Get all assessments      if  ($updatedata{$identifier}) {
           %{$sheet->{'rowlabel'}}=split(/___;___/,$updatedata{$identifier});
  my %allkeys=('timestamp' =>       } else {
                      'Timestamp of Last Transaction<br>timestamp');          # Tie hash
         my %allassess=();          tie(%bighash,'GDBM_File',$sheet->{'coursefilename'}.'.db',
               &GDBM_READER(),0640);
         my $adduserstr='';          if (! tied(%bighash)) {
         if ((&getuname($safeeval) ne $ENV{'user.name'}) ||              return 'Could not access course data';
             (&getudom($safeeval) ne $ENV{'user.domain'})) {          }
             $adduserstr='&uname='.&getuname($safeeval).          # Get all assessments
  '&udom='.&getudom($safeeval);          #
         }          # parameter_labels is used in the assessment sheets to provide labels
           # for the parameters.
         map {          my %parameter_labels=
     if ($_=~/^src\_(\d+)\.(\d+)$/) {              ('timestamp' => 
        my $mapid=$1;                   'parameter:Timestamp of Last Transaction<br>timestamp',
                my $resid=$2;               'subnumber' =>
                my $id=$mapid.'.'.$resid;                   'parameter:Number of Submissions<br>subnumber',
                my $srcf=$bighash{$_};               'tutornumber' =>
                if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) {                   'parameter:Number of Tutor Responses<br>tutornumber',
                  my $symb=               'totalpoints' =>
                      &Apache::lonnet::declutter($bighash{'map_id_'.$mapid}).                   'parameter:Total Points Granted<br>totalpoints');
     '___'.$resid.'___'.          #
     &Apache::lonnet::declutter($srcf);          # assesslist holds the descriptions of all assessments
  $allassess{$symb}=          my %assesslist;
             '<a href="/adm/assesscalc?usymb='.$symb.$adduserstr.'">'.          foreach ('Feedback','Evaluation','Tutoring','Discussion') {
                      $bighash{'title_'.$id}.'</a>';              my $symb = '_'.lc($_);
                  if ($stype eq 'assesscalc') {              $assesslist{$symb} = join(':',('symb',$symb,$uname,$udom,$_));
                    map {          }
                        if (($_=~/^stores\_(.*)/) || ($_=~/^parameter\_(.*)/)) {          while (($_,undef) = each(%bighash)) {
   my $key=$_;              next if ($_!~/^src\_(\d+)\.(\d+)$/);
                           my $display=              my $mapid=$1;
       &Apache::lonnet::metadata($srcf,$key.'.display');              my $resid=$2;
                           unless ($display) {              my $id=$mapid.'.'.$resid;
                               $display.=              my $srcf=$bighash{$_};
          &Apache::lonnet::metadata($srcf,$key.'.name');              if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) {
                           }                  my $symb=
                           $display.='<br>'.$key;                      &Apache::lonnet::declutter($bighash{'map_id_'.$mapid}).
                           $allkeys{$key}=$display;                          '___'.$resid.'___'.&Apache::lonnet::declutter($srcf);
        }                  $assesslist{$symb}='symb:'.&Apache::lonnet::escape($symb).':'
                    } split(/\,/,&Apache::lonnet::metadata($srcf,'keys'));                      .$uname.':'.$udom.':'.$bighash{'title_'.$id};
          }                  next if ($stype ne 'assesscalc');
       }                  foreach my $key (split(/\,/,
    }                                         &Apache::lonnet::metadata($srcf,'keys')
         } keys %bighash;                                         )) {
                       next if ($key !~ /^(stores|parameter)_/);
                       my $display=
                           &Apache::lonnet::metadata($srcf,$key.'.display');
                       unless ($display) {
                           $display.=
                               &Apache::lonnet::metadata($srcf,$key.'.name');
                       }
                       $display.='<br>'.$key;
                       $parameter_labels{$key}='parameter:'.$display;
                   } # end of foreach
               }
           } # end of foreach (keys(%bighash))
         untie(%bighash);          untie(%bighash);
               #
 #          # %parameter_labels has a list of storage and parameter displays by 
 # %allkeys has a list of storage and parameter displays by unikey          # unikey
 # %allassess has a list of all resource displays by symb          # %assesslist has a list of all resource, by symb
 #          #
   
         if ($stype eq 'assesscalc') {          if ($stype eq 'assesscalc') {
     %current=%allkeys;              $sheet->{'rowlabel'} = \%parameter_labels;
         } elsif ($stype eq 'studentcalc') {          } elsif ($stype eq 'studentcalc') {
             %current=%allassess;              $sheet->{'rowlabel'} = \%assesslist;
         }          }
         $updatedata{$ENV{'request.course.fn'}.'_'.$stype}=          $updatedata{$sheet->{'coursefilename'}.'_'.$stype.'_'
     join('___;___',%current);                          .$uname.'_'.$udom}=
     } else {                              join('___;___',%{$sheet->{'rowlabel'}});
         return 'Could not access course data';          # Get current from cache
     }      }
 # ------------------------------------------------------ Get current from cache      # Find discrepancies between the course row table and this
     } else {      #
         %current=split(/\_\_\_\;\_\_\_/,      my %f=&getformulas($sheet);
        $updatedata{$ENV{'request.course.fn'}.'_'.$stype});      my $changed=0;
     }  
 # -------------------- Find discrepancies between the course row table and this  
 #  
         my %f=&getformulas($safeeval);  
         my $changed=0;  
   
         my $maxrow=0;  
         my %existing=();  
   
 # ----------------------------------------------------------- Now obsolete rows  
  map {  
     if ($_=~/^A(\d+)/) {  
                 $maxrow=($1>$maxrow)?$1:$maxrow;  
                 my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{$_});  
                 $existing{$usy}=1;  
  unless ((defined($current{$usy})) || (!$1)) {  
    $f{$_}='!!! Obsolete';  
                    $changed=1;  
         } elsif ($ufn) {  
     $current{$usy}  
                        =~s/assesscalc\?usymb\=/assesscalc\?ufn\=$ufn\&usymb\=/;  
                 }  
             }  
         } keys %f;  
   
 # -------------------------------------------------------- New and unknown keys  
        
         map {  
             unless ($existing{$_}) {  
  $changed=1;  
                 $maxrow++;  
                 $f{'A'.$maxrow}=$_;  
             }  
         } keys %current;          
           
         if ($changed) { &setformulas($safeeval,%f); }      $sheet->{'maxrow'} = 0;
       my %existing=();
         &setmaxrow($safeeval,$maxrow);      # Now obsolete rows
         &setrowlabels($safeeval,%current);      foreach (keys(%f)) {
            next if ($_!~/^A(\d+)/);
         undef %current;          if ($1 > $sheet->{'maxrow'}) {
         undef %existing;              $sheet->{'maxrow'} = $1;
           }
           my ($usy,$ufn)=split(/__&&&\__/,$f{$_});
           $existing{$usy}=1;
           unless ((exists($sheet->{'rowlabel'}->{$usy}) && 
                    (defined($sheet->{'rowlabel'}->{$usy})) || (!$1) ||
                   ($f{$_}=~/^(~~~|---)/))){
               $f{$_}='!!! Obsolete';
               $changed=1;
           } elsif ($ufn) {
               $sheet->{'rowlabel'}->{$usy}
                   =~s/assesscalc\?usymb\=/assesscalc\?ufn\=$ufn\&usymb\=/;
           }
       }
       # New and unknown keys
       foreach (keys(%{$sheet->{'rowlabel'}})) {
           unless ($existing{$_}) {
               $changed=1;
               $sheet->{'maxrow'}++;
               $f{'A'.$sheet->{'maxrow'}}=$_;
           }
       }
       if ($changed) { 
           $sheet->{'f'} = \%f;
           &setformulas($sheet); 
       }
       #
       undef %existing;
 }  }
   
 # ------------------------------------------------ Load data for one assessment  # ------------------------------------------------ Load data for one assessment
   
 sub loadstudent {  sub loadstudent {
     my $safeeval=shift;      my ($sheet)=@_;
     my %c=();      my %c=();
     my %f=&getformulas($safeeval);      my %f=&getformulas($sheet);
     $cachedassess=&getuname($safeeval).':'.&getudom($safeeval);      $cachedassess=$sheet->{'uname'}.':'.$sheet->{'udom'};
     %cachedstores=();      # Get ALL the student preformance data
     {      my @tmp = &Apache::lonnet::dump($sheet->{'cid'},
       my $reply=&Apache::lonnet::reply('dump:'.&getudom($safeeval).':'.                                      $sheet->{'udom'},
                                                &getuname($safeeval).':'.                                      $sheet->{'uname'},
                                                &getcid($safeeval),                                      undef);
                                                &getuhome($safeeval));      if ($tmp[0] !~ /^error:/) {
       unless ($reply=~/^error\:/) {          %cachedstores = @tmp;
          map {  
             my ($name,$value)=split(/\=/,$_);  
             $cachedstores{&Apache::lonnet::unescape($name)}=  
                   &Apache::lonnet::unescape($value);  
          } split(/\&/,$reply);  
       }  
     }      }
       undef @tmp;
       # 
     my @assessdata=();      my @assessdata=();
     map {      foreach (keys(%f)) {
  if ($_=~/^A(\d+)/) {   next if ($_!~/^A(\d+)/);
    my $row=$1;          my $row=$1;
            unless (($f{$_}=~/^\!/) || ($row==0)) {          next if (($f{$_}=~/^[\!\~\-]/) || ($row==0));
       my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{$_});          my ($usy,$ufn)=split(/__&&&\__/,$f{$_});
       @assessdata=&exportsheet(&getuname($safeeval),          @assessdata=&exportsheet($sheet,$sheet->{'uname'},
                                        &getudom($safeeval),                                   $sheet->{'udom'},
                                        'assesscalc',$usy,$ufn);                                   'assesscalc',$usy,$ufn);
               my $index=0;          my $index=0;
               map {          foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',
                   if ($assessdata[$index]) {                   'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') {
      my $col=$_;              if ($assessdata[$index]) {
      if ($assessdata[$index]=~/\D/) {                  my $col=$_;
                          $c{$col.$row}="'".$assessdata[$index]."'";                  if ($assessdata[$index]=~/\D/) {
       } else {                      $c{$col.$row}="'".$assessdata[$index]."'";
          $c{$col.$row}=$assessdata[$index];                  } else {
      }                      $c{$col.$row}=$assessdata[$index];
                      unless ($col eq 'A') {                   }
  $f{$col.$row}='import';                  unless ($col eq 'A') { 
                      }                      $f{$col.$row}='import';
   }                  }
                   $index++;              }
               } ('A','B','C','D','E','F','G','H','I','J','K','L','M',              $index++;
                  'N','O','P','Q','R','S','T','U','V','W','X','Y','Z');  
    }  
         }          }
     } keys %f;      }
     $cachedassess='';      $cachedassess='';
     undef %cachedstores;      undef %cachedstores;
     &setformulas($safeeval,%f);      $sheet->{'f'} = \%f;
     &setconstants($safeeval,%c);      &setformulas($sheet);
       &setconstants($sheet,\%c);
 }  }
   
 # --------------------------------------------------- Load data for one student  # --------------------------------------------------- Load data for one student
   #
 sub loadcourse {  sub loadcourse {
     my ($safeeval,$r)=@_;      my ($sheet,$r)=@_;
     my %c=();      my %c=();
     my %f=&getformulas($safeeval);      my %f=&getformulas($sheet);
     my $total=0;      my $total=0;
     map {      foreach (keys(%f)) {
  if ($_=~/^A(\d+)/) {   if ($_=~/^A(\d+)/) {
     unless ($f{$_}=~/^\!/) { $total++; }      unless ($f{$_}=~/^[\!\~\-]/) { $total++; }
         }          }
     } keys %f;      }
     my $now=0;      my $now=0;
     my $since=time;      my $since=time;
     $r->print(<<ENDPOP);      $r->print(<<ENDPOP);
Line 1401  sub loadcourse { Line 2148  sub loadcourse {
 </script>  </script>
 ENDPOP  ENDPOP
     $r->rflush();      $r->rflush();
     map {      foreach (keys(%f)) {
  if ($_=~/^A(\d+)/) {   next if ($_!~/^A(\d+)/);
    my $row=$1;          my $row=$1;
            unless (($f{$_}=~/^\!/)  || ($row==0)) {          next if (($f{$_}=~/^[\!\~\-]/)  || ($row==0));
       my @studentdata=&exportsheet(split(/\:/,$f{$_}),          my ($sname,$sdom) = split(':',$f{$_});
                                            'studentcalc');          my @studentdata=&exportsheet($sheet,$sname,$sdom,'studentcalc');
               undef %userrdatas;          undef %userrdatas;
               $now++;          $now++;
               $r->print('<script>popwin.document.popremain.remaining.value="'.          $r->print('<script>popwin.document.popremain.remaining.value="'.
                   $now.'/'.$total.': '.int((time-$since)/$now*($total-$now)).                    $now.'/'.$total.': '.int((time-$since)/$now*($total-$now)).
                         ' secs remaining";</script>');                    ' secs remaining";</script>');
               $r->rflush();           $r->rflush(); 
           #
               my $index=0;          my $index=0;
               map {          foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M',
                   if ($studentdata[$index]) {                   'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') {
      my $col=$_;              if (defined($studentdata[$index])) {
      if ($studentdata[$index]=~/\D/) {                  my $col=$_;
                          $c{$col.$row}="'".$studentdata[$index]."'";                  if ($studentdata[$index]=~/\D/) {
       } else {                      $c{$col.$row}="'".$studentdata[$index]."'";
          $c{$col.$row}=$studentdata[$index];                  } else {
      }                      $c{$col.$row}=$studentdata[$index];
                      unless ($col eq 'A') {                   }
  $f{$col.$row}='import';                  unless ($col eq 'A') { 
                      }                      $f{$col.$row}='import';
   }                  }
                   $index++;              } 
               } ('A','B','C','D','E','F','G','H','I','J','K','L','M',              $index++;
                  'N','O','P','Q','R','S','T','U','V','W','X','Y','Z');  
    }  
         }          }
     } keys %f;      }
     &setformulas($safeeval,%f);      $sheet->{'f'}=\%f;
     &setconstants($safeeval,%c);      &setformulas($sheet);
       &setconstants($sheet,\%c);
     $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)=@_;
   
     my $uhome=&getuhome($safeeval);      my $uhome = $sheet->{'uhome'};
     my $uname=&getuname($safeeval);      my $uname = $sheet->{'uname'};
     my $udom=&getudom($safeeval);      my $udom  = $sheet->{'udom'};
     my $symb=&getusymb($safeeval);      my $symb  = $sheet->{'usymb'};
     my $cid=&getcid($safeeval);      my $cid   = $sheet->{'cid'};
     my $cnum=&getcnum($safeeval);      my $cnum  = $sheet->{'cnum'};
     my $cdom=&getcdom($safeeval);      my $cdom  = $sheet->{'cdom'};
     my $chome=&getchome($safeeval);      my $chome = $sheet->{'chome'};
   
     my $namespace;      my $namespace;
     unless ($namespace=$cid) { return ''; }      unless ($namespace=$cid) { return ''; }
       # Get stored values
 # ----------------------------------------------------------- Get stored values      my %returnhash=();
       if ($cachedassess eq $uname.':'.$udom) {
    my %returnhash=();          #
           # get data out of the dumped stores
    if ($cachedassess eq $uname.':'.$udom) {          # 
 #          my $version=$cachedstores{'version:'.$symb};
 # get data out of the dumped stores          my $scope;
 #           for ($scope=1;$scope<=$version;$scope++) {
               foreach (split(/\:/,$cachedstores{$scope.':keys:'.$symb})) {
        my $version=$cachedstores{'version:'.$symb};                  $returnhash{$_}=$cachedstores{$scope.':'.$symb.':'.$_};
        my $scope;              } 
        for ($scope=1;$scope<=$version;$scope++) {          }
            map {      } else {
                $returnhash{$_}=$cachedstores{$scope.':'.$symb.':'.$_};          #
            } split(/\:/,$cachedstores{$scope.':keys:'.$symb});           # restore individual
        }          #
           %returnhash = &Apache::lonnet::restore($symb,$namespace,$udom,$uname);
    } else {          for (my $version=1;$version<=$returnhash{'version'};$version++) {
 #              foreach (split(/\:/,$returnhash{$version.':keys'})) {
 # restore individual                  $returnhash{$_}=$returnhash{$version.':'.$_};
 #              } 
           }
     my $answer=&Apache::lonnet::reply(  
        "restore:$udom:$uname:".  
        &Apache::lonnet::escape($namespace).":".  
        &Apache::lonnet::escape($symb),$uhome);  
     map {  
  my ($name,$value)=split(/\=/,$_);  
         $returnhash{&Apache::lonnet::unescape($name)}=  
                     &Apache::lonnet::unescape($value);  
     } split(/\&/,$answer);  
     my $version;  
     for ($version=1;$version<=$returnhash{'version'};$version++) {  
        map {  
           $returnhash{$_}=$returnhash{$version.':'.$_};  
        } split(/\:/,$returnhash{$version.':keys'});  
     }      }
    }      #
 # ----------------------------- 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
 # ---------------------------- initialize coursedata and userdata for this user      #
       # This is dumb.  It is also necessary :(
       my @oldkeys=keys %returnhash;
       #
       foreach my $name (@oldkeys) {
           my $value=$returnhash{$name};
           delete $returnhash{$name};
           $name=~s/\_/\./g;
           $returnhash{$name}=$value;
       }
       # initialize coursedata and userdata for this user
     undef %courseopt;      undef %courseopt;
     undef %useropt;      undef %useropt;
   
     my $userprefix=$uname.'_'.$udom.'_';      my $userprefix=$uname.'_'.$udom.'_';
   
     unless ($uhome eq 'no_host') {       unless ($uhome eq 'no_host') { 
 # -------------------------------------------------------------- Get coursedata          # Get coursedata
       unless          unless ((time-$courserdatas{$cid.'.last_cache'})<240) {
         ((time-$courserdatas{$cid.'.last_cache'})<240) {              my %Tmp = &Apache::lonnet::dump('resourcedata',$cdom,$cnum);
          my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.              $courserdatas{$cid}=\%Tmp;
               ':resourcedata',$chome);  
          if ($reply!~/^error\:/) {  
             $courserdatas{$cid}=$reply;  
             $courserdatas{$cid.'.last_cache'}=time;              $courserdatas{$cid.'.last_cache'}=time;
          }          }
       }          while (my ($name,$value) = each(%{$courserdatas{$cid}})) {
       map {              $courseopt{$userprefix.$name}=$value;
          my ($name,$value)=split(/\=/,$_);          }
          $courseopt{$userprefix.&Apache::lonnet::unescape($name)}=          # Get userdata (if present)
                     &Apache::lonnet::unescape($value);            unless ((time-$userrdatas{$uname.'@'.$udom.'.last_cache'})<240) {
       } split(/\&/,$courserdatas{$cid});              my %Tmp = &Apache::lonnet::dump('resourcedata',$udom,$uname);
 # --------------------------------------------------- Get userdata (if present)              $userrdatas{$cid} = \%Tmp;
       unless              # Most of the time the user does not have a 'resourcedata.db' 
         ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) {              # file.  We need to cache that we got nothing instead of bothering
          my $reply=              # with requesting it every time.
        &Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome);              $userrdatas{$uname.'@'.$udom.'.last_cache'}=time;
          if ($reply!~/^error\:/) {          }
      $userrdatas{$uname.'___'.$udom}=$reply;          while (my ($name,$value) = each(%{$userrdatas{$cid}})) {
      $userrdatas{$uname.'___'.$udom.'.last_cache'}=time;              $useropt{$userprefix.$name}=$value;
          }          }
       }  
       map {  
          my ($name,$value)=split(/\=/,$_);  
          $useropt{$userprefix.&Apache::lonnet::unescape($name)}=  
           &Apache::lonnet::unescape($value);  
       } split(/\&/,$userrdatas{$uname.'___'.$udom});  
     }      }
 # ----------------- now courseopt, useropt initialized for this user and course      # now courseopt, useropt initialized for this user and course
 # (used by parmval)      # (used by parmval)
       #
 #      # Load keys for this assessment only
 # Load keys for this assessment only      #
 #  
     my %thisassess=();      my %thisassess=();
     my ($symap,$syid,$srcf)=split(/\_\_\_/,$symb);      my ($symap,$syid,$srcf)=split(/\_\_\_/,$symb);
           foreach (split(/\,/,&Apache::lonnet::metadata($srcf,'keys'))) {
     map {  
         $thisassess{$_}=1;          $thisassess{$_}=1;
     } split(/\,/,&Apache::lonnet::metadata($srcf,'keys'));      } 
 #      #
 # Load parameters      # Load parameters
 #      #
    my %c=();      my %c=();
       if (tie(%parmhash,'GDBM_File',
    if (tie(%parmhash,'GDBM_File',              $sheet->{'coursefilename'}.'_parms.db',&GDBM_READER(),0640)) {
            &getcfn($safeeval).'_parms.db',&GDBM_READER,0640)) {          my %f=&getformulas($sheet);
     my %f=&getformulas($safeeval);          foreach my $cell (keys(%f))  {
     map {              next if ($cell !~ /^A/);
  if ($_=~/^A/) {              next if  ($f{$cell} =~/^[\!\~\-]/);
             unless ($f{$_}=~/^\!/) {              if ($f{$cell}=~/^parameter/) {
         if ($f{$_}=~/^parameter/) {                  if (defined($thisassess{$f{$cell}})) {
  if ($thisassess{$f{$_}}) {                      my $val       = &parmval($f{$cell},$sheet);
                   my $val=&parmval($f{$_},$safeeval);                      $c{$cell}     = $val;
                   $c{$_}=$val;                      $c{$f{$cell}} = $val;
                   $c{$f{$_}}=$val;                  }
         }              } else {
        } else {                  my $key=$f{$cell};
   my $key=$f{$_};                  my $ckey=$key;
                   my $ckey=$key;                  $key=~s/^stores\_/resource\./;
                   $key=~s/^stores\_/resource\./;                  $key=~s/\_/\./g;
                   $key=~s/\_/\./;                  $c{$cell}=$returnhash{$key};
            $c{$_}=$returnhash{$key};                  $c{$ckey}=$returnhash{$key};
                   $c{$ckey}=$returnhash{$key};              }
        }  
    }  
         }          }
     } keys %f;          untie(%parmhash);
     untie(%parmhash);      }
    }      &setconstants($sheet,\%c);
    &setconstants($safeeval,%c);  
 }  }
   
 # --------------------------------------------------------- Various form fields  # --------------------------------------------------------- Various form fields
Line 1584  sub loadassessment { Line 2312  sub loadassessment {
 sub textfield {  sub textfield {
     my ($title,$name,$value)=@_;      my ($title,$name,$value)=@_;
     return "\n<p><b>$title:</b><br>".      return "\n<p><b>$title:</b><br>".
            '<input type=text name="'.$name.'" size=80 value="'.$value.'">';          '<input type=text name="'.$name.'" size=80 value="'.$value.'">';
 }  }
   
 sub hiddenfield {  sub hiddenfield {
Line 1595  sub hiddenfield { Line 2323  sub hiddenfield {
 sub selectbox {  sub selectbox {
     my ($title,$name,$value,%options)=@_;      my ($title,$name,$value,%options)=@_;
     my $selout="\n<p><b>$title:</b><br>".'<select name="'.$name.'">';      my $selout="\n<p><b>$title:</b><br>".'<select name="'.$name.'">';
     map {      foreach (sort keys(%options)) {
         $selout.='<option value="'.$_.'"';          $selout.='<option value="'.$_.'"';
         if ($_ eq $value) { $selout.=' selected'; }          if ($_ eq $value) { $selout.=' selected'; }
         $selout.='>'.$options{$_}.'</option>';          $selout.='>'.$options{$_}.'</option>';
     } sort keys %options;      }
     return $selout.'</select>';      return $selout.'</select>';
 }  }
   
Line 1609  sub selectbox { Line 2337  sub selectbox {
 #  #
   
 sub updatesheet {  sub updatesheet {
     my $safeeval=shift;      my ($sheet)=@_;
     my $stype=&gettype($safeeval);      my $stype=$sheet->{'sheettype'};
     if ($stype eq 'classcalc') {      if ($stype eq 'classcalc') {
  return &updateclasssheet($safeeval);   return &updateclasssheet($sheet);
     } else {      } else {
         return &updatestudentassesssheet($safeeval);          return &updatestudentassesssheet($sheet);
     }      }
 }  }
   
Line 1624  sub updatesheet { Line 2352  sub updatesheet {
 #  #
   
 sub loadrows {  sub loadrows {
     my ($safeeval,$r)=@_;      my ($sheet,$r)=@_;
     my $stype=&gettype($safeeval);      my $stype=$sheet->{'sheettype'};
     if ($stype eq 'classcalc') {      if ($stype eq 'classcalc') {
  &loadcourse($safeeval,$r);   &loadcourse($sheet,$r);
     } elsif ($stype eq 'studentcalc') {      } elsif ($stype eq 'studentcalc') {
         &loadstudent($safeeval);          &loadstudent($sheet);
     } else {      } else {
         &loadassessment($safeeval);          &loadassessment($sheet);
     }      }
 }  }
   
Line 1641  sub checkthis { Line 2369  sub checkthis {
     my ($keyname,$time)=@_;      my ($keyname,$time)=@_;
     return ($time<$expiredates{$keyname});      return ($time<$expiredates{$keyname});
 }  }
   
 sub forcedrecalc {  sub forcedrecalc {
     my ($uname,$udom,$stype,$usymb)=@_;      my ($uname,$udom,$stype,$usymb)=@_;
     my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;      my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;
Line 1648  sub forcedrecalc { Line 2377  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 1667  sub forcedrecalc { Line 2396  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)=@_;
  my @exportarr=();      $uname = $uname || $sheet->{'uname'};
 #      $udom  = $udom  || $sheet->{'udom'};
 # Check if cached      $stype = $stype || $sheet->{'sheettype'};
 #      my @exportarr=();
       if (defined($usymb) && ($usymb=~/^\_(\w+)/) && 
  my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;          (!defined($fn) || $fn eq '')) {
  my $found='';          $fn='default_'.$1;
       }
  if ($oldsheets{$key}) {      #
      map {      # Check if cached
          my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);      #
          if ($name eq $fn) {      my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;
      $found=$value;      my $found='';
          }      if ($oldsheets{$key}) {
      } split(/\_\_\_\&\_\_\_/,$oldsheets{$key});          foreach (split(/___&\___/,$oldsheets{$key})) {
  }              my ($name,$value)=split(/___=___/,$_);
   
  unless ($found) {  
      &cachedssheets($uname,$udom,&Apache::lonnet::homeserver($uname,$udom));  
      if ($oldsheets{$key}) {  
         map {  
             my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);  
             if ($name eq $fn) {              if ($name eq $fn) {
         $found=$value;                  $found=$value;
             }              }
         } split(/\_\_\_\&\_\_\_/,$oldsheets{$key});          }
      }      }
  }      unless ($found) {
 #          &cachedssheets($sheet,$uname,$udom);
 # Check if still valid          if ($oldsheets{$key}) {
 #              foreach (split(/___&\___/,$oldsheets{$key})) {
  if ($found) {                  my ($name,$value)=split(/___=___/,$_);
      if (&forcedrecalc($uname,$udom,$stype,$usymb)) {                  if ($name eq $fn) {
  $found='';                      $found=$value;
      }                  }
  }              } 
            }
  if ($found) {  
 #  
 # Return what was cached  
 #  
      @exportarr=split(/\_\_\_\;\_\_\_/,$found);  
   
  } else {  
 #  
 # Not cached  
 #          
   
     my $thissheet=&makenewsheet($uname,$udom,$stype,$usymb);  
     &readsheet($thissheet,$fn);  
     &updatesheet($thissheet);  
     &loadrows($thissheet);  
     &calcsheet($thissheet);   
     @exportarr=&exportdata($thissheet);  
 #  
 # Store now  
 #  
     my $cid=$ENV{'request.course.id'};   
     my $current='';  
     if ($stype eq 'studentcalc') {  
        $current=&Apache::lonnet::reply('get:'.  
                                      $ENV{'course.'.$cid.'.domain'}.':'.  
                                      $ENV{'course.'.$cid.'.num'}.  
      ':nohist_calculatedsheets:'.  
                                      &Apache::lonnet::escape($key),  
                                      $ENV{'course.'.$cid.'.home'});  
     } else {  
        $current=&Apache::lonnet::reply('get:'.  
                                      &getudom($thissheet).':'.  
                                      &getuname($thissheet).  
      ':nohist_calculatedsheets_'.  
                                      $ENV{'request.course.id'}.':'.  
                                      &Apache::lonnet::escape($key),  
                                      &getuhome($thissheet));  
   
     }      }
       #
       # Check if still valid
       #
       if ($found) {
           if (&forcedrecalc($uname,$udom,$stype,$usymb)) {
               $found='';
           }
       }
       if ($found) {
           #
           # Return what was cached
           #
           @exportarr=split(/___;___/,$found);
           return @exportarr;
       }
       #
       # Not cached
       #        
       my ($newsheet)=&makenewsheet($uname,$udom,$stype,$usymb);
       &readsheet($newsheet,$fn);
       &updatesheet($newsheet);
       &loadrows($newsheet);
       &calcsheet($newsheet); 
       @exportarr=&exportdata($newsheet);
       ##
       ## Store now
       ##
       #
       # load in the old value
       #
     my %currentlystored=();      my %currentlystored=();
     unless ($current=~/^error\:/) {      if ($stype eq 'studentcalc') {
        map {          my @tmp = &Apache::lonnet::get('nohist_calculatedsheets',
            my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);                                         [$key],
            $currentlystored{$name}=$value;                                         $sheet->{'cdom'},$sheet->{'cnum'});
        } split(/\_\_\_\&\_\_\_/,&Apache::lonnet::unescape($current));          if ($tmp[0]!~/^error/) {
               %currentlystored = @tmp;
           }
       } else {
           my @tmp = &Apache::lonnet::get('nohist_calculatedsheets_'.
                                          $sheet->{'cid'},[$key],
                                          $sheet->{'udom'},$sheet->{'uname'});
           if ($tmp[0]!~/^error/) {
               %currentlystored = @tmp;
           }
     }      }
       #
       # Add the new line
       #
     $currentlystored{$fn}=join('___;___',@exportarr);      $currentlystored{$fn}=join('___;___',@exportarr);
       #
       # Stick everything back together
       #
     my $newstore='';      my $newstore='';
     map {      foreach (keys(%currentlystored)) {
         if ($newstore) { $newstore.='___&___'; }          if ($newstore) { $newstore.='___&___'; }
         $newstore.=$_.'___=___'.$currentlystored{$_};          $newstore.=$_.'___=___'.$currentlystored{$_};
     } keys %currentlystored;      }
     my $now=time;      my $now=time;
       #
       # Store away the new value
       #
     if ($stype eq 'studentcalc') {      if ($stype eq 'studentcalc') {
        &Apache::lonnet::reply('put:'.          &Apache::lonnet::put('nohist_calculatedsheets',
                          $ENV{'course.'.$cid.'.domain'}.':'.                               { $key => $newstore,
                          $ENV{'course.'.$cid.'.num'}.                                 $key.time => $now },
  ':nohist_calculatedsheets:'.                               $sheet->{'cdom'},$sheet->{'cnum'});
                          &Apache::lonnet::escape($key).'='.      } else {
  &Apache::lonnet::escape($newstore).'&'.          &Apache::lonnet::put('nohist_calculatedsheets_'.$sheet->{'cid'},
                          &Apache::lonnet::escape($key).'.time='.$now,                               { $key => $newstore,
                          $ENV{'course.'.$cid.'.home'});                                 $key.time => $now },
    } else {                               $sheet->{'udom'},
        &Apache::lonnet::reply('put:'.                               $sheet->{'uname'})
                          &getudom($thissheet).':'.      }
                          &getuname($thissheet).      return @exportarr;
  ':nohist_calculatedsheets_'.  
                          $ENV{'request.course.id'}.':'.  
                          &Apache::lonnet::escape($key).'='.  
  &Apache::lonnet::escape($newstore).'&'.  
                          &Apache::lonnet::escape($key).'.time='.$now,  
                          &getuhome($thissheet));  
    }  
  }  
  return @exportarr;  
 }  }
   
 # ============================================================ Expiration Dates  # ============================================================ Expiration Dates
 #  #
 # Load previously cached student spreadsheets for this course  # Load previously cached student spreadsheets for this course
 #  #
   
 sub expirationdates {  sub expirationdates {
     undef %expiredates;      undef %expiredates;
     my $cid=$ENV{'request.course.id'};      my $cid=$ENV{'request.course.id'};
     my $reply=&Apache::lonnet::reply('dump:'.      my @tmp = &Apache::lonnet::dump('nohist_expirationdates',
      $ENV{'course.'.$cid.'.domain'}.':'.                                      $ENV{'course.'.$cid.'.domain'},
                                      $ENV{'course.'.$cid.'.num'}.                                      $ENV{'course.'.$cid.'.num'});
      ':nohist_expirationdates',      if (lc($tmp[0])!~/^error/){
                                      $ENV{'course.'.$cid.'.home'});          %expiredates = @tmp;
     unless ($reply=~/^error\:/) {  
  map {  
             my ($name,$value)=split(/\=/,$_);  
             $expiredates{&Apache::lonnet::unescape($name)}  
                         =&Apache::lonnet::unescape($value);  
         } split(/\&/,$reply);  
     }      }
 }  }
   
Line 1816  sub expirationdates { Line 2532  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) {
  map {              $oldsheets{$key} = $value;
             my ($name,$value)=split(/\=/,$_);          }
             $oldsheets{&Apache::lonnet::unescape($name)}  
                       =&Apache::lonnet::unescape($value);  
         } split(/\&/,$reply);  
     }      }
 }  }
   
Line 1836  sub cachedcsheets { Line 2549  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{$sheet->{'uname'}.'_'.$sheet->{'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/) {
  map {              my %StupidTempHash = @tmp;
             my ($name,$value)=split(/\=/,$_);              while (my ($key,$value) = each %StupidTempHash) {
             $oldsheets{&Apache::lonnet::unescape($name)}                  $oldsheets{$key} = $value;
                       =&Apache::lonnet::unescape($value);              }
         } split(/\&/,$reply);              $loadedcaches{$sheet->{'uname'}.'_'.$sheet->{'udom'}}=1;
           }
     }      }
     $loadedcaches{$sname.'_'.$sdom}=1;  
   }  
 }  }
   
 # ===================================================== Calculated sheets cache  # ===================================================== Calculated sheets cache
Line 1864  sub cachedssheets { Line 2576  sub cachedssheets {
 # Interactive call to screen  # Interactive call to screen
 #  #
 #  #
   
   
 sub handler {  sub handler {
     my $r=shift;      my $r=shift;
   
       if (! exists($ENV{'form.Status'})) {
           $ENV{'form.Status'} = 'Active';
       }
       if (! exists($ENV{'form.output'})) {
           $ENV{'form.output'} = 'HTML';
       }
       # 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; } 
       
     if ($r->header_only) {      if ($r->header_only) {
       $r->content_type('text/html');          $r->content_type('text/html');
       $r->send_http_header;          $r->send_http_header;
       return OK;          return OK;
       }
       # Global directory configs
       $includedir = $r->dir_config('lonIncludes');
       $tmpdir = $r->dir_config('lonDaemons').'/tmp/';
       # Needs to be in a course
       if (! $ENV{'request.course.fn'}) { 
           # Not in a course, or not allowed to modify parms
           $ENV{'user.error.msg'}=
               $r->uri.":opa:0:0:Cannot modify spreadsheet";
           return HTTP_NOT_ACCEPTABLE; 
       }
       # Get query string for limited number of parameters
       &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
                                               ['uname','udom','usymb','ufn']);
       if ($ENV{'request.role'} =~ /^st\./) {
           delete $ENV{'form.unewfield'}   if (exists($ENV{'form.unewfield'}));
           delete $ENV{'form.unewformula'} if (exists($ENV{'form.unewformula'}));
     }      }
       if (($ENV{'form.usymb'}=~/^\_(\w+)/) && (!$ENV{'form.ufn'})) {
 # ---------------------------------------------------- Global directory configs          $ENV{'form.ufn'}='default_'.$1;
       }
 $includedir=$r->dir_config('lonIncludes');      # Interactive loading of specific sheet?
 $tmpdir=$r->dir_config('lonDaemons').'/tmp/';  
   
 # ----------------------------------------------------- Needs to be in a course  
   
   if ($ENV{'request.course.fn'}) {   
   
 # --------------------------- Get query string for limited number of parameters  
   
     map {  
        my ($name, $value) = split(/=/,$_);  
        $value =~ tr/+/ /;  
        $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;  
        if (($name eq 'uname') || ($name eq 'udom') ||   
            ($name eq 'usymb') || ($name eq 'ufn')) {  
            unless ($ENV{'form.'.$name}) {  
               $ENV{'form.'.$name}=$value;  
    }  
        }  
     } (split(/&/,$ENV{'QUERY_STRING'}));  
   
 # -------------------------------------- Interactive loading of specific sheet?  
     if (($ENV{'form.load'}) && ($ENV{'form.loadthissheet'} ne 'Default')) {      if (($ENV{'form.load'}) && ($ENV{'form.loadthissheet'} ne 'Default')) {
  $ENV{'form.ufn'}=$ENV{'form.loadthissheet'};          $ENV{'form.ufn'}=$ENV{'form.loadthissheet'};
     }      }
 # ------------------------------------------- Nothing there? Must be login user      #
       # Determine the user name and domain for the sheet.
     my $aname;      my $aname;
     my $adom;      my $adom;
   
     unless ($ENV{'form.uname'}) {      unless ($ENV{'form.uname'}) {
  $aname=$ENV{'user.name'};          $aname=$ENV{'user.name'};
         $adom=$ENV{'user.domain'};          $adom=$ENV{'user.domain'};
     } else {      } else {
         $aname=$ENV{'form.uname'};          $aname=$ENV{'form.uname'};
         $adom=$ENV{'form.udom'};          $adom=$ENV{'form.udom'};
     }      }
       #
 # ------------------------------------------------------------------- Open page      # Open page
   
     $r->content_type('text/html');      $r->content_type('text/html');
     $r->header_out('Cache-control','no-cache');      $r->header_out('Cache-control','no-cache');
     $r->header_out('Pragma','no-cache');      $r->header_out('Pragma','no-cache');
     $r->send_http_header;      $r->send_http_header;
       # Screen output
 # --------------------------------------------------------------- Screen output  
   
     $r->print('<html><head><title>LON-CAPA Spreadsheet</title>');      $r->print('<html><head><title>LON-CAPA Spreadsheet</title>');
     $r->print(<<ENDSCRIPT);      if ($ENV{'request.role'} !~ /^st\./) {
           $r->print(<<ENDSCRIPT);
 <script language="JavaScript">  <script language="JavaScript">
   
     function celledit(cn,cf) {      function celledit(cn,cf) {
         var cnf=prompt(cn,cf);          var cnf=prompt(cn,cf);
  if (cnf!=null) {          if (cnf!=null) {
     document.sheet.unewfield.value=cn;              document.sheet.unewfield.value=cn;
             document.sheet.unewformula.value=cnf;              document.sheet.unewformula.value=cnf;
             document.sheet.submit();              document.sheet.submit();
         }          }
Line 1943  $tmpdir=$r->dir_config('lonDaemons').'/t Line 2660  $tmpdir=$r->dir_config('lonDaemons').'/t
         document.sheet.submit();          document.sheet.submit();
     }      }
   
       function insertrow(cn) {
    document.sheet.unewfield.value='insertrow';
           document.sheet.unewformula.value=cn;
           document.sheet.submit();
       }
   
 </script>  </script>
 ENDSCRIPT  ENDSCRIPT
     $r->print('</head><body bgcolor="#FFFFFF">'.      }
        '<img align=right src=/adm/lonIcons/lonlogos.gif>'.      $r->print('</head>'.&Apache::loncommon::bodytag('Grades Spreadsheet').
        '<h1>LON-CAPA Spreadsheet</h1>'.                '<form action="'.$r->uri.'" name=sheet method=post>');
        '<form action="'.$r->uri.'" name=sheet method=post>'.      $r->print(&hiddenfield('uname',$ENV{'form.uname'}).
        &hiddenfield('uname',$ENV{'form.uname'}).                &hiddenfield('udom',$ENV{'form.udom'}).
        &hiddenfield('udom',$ENV{'form.udom'}).                &hiddenfield('usymb',$ENV{'form.usymb'}).
        &hiddenfield('usymb',$ENV{'form.usymb'}).                &hiddenfield('unewfield','').
        &hiddenfield('unewfield','').                &hiddenfield('unewformula',''));
        &hiddenfield('unewformula',''));  
   
 # ---------------------- Make sure that this gets out, even if user hits "stop"  
   
     $r->rflush();      $r->rflush();
       #
 # ---------------------------------------------------------------- Full recalc?      # Full recalc?
   
   
     if ($ENV{'form.forcerecalc'}) {      if ($ENV{'form.forcerecalc'}) {
  $r->print('<h4>Completely Recalculating Sheet ...</h4>');          $r->print('<h4>Completely Recalculating Sheet ...</h4>');
         undef %spreadsheets;          undef %spreadsheets;
         undef %courserdatas;          undef %courserdatas;
         undef %userrdatas;          undef %userrdatas;
         undef %defaultsheets;          undef %defaultsheets;
         undef %updatedata;          undef %updatedata;
    }      }
        # Read new sheet or modified worksheet
 # ---------------------------------------- Read new sheet or modified worksheet  
   
     $r->uri=~/\/(\w+)$/;      $r->uri=~/\/(\w+)$/;
       my ($sheet)=&makenewsheet($aname,$adom,$1,$ENV{'form.usymb'});
     my $asheet=&makenewsheet($aname,$adom,$1,$ENV{'form.usymb'});      #
       # If a new formula had been entered, go from work copy
 # ------------------------ If a new formula had been entered, go from work copy  
   
     if ($ENV{'form.unewfield'}) {      if ($ENV{'form.unewfield'}) {
         $r->print('<h2>Modified Workcopy</h2>');          $r->print('<h2>Modified Workcopy</h2>');
         $ENV{'form.unewformula'}=~s/\'/\"/g;          $ENV{'form.unewformula'}=~s/\'/\"/g;
         $r->print('<p>New formula: '.$ENV{'form.unewfield'}.'='.          $r->print('<p>New formula: '.$ENV{'form.unewfield'}.'='.
                   $ENV{'form.unewformula'}.'<p>');                    $ENV{'form.unewformula'}.'<p>');
         &setfilename($asheet,$ENV{'form.ufn'});          $sheet->{'filename'} = $ENV{'form.ufn'};
  &tmpread($asheet,          &tmpread($sheet,$ENV{'form.unewfield'},$ENV{'form.unewformula'});
                  $ENV{'form.unewfield'},$ENV{'form.unewformula'});      } elsif ($ENV{'form.saveas'}) {
           $sheet->{'filename'} = $ENV{'form.ufn'};
      } elsif ($ENV{'form.saveas'}) {          &tmpread($sheet);
         &setfilename($asheet,$ENV{'form.ufn'});      } else {
  &tmpread($asheet);          &readsheet($sheet,$ENV{'form.ufn'});
     } else {      }
         &readsheet($asheet,$ENV{'form.ufn'});      # Print out user information
     }      if ($sheet->{'sheettype'} ne 'classcalc') {
           $r->print('<p><b>User:</b> '.$sheet->{'uname'}.
 # -------------------------------------------------- Print out user information                    '<br><b>Domain:</b> '.$sheet->{'udom'});
           $r->print('<br><b>Section/Group:</b> '.$sheet->{'csec'});
     unless (&gettype($asheet) eq 'classcalc') {          if ($ENV{'form.usymb'}) {
         $r->print('<p><b>User:</b> '.&getuname($asheet).              $r->print('<br><b>Assessment:</b> <tt>'.
                   '<br><b>Domain:</b> '.&getudom($asheet));                        $ENV{'form.usymb'}.'</tt>');
         if (&getcsec($asheet) eq '-1') {  
            $r->print('<h3><font color=red>'.  
                      'Not a student in this course</font></h3>');  
         } else {  
            $r->print('<br><b>Section/Group:</b> '.&getcsec($asheet));  
         }          }
     }      }
       #
 # ---------------------------------------------------------------- Course title      # Check user permissions
       if (($sheet->{'sheettype'} eq 'classcalc'       ) || 
     $r->print('<h1>'.          ($sheet->{'uname'}     ne $ENV{'user.name'} ) ||
             $ENV{'course.'.$ENV{'request.course.id'}.'.description'}.          ($sheet->{'udom'}      ne $ENV{'user.domain'})) {
              '</h1><h3>'.localtime().'</h3>');          unless (&Apache::lonnet::allowed('vgr',$sheet->{'cid'})) {
               $r->print('<h1>Access Permission Denied</h1>'.
 # ---------------------------------------------------- See if user can see this                        '</form></body></html>');
   
     if ((&gettype($asheet) eq 'classcalc') ||   
         (&getuname($asheet) ne $ENV{'user.name'}) ||  
         (&getudom($asheet) ne $ENV{'user.domain'})) {  
         unless (&Apache::lonnet::allowed('vgr',&getcid($asheet))) {  
     $r->print(  
            '<h1>Access Permission Denied</h1></form></body></html>');  
             return OK;              return OK;
         }          }
     }      }
       # Additional options
 # ---------------------------------------------------------- Additional options      $r->print('<br />'.
                 '<input type="submit" name="forcerecalc" '.
     $r->print(                'value="Completely Recalculate Sheet"><p>');
  '<input type=submit name=forcerecalc value="Completely Recalculate Sheet"><p>'      if ($sheet->{'sheettype'} eq 'assesscalc') {
  );          $r->print('<p><font size=+2>'.
     if (&gettype($asheet) eq 'assesscalc') {                    '<a href="/adm/studentcalc?'.
        $r->print ('<p><font size=+2><a href="/adm/studentcalc?uname='.                    'uname='.$sheet->{'uname'}.
                                                &getuname($asheet).                    '&udom='.$sheet->{'udom'}.'">'.
                                                '&udom='.&getudom($asheet).                    'Level up: Student Sheet</a></font><p>');
                   '">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)))) {  
        $r->print (  
                    '<p><font size=+2><a href="/adm/classcalc">'.  
                    'Level up: Course Sheet</a></font><p>');                     'Level up: Course Sheet</a></font><p>');
     }      }
           # Save dialog
   
 # ----------------------------------------------------------------- Save dialog  
   
   
     if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {      if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
         my $fname=$ENV{'form.ufn'};          my $fname=$ENV{'form.ufn'};
         $fname=~s/\_[^\_]+$//;          $fname=~s/\_[^\_]+$//;
         if ($fname eq 'default') { $fname='course_default'; }          if ($fname eq 'default') { $fname='course_default'; }
         $r->print('<input type=submit name=saveas value="Save as ...">'.          $r->print('<input type=submit name=saveas value="Save as ...">'.
               '<input type=text size=20 name=newfn value="'.$fname.                    '<input type=text size=20 name=newfn value="'.$fname.'">'.
               '"> (make default: <input type=checkbox name="makedefufn">)<p>');                    'make default: <input type=checkbox name="makedefufn"><p>');
     }      }
       $r->print(&hiddenfield('ufn',$sheet->{'filename'}));
     $r->print(&hiddenfield('ufn',&getfilename($asheet)));      # Load dialog
   
 # ----------------------------------------------------------------- Load dialog  
     if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {      if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
  $r->print('<p><input type=submit name=load value="Load ...">'.          $r->print('<p><input type=submit name=load value="Load ...">'.
                   '<select name="loadthissheet">'.                    '<select name="loadthissheet">'.
                   '<option name="default">Default</option>');                    '<option name="default">Default</option>');
         map {          foreach (&othersheets($sheet)) {
     $r->print('<option name="'.$_.'"');              $r->print('<option name="'.$_.'"');
             if ($ENV{'form.ufn'} eq $_) {              if ($ENV{'form.ufn'} eq $_) {
                $r->print(' selected');                  $r->print(' selected');
             }              }
             $r->print('>'.$_.'</option>');              $r->print('>'.$_.'</option>');
         } &othersheets($asheet,&gettype($asheet));          } 
         $r->print('</select><p>');          $r->print('</select><p>');
         if (&gettype($asheet) eq 'studentcalc') {          if ($sheet->{'sheettype'} eq 'studentcalc') {
     &setothersheets($asheet,&othersheets($asheet,'assesscalc'));              &setothersheets($sheet,
                               &othersheets($sheet,'assesscalc'));
         }          }
     }      }
       # Cached sheets
 # --------------------------------------------------------------- Cached sheets  
   
     &expirationdates();      &expirationdates();
   
     undef %oldsheets;      undef %oldsheets;
     undef %loadedcaches;      undef %loadedcaches;
       if ($sheet->{'sheettype'} eq 'classcalc') {
     if (&gettype($asheet) eq 'classcalc') {          $r->print("Loading previously calculated student sheets ...\n");
         $r->print("Loading previously calculated student sheets ...<br>\n");  
         $r->rflush();          $r->rflush();
         &cachedcsheets();          &cachedcsheets();
     } elsif (&gettype($asheet) eq 'studentcalc') {      } elsif ($sheet->{'sheettype'} eq 'studentcalc') {
         $r->print("Loading previously calculated assessment sheets ...<br>\n");          $r->print("Loading previously calculated assessment sheets ...\n");
         $r->rflush();          $r->rflush();
         &cachedssheets(&getuname($asheet),&getudom($asheet),          &cachedssheets($sheet);
                        &getuhome($asheet));  
     }      }
       # Update sheet, load rows
 # ----------------------------------------------------- Update sheet, load rows  
   
     $r->print("Loaded sheet(s), updating rows ...<br>\n");      $r->print("Loaded sheet(s), updating rows ...<br>\n");
     $r->rflush();      $r->rflush();
       #
     &updatesheet($asheet);      &updatesheet($sheet);
       $r->print("Updated rows, loading row data ...\n");
     $r->print("Updated rows, loading row data ...<br>\n");  
     $r->rflush();      $r->rflush();
       #
     &loadrows($asheet,$r);      &loadrows($sheet,$r);
   
     $r->print("Loaded row data, calculating sheet ...<br>\n");      $r->print("Loaded row data, calculating sheet ...<br>\n");
     $r->rflush();      $r->rflush();
       #
     my $calcoutput=&calcsheet($asheet);      my $calcoutput=&calcsheet($sheet);
     $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>');      $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>');
       # See if something to save
 # ---------------------------------------------------- See if something to save  
   
     if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {      if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
         my $fname='';          my $fname='';
  if ($ENV{'form.saveas'} && ($fname=$ENV{'form.newfn'})) {          if ($ENV{'form.saveas'} && ($fname=$ENV{'form.newfn'})) {
             $fname=~s/\W/\_/g;              $fname=~s/\W/\_/g;
             if ($fname eq 'default') { $fname='course_default'; }              if ($fname eq 'default') { $fname='course_default'; }
             $fname.='_'.&gettype($asheet);              $fname.='_'.$sheet->{'sheettype'};
             &setfilename($asheet,$fname);              $sheet->{'filename'} = $fname;
             $ENV{'form.ufn'}=$fname;              $ENV{'form.ufn'}=$fname;
     $r->print('<p>Saving spreadsheet: '.              $r->print('<p>Saving spreadsheet: '.
                          &writesheet($asheet,$ENV{'form.makedefufn'}).'<p>');                        &writesheet($sheet,$ENV{'form.makedefufn'}).
  }                        '<p>');
           }
     }      }
       #
 # ------------------------------------------------ Write the modified worksheet      # Write the modified worksheet
       $r->print('<b>Current sheet:</b> '.$sheet->{'filename'}.'<p>');
    $r->print('<b>Current sheet:</b> '.&getfilename($asheet).'<p>');      &tmpwrite($sheet);
       if ($sheet->{'sheettype'} eq 'studentcalc') {
    &tmpwrite($asheet);          $r->print('<br>Show rows with empty A column: ');
   
     if (&gettype($asheet) eq 'studentcalc') {  
  $r->print('<br>Show rows with empty A column: ');  
     } else {      } else {
         $r->print('<br>Show empty rows: ');          $r->print('<br>Show empty rows: ');
     }       }
     $r->print('<input type=checkbox name=showall onClick="submit()"');      #
     if ($ENV{'form.showall'}) { $r->print(' checked'); }      $r->print(&hiddenfield('userselhidden','true').
                 '<input type="checkbox" name="showall" onClick="submit()"');
       #
       if ($ENV{'form.showall'}) { 
           $r->print(' checked'); 
       } else {
           unless ($ENV{'form.userselhidden'}) {
               unless 
                   ($ENV{'course.'.$sheet->{'cid'}.'.hideemptyrows'} eq 'yes') {
                       $r->print(' checked');
                       $ENV{'form.showall'}=1;
                   }
           }
       }
     $r->print('>');      $r->print('>');
       #
 # ------------------------------------------------------------- Print out sheet      # CSV format checkbox (classcalc sheets only)
       $r->print(' Output as <select name="output" size="1" onClick="submit()">'.
     &outsheet($r,$asheet);                "\n");
       foreach my $mode (qw/HTML CSV EXCEL/) {
           $r->print('<option value="'.$mode.'"');
           if ($ENV{'form.output'} eq $mode) {
               $r->print(' selected ');
           } 
           $r->print('>'.$mode.'</option>'."\n");
       }
       $r->print("</select>\n");
       #
       if ($sheet->{'sheettype'} eq 'classcalc') {
           $r->print('&nbsp;Student Status: '.
                     &Apache::lonhtmlcommon::StatusOptions
                     ($ENV{'form.Status'},'sheet'));
       }
       #
       # Buttons to insert rows
   #    $r->print(<<ENDINSERTBUTTONS);
   #<br>
   #<input type='button' onClick='insertrow("top");' 
   #value='Insert Row Top'>
   #<input type='button' onClick='insertrow("bottom");' 
   #value='Insert Row Bottom'><br>
   #ENDINSERTBUTTONS
       # Print out sheet
       &outsheet($r,$sheet);
     $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;

Removed from v.1.65  
changed lines
  Added in v.1.134


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