--- loncom/interface/Attic/lonspreadsheet.pm 2002/04/12 21:41:13 1.86 +++ loncom/interface/Attic/lonspreadsheet.pm 2002/11/21 19:26:34 1.147 @@ -1,5 +1,5 @@ # -# $Id: lonspreadsheet.pm,v 1.86 2002/04/12 21:41:13 matthew Exp $ +# $Id: lonspreadsheet.pm,v 1.147 2002/11/21 19:26:34 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -26,16 +26,6 @@ # The LearningOnline Network with CAPA # Spreadsheet/Grades Display Handler # -# 11/11,11/15,11/27,12/04,12/05,12/06,12/07, -# 12/08,12/09,12/11,12/12,12/15,12/16,12/18,12/19,12/30, -# 01/01/01,02/01,03/01,19/01,20/01,22/01, -# 03/05,03/08,03/10,03/12,03/13,03/15,03/17, -# 03/19,03/20,03/21,03/27,04/05,04/09, -# 07/09,07/14,07/21,09/01,09/10,9/11,9/12,9/13,9/14,9/17, -# 10/16,10/17,10/20,11/05,11/28,12/27 Gerd Kortemeyer -# 01/14/02 Matthew -# 02/04/02 Matthew - # POD required stuff: =head1 NAME @@ -54,7 +44,6 @@ not the grades of their peers. The spre 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 @@ -64,13 +53,22 @@ built-in functions. package Apache::lonspreadsheet; use strict; +use Apache::Constants qw(:common :http); +use Apache::lonnet; +use Apache::lonhtmlcommon; +use Apache::loncoursedata; +use Apache::File(); use Safe; use Safe::Hole; use Opcode; -use Apache::lonnet; -use Apache::Constants qw(:common :http); use GDBM_File; use HTML::TokeParser; +use Spreadsheet::WriteExcel; + +# +# Caches for coursewide information +# +my %Section; # # Caches for previously calculated spreadsheets @@ -96,7 +94,7 @@ my %spreadsheets; my %courserdatas; my %userrdatas; my %defaultsheets; -my %updatedata; +my %rowlabel_cache; # # These global hashes are dependent on user, course and resource, @@ -106,99 +104,37 @@ my %courseopt; my %useropt; my %parmhash; +# +# Some hashes for stats on timing and performance +# + +my %starttimes; +my %usedtimes; +my %numbertimes; + # Stuff that only the screen handler can know my $includedir; my $tmpdir; -# ------------------------------------------------ Send critical message -sub send_crit_msg { - my ($uname,$udom,$subject,$message,$sendback) = @_; - my $result = &Apache::lonmsg::user_crit_msg($uname,$udom,$subject, - $message,$sendback); - return ($result eq 'ok' ? 1 : 0); -} - -# ------------------------------------------------ Send noncritical message -sub send_msg { - my ($uname,$udom,$subject,$message) = @_; - my $result = &Apache::lonmsg::user_normal_msg($uname,$udom, - $subject,$message); - return ($result eq 'ok' ? 1 : 0); -} - - # ============================================================================= # ===================================== Implements an instance of a spreadsheet -sub initsheet { - my $safeeval = new Safe(shift); - my $safehole = new Safe::Hole; - $safeeval->permit("entereval"); - $safeeval->permit(":base_math"); - $safeeval->permit("sort"); - $safeeval->deny(":base_io"); - $safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&EXT'); - $safehole->wrap(\&send_msg, $safeeval,"&send_msg"); - $safehole->wrap(\&send_crit_msg,$safeeval,"&send_crit_msg"); - 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=''; - +## +## mask - used to reside in the safe space. +## sub mask { my ($lower,$upper)=@_; - - $lower=~/([A-Za-z]|\*)(\d+|\*)/; - my $la=$1; - my $ld=$2; - - $upper=~/([A-Za-z]|\*)(\d+|\*)/; - my $ua=$1; - my $ud=$2; + $upper = $lower if (! defined($upper)); + # + my ($la,$ld) = ($lower=~/([A-Za-z]|\*)(\d+|\*)/); + my ($ua,$ud) = ($upper=~/([A-Za-z]|\*)(\d+|\*)/); + # my $alpha=''; my $num=''; - + # if (($la eq '*') || ($ua eq '*')) { - $alpha='[A-Za-z]'; + $alpha='[A-Za-z]'; } else { if (($la=~/[A-Z]/) && ($ua=~/[A-Z]/) || ($la=~/[a-z]/) && ($ua=~/[a-z]/)) { @@ -207,7 +143,6 @@ sub mask { $alpha='['.$la.'-Za-'.$ua.']'; } } - if (($ld eq '*') || ($ud eq '*')) { $num='\d+'; } else { @@ -227,7 +162,9 @@ sub mask { } else { my @lda=($ld=~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++) { if ($lda[$i]==$uda[$i]) { $num.=$lda[$i]; @@ -251,8 +188,8 @@ sub mask { } $num.=')'; } else { - if ($lda[$#lda]!=$uda[$#uda]) { - $num.='['.$lda[$#lda].'-'.$uda[$#uda].']'; + if ($lda[-1]!=$uda[-1]) { + $num.='['.$lda[-1].'-'.$uda[-1].']'; } } } @@ -260,6 +197,78 @@ sub mask { 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) @@ -391,8 +400,8 @@ sub CDLHASH { if ($key =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) { my $keymask = &mask($key); # Assume the keys are addresses - my @Temp = grep /$keymask/,keys(%v); - @Keys = $v{@Temp}; + my @Temp = grep /$keymask/,keys(%sheet_values); + @Keys = $sheet_values{@Temp}; } else { $Keys[0]= $key; } @@ -403,8 +412,8 @@ sub CDLHASH { @Keys = @Temp; if ($value =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) { my $valmask = &mask($value); - my @Temp = grep /$valmask/,keys(%v); - @Values =$v{@Temp}; + my @Temp = grep /$valmask/,keys(%sheet_values); + @Values =$sheet_values{@Temp}; } else { $Values[0]= $value; } @@ -438,7 +447,7 @@ sub GETHASH { $index = 0; } if ($key =~ /^[A-z]\d+$/) { - $key = $v{$key}; + $key = $sheet_values{$key}; } return $hashes{$name}->{$key}->[$index]; } @@ -495,8 +504,8 @@ sub HASH { if ($key =~ /[A-z](\-[A-z])?\d+(\-\d+)?/) { my $keymask = &mask($key); # Assume the keys are addresses - my @Temp = grep /$keymask/,keys(%v); - @Keys = $v{@Temp}; + my @Temp = grep /$keymask/,keys(%sheet_values); + @Keys = $sheet_values{@Temp}; } else { $Keys[0]= $key; } @@ -508,8 +517,8 @@ sub HASH { # 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(%v); - @Values =$v{@Temp}; + my @Temp = grep /$valmask/,keys(%sheet_values); + @Values =$sheet_values{@Temp}; } else { $Values[0]= $value; } @@ -538,7 +547,7 @@ returns the number of items in the range #------------------------------------------------------- sub NUM { my $mask=mask(@_); - my $num= $#{@{grep(/$mask/,keys(%v))}}+1; + my $num= $#{@{grep(/$mask/,keys(%sheet_values))}}+1; return $num; } @@ -546,8 +555,8 @@ sub BIN { my ($low,$high,$lower,$upper)=@_; my $mask=mask($lower,$upper); my $num=0; - foreach (grep /$mask/,keys(%v)) { - if (($v{$_}>=$low) && ($v{$_}<=$high)) { + foreach (grep /$mask/,keys(%sheet_values)) { + if (($sheet_values{$_}>=$low) && ($sheet_values{$_}<=$high)) { $num++; } } @@ -567,8 +576,8 @@ returns the sum of items in the range. sub SUM { my $mask=mask(@_); my $sum=0; - foreach (grep /$mask/,keys(%v)) { - $sum+=$v{$_}; + foreach (grep /$mask/,keys(%sheet_values)) { + $sum+=$sheet_values{$_}; } return $sum; } @@ -584,9 +593,10 @@ compute the average of the items in the #------------------------------------------------------- sub MEAN { my $mask=mask(@_); - my $sum=0; my $num=0; - foreach (grep /$mask/,keys(%v)) { - $sum+=$v{$_}; + my $sum=0; + my $num=0; + foreach (grep /$mask/,keys(%sheet_values)) { + $sum+=$sheet_values{$_}; $num++; } if ($num) { @@ -608,15 +618,15 @@ compute the standard deviation of the it sub STDDEV { my $mask=mask(@_); my $sum=0; my $num=0; - foreach (grep /$mask/,keys(%v)) { - $sum+=$v{$_}; + foreach (grep /$mask/,keys(%sheet_values)) { + $sum+=$sheet_values{$_}; $num++; } unless ($num>1) { return undef; } my $mean=$sum/$num; $sum=0; - foreach (grep /$mask/,keys(%v)) { - $sum+=($v{$_}-$mean)**2; + foreach (grep /$mask/,keys(%sheet_values)) { + $sum+=($sheet_values{$_}-$mean)**2; } return sqrt($sum/($num-1)); } @@ -633,8 +643,8 @@ compute the product of the items in the sub PROD { my $mask=mask(@_); my $prod=1; - foreach (grep /$mask/,keys(%v)) { - $prod*=$v{$_}; + foreach (grep /$mask/,keys(%sheet_values)) { + $prod*=$sheet_values{$_}; } return $prod; } @@ -651,9 +661,9 @@ compute the maximum of the items in the sub MAX { my $mask=mask(@_); my $max='-'; - foreach (grep /$mask/,keys(%v)) { - unless ($max) { $max=$v{$_}; } - if (($v{$_}>$max) || ($max eq '-')) { $max=$v{$_}; } + foreach (grep /$mask/,keys(%sheet_values)) { + unless ($max) { $max=$sheet_values{$_}; } + if (($sheet_values{$_}>$max) || ($max eq '-')) { $max=$sheet_values{$_}; } } return $max; } @@ -670,9 +680,11 @@ compute the minimum of the items in the sub MIN { my $mask=mask(@_); my $min='-'; - foreach (grep /$mask/,keys(%v)) { - unless ($max) { $max=$v{$_}; } - if (($v{$_}<$min) || ($min eq '-')) { $min=$v{$_}; } + foreach (grep /$mask/,keys(%sheet_values)) { + unless ($max) { $max=$sheet_values{$_}; } + if (($sheet_values{$_}<$min) || ($min eq '-')) { + $min=$sheet_values{$_}; + } } return $min; } @@ -691,8 +703,8 @@ sub SUMMAX { my ($num,$lower,$upper)=@_; my $mask=mask($lower,$upper); my @inside=(); - foreach (grep /$mask/,keys(%v)) { - push (@inside,$v{$_}); + foreach (grep /$mask/,keys(%sheet_values)) { + push (@inside,$sheet_values{$_}); } @inside=sort(@inside); my $sum=0; my $i; @@ -716,8 +728,8 @@ sub SUMMIN { my ($num,$lower,$upper)=@_; my $mask=mask($lower,$upper); my @inside=(); - foreach (grep /$mask/,keys(%v)) { - $inside[$#inside+1]=$v{$_}; + foreach (grep /$mask/,keys(%sheet_values)) { + $inside[$#inside+1]=$sheet_values{$_}; } @inside=sort(@inside); my $sum=0; my $i; @@ -729,38 +741,51 @@ sub SUMMIN { #------------------------------------------------------- -=item SEND_CRIT_MSG(subject,message) +=item MINPARM(parametername) -Send a critical message to a student. +Returns the minimum value of the parameters matching the parametername. +parametername should be a string such as 'duedate'. =cut #------------------------------------------------------- -sub SEND_CRIT_MSG { - my ($subject,$message) = @_; - my $name = $uname; - my $dom = $udom; - return (&send_crit_msg($name,$dom,$subject,$message) ? 'Message Sent.' - : 'Error sending message'); +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 SEND_MSG(subject,message) +=item MAXPARM(parametername) -Send a message to a student. +Returns the maximum value of the parameters matching the input parameter name. +parametername should be a string such as 'duedate'. =cut #------------------------------------------------------- -sub SEND_MSG { - my ($subject,$message) = @_; - my $name = $uname; - my $dom = $udom; - return (&send_msg($name,$dom,$subject,$message) ? 'Message Sent.' - : 'Error sending message'); +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 { my $expression=shift; if ($expression=~/^\&/) { @@ -793,7 +818,37 @@ sub expandnamed { return 0; } } 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; } } @@ -805,28 +860,32 @@ sub sett { } else { $pattern='[A-Z]'; } + # Deal with the template row foreach (keys(%f)) { - if ($_=~/template\_(\w)/) { - my $col=$1; - unless ($col=~/^$pattern/) { - foreach (keys(%f)) { - if ($_=~/A(\d+)/) { - my $trow=$1; - if ($trow) { - my $lb=$col.$trow; - $t{$lb}=$f{'template_'.$col}; - $t{$lb}=~s/\#/$trow/g; - $t{$lb}=~s/\.\.+/\,/g; - $t{$lb}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g; - $t{$lb}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge; - } - } - } - } - } + next if ($_!~/template\_(\w)/); + my $col=$1; + next if ($col=~/^$pattern/); + foreach (keys(%f)) { + next if ($_!~/A(\d+)/); + my $trow=$1; + next if (! $trow); + # Get the name of this cell + my $lb=$col.$trow; + # Grab the template declaration + $t{$lb}=$f{'template_'.$col}; + # Replace '#' with the row number + $t{$lb}=~s/\#/$trow/g; + # Replace '....' with ',' + $t{$lb}=~s/\.\.+/\,/g; + # Replace 'A0' with the value from 'A0' + $t{$lb}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g; + # Replace parameters + $t{$lb}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge; + } } + # Deal with the normal cells foreach (keys(%f)) { - if (($f{$_}) && ($_!~/template\_/)) { + if (exists($f{$_}) && ($_!~/template\_/)) { my $matches=($_=~/^$pattern(\d+)/); if (($matches) && ($1)) { unless ($f{$_}=~/^\!/) { @@ -835,446 +894,831 @@ sub sett { } else { $t{$_}=$f{$_}; $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; } } } + # 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'}=~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; } sub calc { - %v=(); + undef %sheet_values; &sett(); my $notfinished=1; + my $lastcalc=''; my $depth=0; while ($notfinished) { $notfinished=0; foreach (keys(%t)) { - my $old=$v{$_}; - $v{$_}=eval($t{$_}); + #$errorlog .= "$_:".$t{$_}; + my $old=$sheet_values{$_}; + $sheet_values{$_}=eval $t{$_}; if ($@) { - %v=(); - return $@; + undef %sheet_values; + return $_.': '.$@; } - if ($v{$_} ne $old) { $notfinished=1; } + if ($sheet_values{$_} ne $old) { $notfinished=1; $lastcalc=$_; } + #$errorlog .= ":".$sheet_values{$_}."\n"; } $depth++; if ($depth>100) { - %v=(); - return 'Maximum calculation depth exceeded'; + undef %sheet_values; + return $lastcalc.': Maximum calculation depth exceeded'; } } return ''; } +# ------------------------------------------- End of "Inside of the safe space" +ENDDEFS + $safeeval->reval($code); + return $safeeval; +} + +# +# +# sub templaterow { + my $sheet = shift; my @cols=(); - $cols[0]='Template'; + my $rowlabel = 'Template'; foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', 'a','b','c','d','e','f','g','h','i','j','k','l','m', 'n','o','p','q','r','s','t','u','v','w','x','y','z') { - my $fm=$f{'template_'.$_}; + my $fm=$sheet->{'f'}->{'template_'.$_}; $fm=~s/[\'\"]/\&\#34;/g; - $cols[$#cols+1]="'template_$_','$fm'".'___eq___'.$fm; + push(@cols,{ name => 'template_'.$_, + formula => $fm, + value => $fm }); } - return @cols; + return ($rowlabel,@cols); } sub outrowassess { - my $n=shift; + # $n is the current row number + my ($sheet,$n) = @_; my @cols=(); + my $rowlabel=''; if ($n) { - my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{'A'.$n}); - $cols[0]=$rl{$usy}.'
'. - ''; + my ($usy,$ufn)=split(/__&&&\__/,$sheet->{'f'}->{'A'.$n}); + if (exists($sheet->{'rowlabel'}->{$usy})) { + $rowlabel = $sheet->{'rowlabel'}->{$usy}; + } else { + $rowlabel = ''; + } } else { - $cols[0]='Export'; + $rowlabel = 'Export'; } foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', 'a','b','c','d','e','f','g','h','i','j','k','l','m', 'n','o','p','q','r','s','t','u','v','w','x','y','z') { - my $fm=$f{$_.$n}; + my $fm=$sheet->{'f'}->{$_.$n}; $fm=~s/[\'\"]/\&\#34;/g; - push(@cols,"'$_$n','$fm'".'___eq___'.$v{$_.$n}); + push(@cols,{ name => $_.$n, + formula => $fm, + value => $sheet->{'values'}->{$_.$n}}); } - return @cols; + return ($rowlabel,@cols); } sub outrow { - my $n=shift; + my ($sheet,$n)=@_; my @cols=(); + my $rowlabel; if ($n) { - $cols[0]=$rl{$f{'A'.$n}}; + $rowlabel = $sheet->{'rowlabel'}->{$sheet->{'f'}->{'A'.$n}}; } else { - $cols[0]='Export'; + if ($sheet->{'sheettype'} eq 'classcalc') { + $rowlabel = 'Summary'; + } else { + $rowlabel = 'Export'; + } } foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', 'a','b','c','d','e','f','g','h','i','j','k','l','m', 'n','o','p','q','r','s','t','u','v','w','x','y','z') { - my $fm=$f{$_.$n}; + my $fm=$sheet->{'f'}->{$_.$n}; $fm=~s/[\'\"]/\&\#34;/g; - $cols[$#cols+1]="'$_$n','$fm'".'___eq___'.$v{$_.$n}; + push(@cols,{ name => $_.$n, + formula => $fm, + value => $sheet->{'values'}->{$_.$n}}); } - return @cols; -} - -sub exportrowa { - my @exportarray=(); - foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', - 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') { - $exportarray[$#exportarray+1]=$v{$_.'0'}; - } - return @exportarray; -} - -# ------------------------------------------- End of "Inside of the safe space" -ENDDEFS - $safeeval->reval($code); - return $safeeval; + return ($rowlabel,@cols); } # ------------------------------------------------ Add or change formula values - sub setformulas { - my ($safeeval,%f)=@_; - %{$safeeval->varglob('f')}=%f; + my ($sheet)=shift; + %{$sheet->{'safe'}->varglob('f')}=%{$sheet->{'f'}}; } # ------------------------------------------------ Add or change formula values - sub setconstants { - my ($safeeval,%c)=@_; - %{$safeeval->varglob('c')}=%c; + my ($sheet)=shift; + my ($constants) = @_; + if (! ref($constants)) { + my %tmp = @_; + $constants = \%tmp; + } + $sheet->{'constants'} = $constants; + return %{$sheet->{'safe'}->varglob('c')}=%{$sheet->{'constants'}}; } # --------------------------------------------- Set names of other spreadsheets - sub setothersheets { - my ($safeeval,@os)=@_; - @{$safeeval->varglob('os')}=@os; + my $sheet = shift; + my @othersheets = @_; + $sheet->{'othersheets'} = \@othersheets; + @{$sheet->{'safe'}->varglob('os')}=@othersheets; + return; } # ------------------------------------------------ Add or change formula values - sub setrowlabels { - my ($safeeval,%rl)=@_; - %{$safeeval->varglob('rl')}=%rl; + my $sheet=shift; + my ($rowlabel) = @_; + if (! ref($rowlabel)) { + my %tmp = @_; + $rowlabel = \%tmp; + } + $sheet->{'rowlabel'}=$rowlabel; } # ------------------------------------------------------- Calculate spreadsheet - sub calcsheet { - my $safeeval=shift; - $safeeval->reval('&calc();'); -} - -# ------------------------------------------------------------------ Get values - -sub getvalues { - my $safeeval=shift; - return $safeeval->reval('%v'); + my $sheet=shift; + my $result = $sheet->{'safe'}->reval('&calc();'); + %{$sheet->{'values'}} = %{$sheet->{'safe'}->varglob('sheet_values')}; + return $result; } # ---------------------------------------------------------------- Get formulas - +# Return a copy of the formulas sub getformulas { - my $safeeval=shift; - return %{$safeeval->varglob('f')}; -} - -# -------------------------------------------------------------------- Get type - -sub gettype { - my $safeeval=shift; - return $safeeval->reval('$sheettype'); + my $sheet = shift; + return %{$sheet->{'safe'}->varglob('f')}; } -# ------------------------------------------------------------------ Set maxrow - -sub setmaxrow { - my ($safeeval,$row)=@_; - $safeeval->reval('$maxrow='.$row.';'); -} - -# ------------------------------------------------------------------ Get maxrow - -sub getmaxrow { - my $safeeval=shift; - return $safeeval->reval('$maxrow'); -} - -# ---------------------------------------------------------------- Set filename - -sub setfilename { - my ($safeeval,$fn)=@_; - $safeeval->reval('$filename="'.$fn.'";'); -} - -# ---------------------------------------------------------------- Get filename - -sub getfilename { - my $safeeval=shift; - return $safeeval->reval('$filename'); -} - -# --------------------------------------------------------------- Get course ID - -sub getcid { - my $safeeval=shift; - return $safeeval->reval('$cid'); -} - -# --------------------------------------------------------- Get course filename - -sub getcfn { - my $safeeval=shift; - return $safeeval->reval('$cfn'); -} - -# ----------------------------------------------------------- Get course number - -sub getcnum { - my $safeeval=shift; - return $safeeval->reval('$cnum'); -} - -# ------------------------------------------------------------- Get course home - -sub getchome { - my $safeeval=shift; - return $safeeval->reval('$chome'); -} - -# ----------------------------------------------------------- Get course domain - -sub getcdom { - my $safeeval=shift; - return $safeeval->reval('$cdom'); -} - -# ---------------------------------------------------------- Get course section - -sub getcsec { - my $safeeval=shift; - return $safeeval->reval('$csec'); -} - -# --------------------------------------------------------------- Get user name - -sub getuname { - my $safeeval=shift; - return $safeeval->reval('$uname'); +sub geterrorlog { + my $sheet = shift; + return ${$sheet->{'safe'}->varglob('errorlog')}; +} + +sub gettitle { + my $sheet = shift; + if ($sheet->{'sheettype'} eq 'classcalc') { + return $sheet->{'coursedesc'}; + } elsif ($sheet->{'sheettype'} eq 'studentcalc') { + return 'Grades for '.$sheet->{'uname'}.'@'.$sheet->{'udom'}; + } elsif ($sheet->{'sheettype'} eq 'assesscalc') { + if (($sheet->{'usymb'} eq '_feedback') || + ($sheet->{'usymb'} eq '_evaluation') || + ($sheet->{'usymb'} eq '_discussion') || + ($sheet->{'usymb'} eq '_tutoring')) { + my $title = $sheet->{'usymb'}; + $title =~ s/^_//; + $title = ucfirst($title); + return $title; + } + return if (! defined($sheet->{'mapid'}) || + $sheet->{'mapid'} !~ /^\d+$/); + my $mapid = $sheet->{'mapid'}; + return if (! defined($sheet->{'resid'}) || + $sheet->{'resid'} !~ /^\d+$/); + my $resid = $sheet->{'resid'}; + my %course_db; + tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db', + &GDBM_READER(),0640); + return if (! tied(%course_db)); + my $key = 'title_'.$mapid.'.'.$resid; + my $title = ''; + if (exists($course_db{$key})) { + $title = $course_db{$key}; + } else { + $title = $sheet->{'usymb'}; + } + untie (%course_db); + return $title; + } } -# ------------------------------------------------------------- Get user domain - -sub getudom { - my $safeeval=shift; - return $safeeval->reval('$udom'); +# ----------------------------------------------------- Get value of $f{'A'.$n} +sub getfa { + my $sheet = shift; + my ($n)=@_; + return $sheet->{'safe'}->reval('$f{"A'.$n.'"}'); } -# --------------------------------------------------------------- Get user home - -sub getuhome { - my $safeeval=shift; - return $safeeval->reval('$uhome'); +# ------------------------------------------------------------- Export of A-row +sub exportdata { + my $sheet=shift; + 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; } -# -------------------------------------------------------------------- Get symb -sub getusymb { - my $safeeval=shift; - return $safeeval->reval('$usymb'); -} - -# ------------------------------------------------------------- Export of A-row -sub exportdata { - my $safeeval=shift; - return $safeeval->reval('&exportrowa()'); +sub update_student_sheet{ + my ($sheet,$r,$c) = @_; + # Load in the studentcalc sheet + &readsheet($sheet,'default_studentcalc'); + # Determine the structure (contained assessments, etc) of the sheet + &updatesheet($sheet); + # Load in the cached sheets for this student + &cachedssheets($sheet); + # Load in the (possibly cached) data from the assessment sheets + &loadstudent($sheet,$r,$c); + # Compute the sheet + &calcsheet($sheet); } - # ========================================================== End of Spreadsheet # ============================================================================= - # -# Procedures for screen output +# Procedures for spreadsheet output # # --------------------------------------------- Produce output row n from sheet -sub rown { - my ($safeeval,$n)=@_; - my $defaultbg; - my $rowdata=''; - my $dataflag=0; - unless ($n eq '-') { - $defaultbg=((($n-1)/5)==int(($n-1)/5))?'#E0E0':'#FFFF'; - } else { - $defaultbg='#E0FF'; - } - unless ($ENV{'form.showcsv'}) { - $rowdata.="\n$n"; - } else { - $rowdata.="\n".'"'.$n.'"'; - } - my $showf=0; - my $proc; - my $maxred; - my $sheettype=&gettype($safeeval); - if ($sheettype eq 'studentcalc') { - $proc='&outrowassess'; - $maxred=26; - } else { - $proc='&outrow'; +sub get_row { + my ($sheet,$n) = @_; + my ($rowlabel,@rowdata); + if ($n eq '-') { + ($rowlabel,@rowdata) = &templaterow($sheet); + } elsif ($sheet->{'sheettype'} eq 'studentcalc') { + ($rowlabel,@rowdata) = &outrowassess($sheet,$n); + } else { + ($rowlabel,@rowdata) = &outrow($sheet,$n); + } + return ($rowlabel,@rowdata); +} + +######################################################################## +######################################################################## +sub sort_indicies { + my $sheet = shift; + my @sortidx=(); + # + if ($sheet->{'sheettype'} eq 'classcalc') { + my @sortby=(undef); + # Skip row 0 + for (my $row=1;$row<=$sheet->{'maxrow'};$row++) { + my (undef,$sname,$sdom,$fullname,$section,$id) = + split(':',$sheet->{'rowlabel'}->{$sheet->{'f'}->{'A'.$row}}); + push (@sortby, lc($fullname)); + push (@sortidx, $row); + } + @sortidx = sort { $sortby[$a] cmp $sortby[$b]; } @sortidx; + } elsif ($sheet->{'sheettype'} eq 'studentcalc') { + my @sortby1=(undef); + my @sortby2=(undef); + # Skip row 0 + for (my $row=1;$row<=$sheet->{'maxrow'};$row++) { + my (undef,$symb,$uname,$udom,$mapid,$resid,$title) = + split(':',$sheet->{'rowlabel'}->{$sheet->{'f'}->{'A'.$row}}); + $symb = &Apache::lonnet::unescape($symb); + my ($sequence) = ($symb =~ /\/([^\/]*\.sequence)/); + if ($sequence eq '') { + $sequence = $symb; + } + push (@sortby1, $sequence); + push (@sortby2, $title); + push (@sortidx, $row); + } + @sortidx = sort { $sortby1[$a] cmp $sortby1[$b] || + $sortby2[$a] cmp $sortby2[$b] } @sortidx; + } else { + my @sortby=(undef); + # Skip row 0 + for (my $row=1;$row<=$sheet->{'maxrow'};$row++) { + push (@sortby, $sheet->{'safe'}->reval('$f{"A'.$row.'"}')); + push (@sortidx, $row); + } + @sortidx = sort { $sortby[$a] cmp $sortby[$b]; } @sortidx; + } + return @sortidx; +} + +############################################################# +### ### +### Spreadsheet Output Routines ### +### ### +############################################################# + +############################################ +## HTML output routines ## +############################################ +sub html_editable_cell { + my ($cell,$bgcolor) = @_; + my $result; + my ($name,$formula,$value); + if (defined($cell)) { + $name = $cell->{'name'}; + $formula = $cell->{'formula'}; + $value = $cell->{'value'}; + } + $name = '' if (! defined($name)); + $formula = '' if (! defined($formula)); + if (! defined($value)) { + $value = '#'; + if ($formula ne '') { + $value = 'undefined value'; + } + } + if ($value =~ /^\s*$/ ) { + $value = '#'; + } + $formula =~ s/\n/\\n/gs; + $result .= ''.$value.''; + return $result; +} + +sub html_uneditable_cell { + my ($cell,$bgcolor) = @_; + my $value = (defined($cell) ? $cell->{'value'} : ''); + return ' '.$value.' '; +} + +sub outsheet_html { + my ($sheet,$r) = @_; + my ($num_uneditable,$realm,$row_type); + if ($sheet->{'sheettype'} eq 'assesscalc') { + $num_uneditable = 1; + $realm = 'Assessment'; + $row_type = 'Item'; + } elsif ($sheet->{'sheettype'} eq 'studentcalc') { + $num_uneditable = 26; + $realm = 'User'; + $row_type = 'Assessment'; + } elsif ($sheet->{'sheettype'} eq 'classcalc') { + $num_uneditable = 26; + $realm = 'Course'; + $row_type = 'Student'; + } else { + return; # error + } + #################################### + # Print out header table + #################################### + my $num_left = 52-$num_uneditable; + my $tabledata =<<"END"; + + + + + + +END + my $label_num = 0; + foreach (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')){ + if ($label_num<$num_uneditable) { + $tabledata.='"; + $label_num++; } - if ($sheettype eq 'assesscalc') { - $maxred=1; - } else { - $maxred=26; + $tabledata.="\n"; + $r->print($tabledata); + #################################### + # Print out template row + #################################### + my ($rowlabel,@rowdata) = &get_row($sheet,'-'); + my $row_html = ''; + my $num_cols_output = 0; + foreach my $cell (@rowdata) { + if ($num_cols_output++ < $num_uneditable) { + $row_html .= ''; } - if ($n eq '-') { $proc='&templaterow'; $n=-1; $dataflag=1; } - foreach ($safeeval->reval($proc.'('.$n.')')) { - my $bgcolor=$defaultbg.((($showf-1)/5==int(($showf-1)/5))?'99':'DD'); - my ($fm,$vl)=split(/\_\_\_eq\_\_\_/,$_); - if ((($vl ne '') || ($vl eq '0')) && - (($showf==1) || ($sheettype ne 'studentcalc'))) { $dataflag=1; } - if ($showf==0) { $vl=$_; } - unless ($ENV{'form.showcsv'}) { - if ($showf<=$maxred) { $bgcolor='#FFDDDD'; } - if (($n==0) && ($showf<=26)) { $bgcolor='#CCCCFF'; } - if (($showf>$maxred) || ((!$n) && ($showf>0))) { - if ($vl eq '') { - $vl='#'; - } - $rowdata.= - ''; - } else { - $rowdata.=''; - } - } else { - $rowdata.=',"'.$vl.'"'; - } - $showf++; - } # End of foreach($safeval...) - if ($ENV{'form.showall'} || ($dataflag)) { - return $rowdata.($ENV{'form.showcsv'}?'':''); - } else { - return ''; + $row_html.= "\n"; + $r->print($row_html); + #################################### + # Print out summary/export row + #################################### + my ($rowlabel,@rowdata) = &get_row($sheet,'0'); + $row_html = ''; + $num_cols_output = 0; + foreach my $cell (@rowdata) { + if ($num_cols_output++ < 26) { + $row_html .= ''; } + $row_html.= "\n"; + $r->print($row_html); + $r->print('
$realm + Import + Calculations
'; + } else { + $tabledata.=''; + } + $tabledata.="$_
'.&format_html_rowlabel($rowlabel).''; + $row_html .= &html_uneditable_cell($cell,'#FFDDDD'); + } else { + $row_html .= ''; + $row_html .= &html_editable_cell($cell,'#E0FFDD'); + } + $row_html .= ''.$vl. - ' '.$vl.' 
'.&format_html_rowlabel($rowlabel).''; + $row_html .= &html_editable_cell($cell,'#CCCCFF'); + } else { + $row_html .= ''; + $row_html .= &html_uneditable_cell(undef,'#CCCCFF'); + } + $row_html .= '
'); + #################################### + # Prepare to output rows + #################################### + my @Rows = &sort_indicies($sheet); + # + # Loop through the rows and output them one at a time + my $rows_output=0; + foreach my $rownum (@Rows) { + my ($rowlabel,@rowdata) = &get_row($sheet,$rownum); + next if ($rowlabel =~ /^\s*$/); + next if (($sheet->{'sheettype'} eq 'assesscalc') && + (! $ENV{'form.showall'}) && + ($rowdata[0]->{'value'} =~ /^\s*$/)); + if (! $ENV{'form.showall'} && + $sheet->{'sheettype'} =~ /^(studentcalc|classcalc)$/) { + my $row_is_empty = 1; + foreach my $cell (@rowdata) { + if ($cell->{'value'} !~ /^\s*$/) { + $row_is_empty = 0; + last; + } + } + next if ($row_is_empty); + } + # + my $defaultbg='#E0FF'; + # + my $row_html ="\n".''.$rownum. + ''; + # + if ($sheet->{'sheettype'} eq 'classcalc') { + $row_html.=''.&format_html_rowlabel($rowlabel).''; + # 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.=''.&format_html_rowlabel($rowlabel); + $row_html.= '
'. + ''; + } elsif ($sheet->{'sheettype'} eq 'assesscalc') { + $row_html.=''.&format_html_rowlabel($rowlabel).''; + } + # + 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.=''; + if ($shown_cells < $num_uneditable) { + $row_html .= &html_uneditable_cell($cell,$bgcolor); + } else { + $row_html .= &html_editable_cell($cell,$bgcolor); + } + $row_html.=''; + $shown_cells++; + } + if ($row_html) { + if ($rows_output % 25 == 0) { + $r->print("\n
\n"); + $r->rflush(); + $r->print(''. + ''. + '\n"); + } + $rows_output++; + $r->print($row_html); + } + } + # + $r->print('
 '.$row_type.''. + join('', + (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. + 'abcdefghijklmnopqrstuvwxyz'))). + "
'); + # + # Debugging code (be sure to uncomment errorlog code in safe space): + # + # $r->print("\n
");
+    # $r->print(&geterrorlog($sheet));
+    # $r->print("\n
"); + return 1; } -# ------------------------------------------------------------- Print out sheet +############################################ +## csv output routines ## +############################################ +sub outsheet_csv { + my ($sheet,$r) = @_; + my $csvdata = ''; + my @Values; + #################################### + # Prepare to output rows + #################################### + my @Rows = &sort_indicies($sheet); + # + # Loop through the rows and output them one at a time + my $rows_output=0; + foreach my $rownum (@Rows) { + my ($rowlabel,@rowdata) = &get_row($sheet,$rownum); + next if ($rowlabel =~ /^\s*$/); + push (@Values,&format_csv_rowlabel($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("
\n".$csvdata."
\n"); + return 0; + } + print $file $csvdata; + close($file); + $r->print('

'. + 'Your CSV spreadsheet.'."\n"); + # + return 1; +} -sub outsheet { - my ($r,$safeeval)=@_; - my $maxred; - my $realm; - if (&gettype($safeeval) eq 'assesscalc') { - $maxred=1; - $realm='Assessment'; - } elsif (&gettype($safeeval) eq 'studentcalc') { - $maxred=26; - $realm='User'; - } else { - $maxred=26; - $realm='Course'; - } - my $maxyellow=52-$maxred; - my $tabledata; - unless ($ENV{'form.showcsv'}) { - $tabledata= - ''. - ''. - ''; - my $showf=0; - foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', - 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', - 'a','b','c','d','e','f','g','h','i','j','k','l','m', - 'n','o','p','q','r','s','t','u','v','w','x','y','z') { - $showf++; - if ($showf<=$maxred) { - $tabledata.='"; + if(defined($c) && ($c->aborted())) { + last; + } + } + # + if(! $c->aborted() ) { + $r->print('All students spreadsheets completed!
'); + $r->rflush(); + # + # &export_sheet_as_excel fills $worksheet with the data from $sheet + &export_sheet_as_excel($sheet,$main_worksheet); + # + $workbook->close(); + # Okay, the spreadsheet is taken care of, so give the user a link. + $r->print('

'. + 'Your Excel spreadsheet.'."\n"); + } else { + $workbook->close(); # Not sure how necessary this is. + #unlink('/home/httpd'.$filename); # No need to keep this around? + } + return 1; +} + +sub outsheet_excel { + my ($sheet,$r) = @_; + my ($workbook,$filename) = &create_excel_spreadsheet($sheet,$r); + return undef if (! defined($workbook)); + my $sheetname; + if ($sheet->{'sheettype'} eq 'classcalc') { + $sheetname = 'Main'; + } elsif ($sheet->{'sheettype'} eq 'studentcalc') { + $sheetname = $sheet->{'uname'}.'@'.$sheet->{'udom'}; + } elsif ($sheet->{'sheettype'} eq 'assesscalc') { + $sheetname = $sheet->{'uname'}.'@'.$sheet->{'udom'}.' assessment'; + } + my $worksheet = $workbook->addworksheet($sheetname); + # + # &export_sheet_as_excel fills $worksheet with the data from $sheet + &export_sheet_as_excel($sheet,$worksheet); + # + $workbook->close(); + # Okay, the spreadsheet is taken care of, so give the user a link. + $r->print('

'. + 'Your Excel spreadsheet.'."\n"); + return 1; +} + +sub create_excel_spreadsheet { + my ($sheet,$r) = @_; + my $filename = '/prtspool/'. + $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'. + time.'_'.rand(1000000000).'.xls'; + my $workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename); + if (! defined($workbook)) { + $r->log_error("Error creating excel spreadsheet $filename: $!"); + $r->print("Problems creating new Excel file. ". + "This error has been logged. ". + "Please alert your LON-CAPA administrator"); + return undef; } - $tabledata.=''.&rown($safeeval,'-').&rown($safeeval,0); - } else { $tabledata='
'; }
+    #
+    # The spreadsheet stores temporary data in files, then put them
+    # together.  If needed we should be able to disable this (memory only).
+    # The temporary directory must be specified before calling 'addworksheet'.
+    # File::Temp is used to determine the temporary directory.
+    $workbook->set_tempdir('/home/httpd/perl/tmp');
+    #
+    # Determine the name to give the worksheet
+    return ($workbook,$filename);
+}
 
-    $r->print($tabledata);
+sub export_sheet_as_excel {
+    my $sheet = shift;
+    my $worksheet = shift;
+    #
+    my $rows_output = 0;
+    my $cols_output = 0;
+    ####################################
+    #    Write an identifying row      #
+    ####################################
+    my @Headerinfo = ($sheet->{'coursedesc'});
+    my $title = &gettitle($sheet);
+    $cols_output = 0;    
+    if (defined($title)) {
+        $worksheet->write($rows_output++,$cols_output++,$title);
+    }
+    ####################################
+    #   Write the summary/export row   #
+    ####################################
+    my ($rowlabel,@rowdata) = &get_row($sheet,'0');
+    my $label = &format_excel_rowlabel($rowlabel);
+    $cols_output = 0;
+    $worksheet->write($rows_output,$cols_output++,$label);
+    foreach my $cell (@rowdata) {
+        $worksheet->write($rows_output,$cols_output++,$cell->{'value'});
+    }
+    $rows_output+= 2;   # Skip a row, just for fun
+    ####################################
+    # Prepare to output rows
+    ####################################
+    my @Rows = &sort_indicies($sheet);
+    #
+    # Loop through the rows and output them one at a time
+    foreach my $rownum (@Rows) {
+        my ($rowlabel,@rowdata) = &get_row($sheet,$rownum);
+        next if ($rowlabel =~ /^[\s]*$/);
+        $cols_output = 0;
+        my $label = &format_excel_rowlabel($rowlabel);
+        if ( ! $ENV{'form.showall'} &&
+             $sheet->{'sheettype'} =~ /^(studentcalc|classcalc)$/) {
+            my $row_is_empty = 1;
+            foreach my $cell (@rowdata) {
+                if ($cell->{'value'} !~  /^\s*$/) {
+                    $row_is_empty = 0;
+                    last;
+                }
+            }
+            next if ($row_is_empty);
+        }
+        $worksheet->write($rows_output,$cols_output++,$label);
+        if (ref($label)) {
+            $cols_output = (scalar(@$label));
+        }
+        foreach my $cell (@rowdata) {
+            $worksheet->write($rows_output,$cols_output++,$cell->{'value'});
+        }
+        $rows_output++;
+    }
+    return;
+}
 
-    my $row;
-    my $maxrow=&getmaxrow($safeeval);
+############################################
+##          XML output routines           ##
+############################################
+sub outsheet_xml   {
+    my ($sheet,$r) = @_;
+    ## Someday XML
+    ## Will be rendered for the user
+    ## But not on this day
+}
 
-    my @sortby=();
-    my @sortidx=();
-    for ($row=1;$row<=$maxrow;$row++) {
-       $sortby[$row-1]=$safeeval->reval('$f{"A'.$row.'"}');
-       $sortidx[$row-1]=$row-1;
-    }
-    @sortidx=sort { $sortby[$a] cmp $sortby[$b]; } @sortidx;
-
-        my $what='Student';
-        if (&gettype($safeeval) eq 'assesscalc') {
-	    $what='Item';
-	} elsif (&gettype($safeeval) eq 'studentcalc') {
-            $what='Assessment';
-        }
-
-    my $n=0;
-    for ($row=0;$row<$maxrow;$row++) {
-     my $thisrow=&rown($safeeval,$sortidx[$row]+1);
-     if ($thisrow) {
-       if (($n/25==int($n/25)) && (!$ENV{'form.showcsv'})) {
-	$r->print("
'. - $realm.'ImportCalculations
'; - } else { - $tabledata.=''; +############################################ +## Excel output routines ## +############################################ +sub outsheet_recursive_excel { + my ($sheet,$r) = @_; + my $c = $r->connection; + return undef if ($sheet->{'sheettype'} ne 'classcalc'); + my ($workbook,$filename) = &create_excel_spreadsheet($sheet,$r); + return undef if (! defined($workbook)); + # + # Create main worksheet + my $main_worksheet = $workbook->addworksheet('main'); + # + # Figure out who the students are + my %f=&getformulas($sheet); + my $count = 0; + $r->print(< +Compiling Excel Workbook with a worksheet for each student. +

+This operation may take longer than a complete recalculation of the +spreadsheet. +

+To abort this operation, hit the stop button on your browser. +

+A link to the spreadsheet will be available at the end of this process. +

+

+END + $r->rflush(); + my $starttime = time; + foreach my $rownum (&sort_indicies($sheet)) { + $count++; + my ($sname,$sdom) = split(':',$f{'A'.$rownum}); + my $student_excel_worksheet=$workbook->addworksheet($sname.'@'.$sdom); + # Create a new spreadsheet + my $studentsheet = &makenewsheet($sname,$sdom,'studentcalc',undef); + # Read in the spreadsheet definition + &update_student_sheet($studentsheet,$r,$c); + # Stuff the sheet into excel + &export_sheet_as_excel($studentsheet,$student_excel_worksheet); + my $totaltime = int((time - $starttime) / $count * $sheet->{'maxrow'}); + my $timeleft = int((time - $starttime) / $count * ($sheet->{'maxrow'} - $count)); + if ($count % 5 == 0) { + $r->print($count.' students completed.'. + ' Time remaining: '.$timeleft.' sec. '. + ' Estimated total time: '.$totaltime." sec
\n"); + $r->rflush(); } - $tabledata.="$_

\n
\n"); - $r->rflush(); - $r->print(''); - foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', - 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', - 'a','b','c','d','e','f','g','h','i','j','k','l','m', - 'n','o','p','q','r','s','t','u','v','w','x','y','z') { - $r->print(''); - } - $r->print(''); - } - $n++; - $r->print($thisrow); - } +## +## Outsheet - calls other outsheet_* functions +## +sub outsheet { + my ($sheet,$r)=@_; + if (! exists($ENV{'form.output'})) { + $ENV{'form.output'} = 'HTML'; + } + if (lc($ENV{'form.output'}) eq 'csv') { + &outsheet_csv($sheet,$r); + } elsif (lc($ENV{'form.output'}) eq 'excel') { + &outsheet_excel($sheet,$r); + } elsif (lc($ENV{'form.output'}) eq 'recursive excel') { + &outsheet_recursive_excel($sheet,$r); +# } elsif (lc($ENV{'form.output'}) eq 'xml' ) { +# &outsheet_xml($sheet,$r); + } else { + &outsheet_html($sheet,$r); } - $r->print($ENV{'form.showcsv'}?'':'
 '.$what.''.$_.'
'); } -# -# ----------------------------------------------- Read list of available sheets -# +######################################################################## +######################################################################## sub othersheets { - my ($safeeval,$stype)=@_; + my ($sheet,$stype)=@_; + $stype = $sheet->{'sheettype'} if (! defined($stype)); # - my $cnum=&getcnum($safeeval); - my $cdom=&getcdom($safeeval); - my $chome=&getchome($safeeval); + my $cnum = $sheet->{'cnum'}; + my $cdom = $sheet->{'cdom'}; + my $chome = $sheet->{'chome'}; # my @alternatives=(); my %results=&Apache::lonnet::dump($stype.'_spreadsheets',$cdom,$cnum); @@ -1285,7 +1729,6 @@ sub othersheets { return @alternatives; } - # # -------------------------------------- Parse a spreadsheet # @@ -1317,179 +1760,184 @@ sub parse_sheet { # # -------------------------------------- Read spreadsheet formulas for a course # - sub readsheet { - my ($safeeval,$fn)=@_; - my $stype=&gettype($safeeval); - my $cnum=&getcnum($safeeval); - my $cdom=&getcdom($safeeval); - my $chome=&getchome($safeeval); - - if (! defined($fn)) { - # There is no filename. Look for defaults in course and global, cache - unless ($fn=$defaultsheets{$cnum.'_'.$cdom.'_'.$stype}) { - my %tmphash = &Apache::lonnet::get('environment', - ['spreadsheet_default_'.$stype], - $cdom,$cnum); - my ($tmp) = keys(%tmphash); - if ($tmp =~ /^(con_lost|error|no_such_host)/i) { - $fn = 'default_'.$stype; - } else { - $fn = $tmphash{'spreadsheet_default_'.$stype}; - } - $defaultsheets{$cnum.'_'.$cdom.'_'.$stype}=$fn; - } - } - -# ---------------------------------------------------------- fn now has a value - - &setfilename($safeeval,$fn); - -# ------------------------------------------------------ see if sheet is cached - my $fstring=''; - if ($fstring=$spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}) { - &setformulas($safeeval,split(/\_\_\_\;\_\_\_/,$fstring)); - } else { - -# ---------------------------------------------------- Not cached, need to read - - my %f=(); - - if ($fn=~/^default\_/) { - my $sheetxml=''; - my $fh; - my $dfn=$fn; - $dfn=~s/\_/\./g; - if ($fh=Apache::File->new($includedir.'/'.$dfn)) { - $sheetxml=join('',<$fh>); - } else { - $sheetxml='"Error"'; - } - %f=%{&parse_sheet(\$sheetxml)}; - } elsif($fn=~/\/*\.spreadsheet$/) { - my $sheetxml=&Apache::lonnet::getfile - (&Apache::lonnet::filelocation('',$fn)); - if ($sheetxml == -1) { - $sheetxml='"Error loading spreadsheet ' - .$fn.'"'; - } - %f=%{&parse_sheet(\$sheetxml)}; - } else { - my $sheet=''; - my %tmphash = &Apache::lonnet::dump($fn,$cdom,$cnum); - my ($tmp) = keys(%tmphash); - unless ($tmp =~ /^(con_lost|error|no_such_host)/i) { - foreach (keys(%tmphash)) { - $f{$_}=$tmphash{$_}; - } - } - } -# --------------------------------------------------------------- Cache and set - $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f); - &setformulas($safeeval,%f); + my ($sheet,$fn)=@_; + # + my $stype = $sheet->{'sheettype'}; + my $cnum = $sheet->{'cnum'}; + my $cdom = $sheet->{'cdom'}; + my $chome = $sheet->{'chome'}; + # + if (! defined($fn)) { + # There is no filename. Look for defaults in course and global, cache + unless ($fn=$defaultsheets{$cnum.'_'.$cdom.'_'.$stype}) { + my %tmphash = &Apache::lonnet::get('environment', + ['spreadsheet_default_'.$stype], + $cdom,$cnum); + my ($tmp) = keys(%tmphash); + if ($tmp =~ /^(con_lost|error|no_such_host)/i) { + $fn = 'default_'.$stype; + } else { + $fn = $tmphash{'spreadsheet_default_'.$stype}; + } + unless (($fn) && ($fn!~/^error\:/)) { + $fn='default_'.$stype; + } + $defaultsheets{$cnum.'_'.$cdom.'_'.$stype}=$fn; + } + } + # $fn now has a value + $sheet->{'filename'} = $fn; + # see if sheet is cached + my $fstring=''; + if ($fstring=$spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}) { + my %tmp = split(/___;___/,$fstring); + $sheet->{'f'} = \%tmp; + &setformulas($sheet); + } else { + # Not cached, need to read + my %f=(); + if ($fn=~/^default\_/) { + my $sheetxml=''; + my $fh; + my $dfn=$fn; + $dfn=~s/\_/\./g; + if ($fh=Apache::File->new($includedir.'/'.$dfn)) { + $sheetxml=join('',<$fh>); + } else { + # $sheetxml='"Error"'; + $sheetxml=''; + } + %f=%{&parse_sheet(\$sheetxml)}; + } elsif($fn=~/\/*\.spreadsheet$/) { + my $sheetxml=&Apache::lonnet::getfile + (&Apache::lonnet::filelocation('',$fn)); + if ($sheetxml == -1) { + $sheetxml='"Error loading spreadsheet ' + .$fn.'"'; + } + %f=%{&parse_sheet(\$sheetxml)}; + } else { + my $sheet=''; + my %tmphash = &Apache::lonnet::dump($fn,$cdom,$cnum); + my ($tmp) = keys(%tmphash); + unless ($tmp =~ /^(con_lost|error|no_such_host)/i) { + foreach (keys(%tmphash)) { + $f{$_}=$tmphash{$_}; + } + } + } + # Cache and set + $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f); + $sheet->{'f'}=\%f; + &setformulas($sheet); } } # -------------------------------------------------------- Make new spreadsheet - sub makenewsheet { my ($uname,$udom,$stype,$usymb)=@_; - my $safeeval=initsheet($stype); - $safeeval->reval( - '$uname="'.$uname. - '";$udom="'.$udom. - '";$uhome="'.&Apache::lonnet::homeserver($uname,$udom). - '";$sheettype="'.$stype. - '";$usymb="'.$usymb. - '";$csec="'.&Apache::lonnet::usection($udom,$uname, - $ENV{'request.course.id'}). - '";$cid="'.$ENV{'request.course.id'}. - '";$cfn="'.$ENV{'request.course.fn'}. - '";$cnum="'.$ENV{'course.'.$ENV{'request.course.id'}.'.num'}. - '";$cdom="'.$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}. - '";$chome="'.$ENV{'course.'.$ENV{'request.course.id'}.'.home'}.'";'); - return $safeeval; + my $sheet={}; + $sheet->{'uname'} = $uname; + $sheet->{'udom'} = $udom; + $sheet->{'sheettype'} = $stype; + $sheet->{'usymb'} = $usymb; + $sheet->{'mapid'} = $ENV{'form.mapid'}; + $sheet->{'resid'} = $ENV{'form.resid'}; + $sheet->{'cid'} = $ENV{'request.course.id'}; + $sheet->{'csec'} = $Section{$uname.':'.$udom}; + $sheet->{'coursefilename'} = $ENV{'request.course.fn'}; + $sheet->{'cnum'} = $ENV{'course.'.$ENV{'request.course.id'}.'.num'}; + $sheet->{'cdom'} = $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; + $sheet->{'chome'} = $ENV{'course.'.$ENV{'request.course.id'}.'.home'}; + $sheet->{'coursedesc'} = $ENV{'course.'.$ENV{'request.course.id'}. + '.description'}; + $sheet->{'uhome'} = &Apache::lonnet::homeserver($uname,$udom); + # + # + $sheet->{'f'} = {}; + $sheet->{'constants'} = {}; + $sheet->{'othersheets'} = []; + $sheet->{'rowlabel'} = {}; + # + # + $sheet->{'safe'}=&initsheet($sheet->{'sheettype'}); + # + # Place all the %$sheet items into the safe space except the safe space + # itself + my $initstring = ''; + foreach (qw/uname udom sheettype usymb cid csec coursefilename + cnum cdom chome uhome/) { + $initstring.= qq{\$$_="$sheet->{$_}";}; + } + $sheet->{'safe'}->reval($initstring); + return $sheet; } # ------------------------------------------------------------ Save spreadsheet - sub writesheet { - my ($safeeval,$makedef)=@_; - my $cid=&getcid($safeeval); - if (&Apache::lonnet::allowed('opa',$cid)) { - my %f=&getformulas($safeeval); - my $stype=&gettype($safeeval); - my $cnum=&getcnum($safeeval); - my $cdom=&getcdom($safeeval); - my $chome=&getchome($safeeval); - my $fn=&getfilename($safeeval); - -# ------------------------------------------------------------- Cache new sheet - $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f); -# ----------------------------------------------------------------- Write sheet - my $sheetdata=''; - foreach (keys(%f)) { - unless ($f{$_} eq 'import') { - $sheetdata.=&Apache::lonnet::escape($_).'='. - &Apache::lonnet::escape($f{$_}).'&'; - } - } - $sheetdata=~s/\&$//; - my $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'.$fn.':'. - $sheetdata,$chome); - if ($reply eq 'ok') { - $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'. - $stype.'_spreadsheets:'. - &Apache::lonnet::escape($fn).'='.$ENV{'user.name'}.'@'. - $ENV{'user.domain'}, - $chome); - if ($reply eq 'ok') { - if ($makedef) { - return &Apache::lonnet::reply('put:'.$cdom.':'.$cnum. - ':environment:spreadsheet_default_'.$stype.'='. - &Apache::lonnet::escape($fn), - $chome); - } else { - return $reply; - } - } else { - return $reply; - } - } else { - return $reply; - } - } - return 'unauthorized'; + my ($sheet,$makedef)=@_; + my $cid=$sheet->{'cid'}; + if (&Apache::lonnet::allowed('opa',$cid)) { + my %f=&getformulas($sheet); + my $stype= $sheet->{'sheettype'}; + my $cnum = $sheet->{'cnum'}; + my $cdom = $sheet->{'cdom'}; + my $chome= $sheet->{'chome'}; + my $fn = $sheet->{'filename'}; + # Cache new sheet + $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f); + # Write sheet + foreach (keys(%f)) { + delete($f{$_}) if ($f{$_} eq 'import'); + } + my $reply = &Apache::lonnet::put($fn,\%f,$cdom,$cnum); + if ($reply eq 'ok') { + $reply = &Apache::lonnet::put($stype.'_spreadsheets', + {$fn => $ENV{'user.name'}.'@'.$ENV{'user.domain'}}, + $cdom,$cnum); + if ($reply eq 'ok') { + if ($makedef) { + return &Apache::lonnet::put('environment', + {'spreadsheet_default_'.$stype => $fn }, + $cdom,$cnum); + } + return $reply; + } + return $reply; + } + return $reply; + } + return 'unauthorized'; } # ----------------------------------------------- Make a temp copy of the sheet # "Modified workcopy" - interactive only # - sub tmpwrite { - my $safeeval=shift; + my ($sheet) = @_; my $fn=$ENV{'user.name'}.'_'. - $ENV{'user.domain'}.'_spreadsheet_'.&getusymb($safeeval).'_'. - &getfilename($safeeval); + $ENV{'user.domain'}.'_spreadsheet_'.$sheet->{'usymb'}.'_'. + $sheet->{'filename'}; $fn=~s/\W/\_/g; $fn=$tmpdir.$fn.'.tmp'; my $fh; if ($fh=Apache::File->new('>'.$fn)) { - print $fh join("\n",&getformulas($safeeval)); + print $fh join("\n",&getformulas($sheet)); } } # ---------------------------------------------------------- Read the temp copy - sub tmpread { - my ($safeeval,$nfield,$nform)=@_; + my ($sheet,$nfield,$nform)=@_; my $fn=$ENV{'user.name'}.'_'. - $ENV{'user.domain'}.'_spreadsheet_'.&getusymb($safeeval).'_'. - &getfilename($safeeval); + $ENV{'user.domain'}.'_spreadsheet_'.$sheet->{'usymb'}.'_'. + $sheet->{'filename'}; $fn=~s/\W/\_/g; $fn=$tmpdir.$fn.'.tmp'; my $fh; my %fo=(); + my $countrows=0; if ($fh=Apache::File->new($fn)) { my $name; while ($name=<$fh>) { @@ -1497,415 +1945,471 @@ sub tmpread { my $value=<$fh>; chomp($value); $fo{$name}=$value; + if ($name=~/^A(\d+)$/) { + if ($1>$countrows) { + $countrows=$1; + } + } } } 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') { $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 { 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); +=pod - unless ($symb) { return ''; } - my $result=''; - - my ($mapname,$id,$fn)=split(/\_\_\_/,$symb); -# ----------------------------------------------------- Cascading lookup scheme - my $rwhat=$what; - $what=~s/^parameter\_//; - $what=~s/\_([^\_]+)$/\.$1/; +=item &parmval() - my $symbparm=$symb.'.'.$what; - my $mapparm=$mapname.'___(all).'.$what; - my $usercourseprefix=$uname.'_'.$udom.'_'.$cid; +Determine the value of a parameter. - my $seclevel= - $usercourseprefix.'.['. - $csec.'].'.$what; - my $seclevelr= - $usercourseprefix.'.['. - $csec.'].'.$symbparm; - my $seclevelm= - $usercourseprefix.'.['. - $csec.'].'.$mapparm; +Inputs: $what, the parameter needed, $sheet, the safe space - my $courselevel= - $usercourseprefix.'.'.$what; - my $courselevelr= - $usercourseprefix.'.'.$symbparm; - my $courselevelm= - $usercourseprefix.'.'.$mapparm; +Returns: The value of a parameter, or '' if none. -# ---------------------------------------------------------- fourth, check user - - if ($uname) { +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 ($useropt{$courselevelr}) { return $useropt{$courselevelr}; } - - if ($useropt{$courselevelm}) { return $useropt{$courselevelm}; } - - if ($useropt{$courselevel}) { return $useropt{$courselevel}; } - - } - -# --------------------------------------------------------- third, check course - - if ($csec) { - - if ($courseopt{$seclevelr}) { return $courseopt{$seclevelr}; } - - if ($courseopt{$seclevelm}) { return $courseopt{$seclevelm}; } - - if ($courseopt{$seclevel}) { return $courseopt{$seclevel}; } - - } - - if ($courseopt{$courselevelr}) { return $courseopt{$courselevelr}; } - - if ($courseopt{$courselevelm}) { return $courseopt{$courselevelm}; } - - if ($courseopt{$courselevel}) { return $courseopt{$courselevel}; } - -# ----------------------------------------------------- second, check map parms - - my $thisparm=$parmhash{$symbparm}; - if ($thisparm) { return $thisparm; } - -# -------------------------------------------------------- first, check default +=cut - return &Apache::lonnet::metadata($fn,$rwhat.'.default'); - +################################################## +################################################## +sub parmval { + my ($what,$sheet)=@_; + my $symb = $sheet->{'usymb'}; + unless ($symb) { return ''; } + # + my $cid = $sheet->{'cid'}; + my $csec = $sheet->{'csec'}; + my $uname = $sheet->{'uname'}; + my $udom = $sheet->{'udom'}; + my $result=''; + # + my ($mapname,$id,$fn)=split(/\_\_\_/,$symb); + # Cascading lookup scheme + my $rwhat=$what; + $what =~ s/^parameter\_//; + $what =~ s/\_([^\_]+)$/\.$1/; + # + my $symbparm = $symb.'.'.$what; + my $mapparm = $mapname.'___(all).'.$what; + my $usercourseprefix = $uname.'_'.$udom.'_'.$cid; + # + my $seclevel = $usercourseprefix.'.['.$csec.'].'.$what; + my $seclevelr = $usercourseprefix.'.['.$csec.'].'.$symbparm; + my $seclevelm = $usercourseprefix.'.['.$csec.'].'.$mapparm; + # + my $courselevel = $usercourseprefix.'.'.$what; + my $courselevelr = $usercourseprefix.'.'.$symbparm; + my $courselevelm = $usercourseprefix.'.'.$mapparm; + # fourth, check user + if (defined($uname)) { + return $useropt{$courselevelr} if (defined($useropt{$courselevelr})); + return $useropt{$courselevelm} if (defined($useropt{$courselevelm})); + return $useropt{$courselevel} if (defined($useropt{$courselevel})); + } + # third, check course + if (defined($csec)) { + return $courseopt{$seclevelr} if (defined($courseopt{$seclevelr})); + return $courseopt{$seclevelm} if (defined($courseopt{$seclevelm})); + return $courseopt{$seclevel} if (defined($courseopt{$seclevel})); + } + # + return $courseopt{$courselevelr} if (defined($courseopt{$courselevelr})); + return $courseopt{$courselevelm} if (defined($courseopt{$courselevelm})); + return $courseopt{$courselevel} if (defined($courseopt{$courselevel})); + # second, check map parms + my $thisparm = $parmhash{$symbparm}; + return $thisparm if (defined($thisparm)); + # first, check default + return &Apache::lonnet::metadata($fn,$rwhat.'.default'); +} + + +################################################################## +## Row label formatting routines ## +################################################################## +sub format_html_rowlabel { + my $rowlabel = shift; + return '' if ($rowlabel eq ''); + my ($type,$labeldata) = split(':',$rowlabel,2); + my $result = ''; + if ($type eq 'symb') { + my ($symb,$uname,$udom,$mapid,$resid,$title) = split(':',$labeldata); + $symb = &Apache::lonnet::unescape($symb); + $result = ''.$title.''; + } elsif ($type eq 'student') { + my ($sname,$sdom,$fullname,$section,$id) = split(':',$labeldata); + $result =''; + $result.=$section.' '.$id." ".$fullname.''; + } elsif ($type eq 'parameter') { + $result = $labeldata; + } else { + $result = ''.$rowlabel.''; + } + return $result; +} + +sub format_csv_rowlabel { + my $rowlabel = shift; + return '' if ($rowlabel eq ''); + my ($type,$labeldata) = split(':',$rowlabel,2); + my $result = ''; + if ($type eq 'symb') { + my ($symb,$uname,$udom,$mapid,$resid,$title) = split(':',$labeldata); + $symb = &Apache::lonnet::unescape($symb); + $result = $title; + } elsif ($type eq 'student') { + my ($sname,$sdom,$fullname,$section,$id) = split(':',$labeldata); + $result = join('","',($sname,$sdom,$fullname,$section,$id)); + } elsif ($type eq 'parameter') { + $labeldata =~ s/
/ /g; + $result = $labeldata; + } else { + $result = $rowlabel; + } + return '"'.$result.'"'; +} + +sub format_excel_rowlabel { + my $rowlabel = shift; + return '' if ($rowlabel eq ''); + my ($type,$labeldata) = split(':',$rowlabel,2); + my $result = ''; + if ($type eq 'symb') { + my ($symb,$uname,$udom,$mapid,$resid,$title) = split(':',$labeldata); + $symb = &Apache::lonnet::unescape($symb); + $result = $title; + } elsif ($type eq 'student') { + my ($sname,$sdom,$fullname,$section,$id) = split(':',$labeldata); + $section = '' if (! defined($section)); + $id = '' if (! defined($id)); + my @Data = ($sname,$sdom,$fullname,$section,$id); + $result = \@Data; + } elsif ($type eq 'parameter') { + $labeldata =~ s/
/ /g; + $result = $labeldata; + } else { + $result = $rowlabel; + } + return $result; } # ---------------------------------------------- Update rows for course listing - sub updateclasssheet { - my $safeeval=shift; - my $cnum=&getcnum($safeeval); - my $cdom=&getcdom($safeeval); - my $cid=&getcid($safeeval); - my $chome=&getchome($safeeval); - -# ---------------------------------------------- Read class list and row labels - - my $classlst=&Apache::lonnet::reply - ('dump:'.$cdom.':'.$cnum.':classlist',$chome); + my ($sheet) = @_; + my $cnum =$sheet->{'cnum'}; + my $cdom =$sheet->{'cdom'}; + my $cid =$sheet->{'cid'}; + my $chome =$sheet->{'chome'}; + # + %Section = (); + # + # Read class list and row labels + my $classlist = &Apache::loncoursedata::get_classlist(); + if (! defined($classlist)) { + return 'Could not access course classlist'; + } + # my %currentlist=(); - my $now=time; - unless ($classlst=~/^error\:/) { - foreach (split(/\&/,$classlst)) { - my ($name,$value)=split(/\=/,$_); - my ($end,$start)=split(/\:/,&Apache::lonnet::unescape($value)); - my $active=1; - if (($end) && ($now>$end)) { $active=0; } - if ($active) { - my $rowlabel=''; - $name=&Apache::lonnet::unescape($name); - my ($sname,$sdom)=split(/\:/,$name); - my $ssec=&Apache::lonnet::usection($sdom,$sname,$cid); - if ($ssec==-1) { - unless ($ENV{'form.showcsv'}) { - $rowlabel='Data not available: '.$name. - ''; - } else { - $rowlabel='ERROR","'.$name. - '","Data not available","","","'; - } - } else { - my %reply=&Apache::lonnet::idrget($sdom,$sname); - my $reply=&Apache::lonnet::reply('get:'.$sdom.':'.$sname. - ':environment:firstname&middlename&lastname&generation', - &Apache::lonnet::homeserver($sname,$sdom)); - unless ($ENV{'form.showcsv'}) { - $rowlabel=''. - $ssec.' '.$reply{$sname}.'
'; - foreach ( split(/\&/,$reply)) { - $rowlabel.=&Apache::lonnet::unescape($_).' '; - } - $rowlabel.='
'; - } else { - $rowlabel=$ssec.'","'.$reply{$sname}.'"'; - my $ncount=0; - foreach (split(/\&/,$reply)) { - $rowlabel.=',"'.&Apache::lonnet::unescape($_).'"'; - $ncount++; - } - unless ($ncount==4) { $rowlabel.=',""'; } - $rowlabel=~s/\"$//; - } - } - $currentlist{&Apache::lonnet::unescape($name)}=$rowlabel; + foreach my $student (keys(%$classlist)) { + my ($studentDomain,$studentName,$end,$start,$id,$studentSection, + $fullname,$status) = @{$classlist->{$student}}; + $Section{$studentName.':'.$studentDomain} = $studentSection; + if ($ENV{'form.Status'} eq $status || $ENV{'form.Status'} eq 'Any') { + $currentlist{$student}=join(':',('student',$studentName, + $studentDomain,$fullname, + $studentSection,$id)); + } + } + # + # Find discrepancies between the course row table and this + # + my %f=&getformulas($sheet); + my $changed=0; + # + $sheet->{'maxrow'}=0; + my %existing=(); + # + # Now obsolete rows + foreach (keys(%f)) { + if ($_=~/^A(\d+)/) { + if ($1 > $sheet->{'maxrow'}) { + $sheet->{'maxrow'}= $1; } - } # end of foreach (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 - foreach (keys(%f)) { - if ($_=~/^A(\d+)/) { - $maxrow=($1>$maxrow)?$1:$maxrow; - $existing{$f{$_}}=1; - unless ((defined($currentlist{$f{$_}})) || (!$1)) { - $f{$_}='!!! Obsolete'; - $changed=1; - } + $existing{$f{$_}}=1; + unless ((defined($currentlist{$f{$_}})) || (!$1) || + ($f{$_}=~/^(~~~|---)/)) { + $f{$_}='!!! Obsolete'; + $changed=1; } } - -# -------------------------------------------------------- New and unknown keys - - foreach (sort keys(%currentlist)) { - unless ($existing{$_}) { - $changed=1; - $maxrow++; - $f{'A'.$maxrow}=$_; - } + } + # + # New and unknown keys + foreach my $student (sort keys(%currentlist)) { + unless ($existing{$student}) { + $changed=1; + $sheet->{'maxrow'}++; + $f{'A'.$sheet->{'maxrow'}}=$student; } - - if ($changed) { &setformulas($safeeval,%f); } - - &setmaxrow($safeeval,$maxrow); - &setrowlabels($safeeval,%currentlist); - - } else { - return 'Could not access course data'; } + if ($changed) { + $sheet->{'f'} = \%f; + &setformulas($sheet,%f); + } + # + &setrowlabels($sheet,\%currentlist); } # ----------------------------------- Update rows for student and assess sheets - -sub updatestudentassesssheet { - my $safeeval=shift; - my %bighash; - my $stype=&gettype($safeeval); - my %current=(); - unless ($updatedata{$ENV{'request.course.fn'}.'_'.$stype}) { -# -------------------------------------------------------------------- Tie hash - if (tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db', - &GDBM_READER,0640)) { -# --------------------------------------------------------- Get all assessments - - my %allkeys=('timestamp' => - 'Timestamp of Last Transaction
timestamp', - 'subnumber' => - 'Number of Submissions
subnumber', - 'tutornumber' => - 'Number of Tutor Responses
tutornumber', - 'totalpoints' => - 'Total Points Granted
totalpoints'); - - my $adduserstr=''; - if ((&getuname($safeeval) ne $ENV{'user.name'}) || - (&getudom($safeeval) ne $ENV{'user.domain'})) { - $adduserstr='&uname='.&getuname($safeeval). - '&udom='.&getudom($safeeval); - } - - my %allassess=('_feedback' => - 'Feedback', - '_evaluation' => - 'Evaluation', - '_tutoring' => - 'Tutoring', - '_discussion' => - 'Discussion' - ); - - foreach (keys(%bighash)) { - if ($_=~/^src\_(\d+)\.(\d+)$/) { - my $mapid=$1; - my $resid=$2; - my $id=$mapid.'.'.$resid; - my $srcf=$bighash{$_}; - if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) { - my $symb= - &Apache::lonnet::declutter($bighash{'map_id_'.$mapid}). - '___'.$resid.'___'. - &Apache::lonnet::declutter($srcf); - $allassess{$symb}= - ''. - $bighash{'title_'.$id}.''; - if ($stype eq 'assesscalc') { - foreach (split(/\,/, - &Apache::lonnet::metadata($srcf,'keys'))) { - if (($_=~/^stores\_(.*)/) || ($_=~/^parameter\_(.*)/)) { - my $key=$_; - my $display= - &Apache::lonnet::metadata($srcf,$key.'.display'); - unless ($display) { - $display.= - &Apache::lonnet::metadata($srcf,$key.'.name'); - } - $display.='
'.$key; - $allkeys{$key}=$display; - } - } # end of foreach - } - } - } - } # end of foreach (keys(%bighash)) - untie(%bighash); - -# -# %allkeys has a list of storage and parameter displays by unikey -# %allassess has a list of all resource displays by symb -# - - if ($stype eq 'assesscalc') { - %current=%allkeys; - } elsif ($stype eq 'studentcalc') { - %current=%allassess; +sub get_student_rowlabels { + my ($sheet) = @_; + # + my %course_db; + # + my $stype = $sheet->{'sheettype'}; + my $uname = $sheet->{'uname'}; + my $udom = $sheet->{'udom'}; + # + $sheet->{'rowlabel'} = {}; + # + my $identifier =$sheet->{'coursefilename'}.'_'.$stype; + if ($rowlabel_cache{$identifier}) { + %{$sheet->{'rowlabel'}}=split(/___;___/,$rowlabel_cache{$identifier}); + } else { + # Get the data and store it in the cache + # Tie hash + tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db', + &GDBM_READER(),0640); + if (! tied(%course_db)) { + return 'Could not access course data'; + } + # + my %assesslist; + foreach ('Feedback','Evaluation','Tutoring','Discussion') { + my $symb = '_'.lc($_); + $assesslist{$symb} = join(':',('symb',$symb,$uname,$udom,0,0,$_)); + } + # + while (my ($key,$srcf) = each(%course_db)) { + next if ($key !~ /^src_(\d+)\.(\d+)$/); + my $mapid = $1; + my $resid = $2; + my $id = $mapid.'.'.$resid; + if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) { + my $symb= + &Apache::lonnet::declutter($course_db{'map_id_'.$mapid}). + '___'.$resid.'___'.&Apache::lonnet::declutter($srcf); + $assesslist{$symb}='symb:'.&Apache::lonnet::escape($symb).':' + .$uname.':'.$udom.':'.$mapid.':'.$resid.':'. + $course_db{'title_'.$id}; + } } - $updatedata{$ENV{'request.course.fn'}.'_'.$stype}= - join('___;___',%current); - } else { - return 'Could not access course data'; - } -# ------------------------------------------------------ Get current from cache - } else { - %current=split(/\_\_\_\;\_\_\_/, - $updatedata{$ENV{'request.course.fn'}.'_'.$stype}); + untie(%course_db); + # Store away the data + $sheet->{'rowlabel'} = \%assesslist; + $rowlabel_cache{$identifier}=join('___;___',%{$sheet->{'rowlabel'}}); } -# -------------------- Find discrepancies between the course row table and this -# - my %f=&getformulas($safeeval); - my $changed=0; - my $maxrow=0; - my %existing=(); +} -# ----------------------------------------------------------- Now obsolete rows - foreach (keys(%f)) { - if ($_=~/^A(\d+)/) { - $maxrow=($1>$maxrow)?$1:$maxrow; - my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{$_}); - $existing{$usy}=1; - unless ((defined($current{$usy})) || (!$1)) { - $f{$_}='!!! Obsolete'; - $changed=1; - } elsif ($ufn) { - $current{$usy} - =~s/assesscalc\?usymb\=/assesscalc\?ufn\=$ufn\&usymb\=/; - } +sub get_assess_rowlabels { + my ($sheet) = @_; + # + my %course_db; + # + my $stype = $sheet->{'sheettype'}; + my $uname = $sheet->{'uname'}; + my $udom = $sheet->{'udom'}; + my $usymb = $sheet->{'usymb'}; + # + $sheet->{'rowlabel'} = {}; + my $identifier =$sheet->{'coursefilename'}.'_'.$stype.'_'.$usymb; + # + if ($rowlabel_cache{$identifier}) { + %{$sheet->{'rowlabel'}}=split(/___;___/,$rowlabel_cache{$identifier}); + } else { + # Get the data and store it in the cache + # Tie hash + tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db', + &GDBM_READER(),0640); + if (! tied(%course_db)) { + return 'Could not access course data'; + } + # + my %parameter_labels= + ('timestamp' => + 'parameter:Timestamp of Last Transaction
timestamp', + 'subnumber' => + 'parameter:Number of Submissions
subnumber', + 'tutornumber' => + 'parameter:Number of Tutor Responses
tutornumber', + 'totalpoints' => + 'parameter:Total Points Granted
totalpoints'); + while (my ($key,$srcf) = each(%course_db)) { + next if ($key !~ /^src_(\d+)\.(\d+)$/); + my $mapid = $1; + my $resid = $2; + my $id = $mapid.'.'.$resid; + if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) { + # Loop through the metadata for this key + my @Metadata = split(/,/, + &Apache::lonnet::metadata($srcf,'keys')); + foreach my $key (@Metadata) { + next if ($key !~ /^(stores|parameter)_/); + my $display= + &Apache::lonnet::metadata($srcf,$key.'.display'); + unless ($display) { + $display.= + &Apache::lonnet::metadata($srcf,$key.'.name'); + } + $display.='
'.$key; + $parameter_labels{$key}='parameter:'.$display; + } # end of foreach } } + untie(%course_db); + # Store away the results + $sheet->{'rowlabel'} = \%parameter_labels; + $rowlabel_cache{$identifier}=join('___;___',%{$sheet->{'rowlabel'}}); + } + +} -# -------------------------------------------------------- New and unknown keys - - foreach (keys(%current)) { - unless ($existing{$_}) { - $changed=1; - $maxrow++; - $f{'A'.$maxrow}=$_; - } - } +sub updatestudentassesssheet { + my $sheet = shift; + if ($sheet->{'sheettype'} eq 'studentcalc') { + &get_student_rowlabels($sheet); + } else { + &get_assess_rowlabels($sheet); + } + # Determine if any of the information has changed + my %f=&getformulas($sheet); + my $changed=0; - if ($changed) { &setformulas($safeeval,%f); } - - &setmaxrow($safeeval,$maxrow); - &setrowlabels($safeeval,%current); - - undef %current; - undef %existing; + $sheet->{'maxrow'} = 0; + my %existing=(); + # Now obsolete rows + foreach my $cell (keys(%f)) { + my $formula = $f{$cell}; + next if ($cell !~ /^A(\d+)/); + $sheet->{'maxrow'} = $1 if ($1 > $sheet->{'maxrow'}); + my ($usy,$ufn)=split(/__&&&\__/,$formula); + $existing{$usy}=1; + unless ((exists($sheet->{'rowlabel'}->{$usy}) && + (defined($sheet->{'rowlabel'}->{$usy})) || (!$1) || + ($formula =~ /^(~~~|---)/) )) { + $f{$_}='!!! Obsolete'; + $changed=1; + } elsif ($ufn) { + # I do not think this works any more + $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); + } } # ------------------------------------------------ Load data for one assessment -sub loadstudent { - my $safeeval=shift; - my %c=(); - my %f=&getformulas($safeeval); - $cachedassess=&getuname($safeeval).':'.&getudom($safeeval); - %cachedstores=(); - { - my $reply=&Apache::lonnet::reply('dump:'.&getudom($safeeval).':'. - &getuname($safeeval).':'. - &getcid($safeeval), - &getuhome($safeeval)); - unless ($reply=~/^error\:/) { - foreach ( split(/\&/,$reply)) { - my ($name,$value)=split(/\=/,$_); - $cachedstores{&Apache::lonnet::unescape($name)}= - &Apache::lonnet::unescape($value); - } - } +sub loadstudent{ + my ($sheet,$r,$c)=@_; + my %constants=(); + my %formulas=&getformulas($sheet); + $cachedassess=$sheet->{'uname'}.':'.$sheet->{'udom'}; + # Get ALL the student preformance data + my @tmp = &Apache::lonnet::dump($sheet->{'cid'}, + $sheet->{'udom'}, + $sheet->{'uname'}, + undef); + if ($tmp[0] !~ /^error:/) { + %cachedstores = @tmp; } + undef @tmp; + # my @assessdata=(); - foreach (keys(%f)) { - if ($_=~/^A(\d+)/) { - my $row=$1; - unless (($f{$_}=~/^\!/) || ($row==0)) { - my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{$_}); - @assessdata=&exportsheet(&getuname($safeeval), - &getudom($safeeval), - 'assesscalc',$usy,$ufn); - my $index=0; - foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', - 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') { - if ($assessdata[$index]) { - my $col=$_; - if ($assessdata[$index]=~/\D/) { - $c{$col.$row}="'".$assessdata[$index]."'"; - } else { - $c{$col.$row}=$assessdata[$index]; - } - unless ($col eq 'A') { - $f{$col.$row}='import'; - } - } - $index++; - } - } + foreach my $cell (keys(%formulas)) { + my $value = $formulas{$cell}; + if(defined($c) && ($c->aborted())) { + last; + } + next if ($cell !~ /^A(\d+)/); + my $row=$1; + next if (($value =~ /^[!~-]/) || ($row==0)); + my ($usy,$ufn)=split(/__&&&\__/,$value); + @assessdata=&exportsheet($sheet,$sheet->{'uname'}, + $sheet->{'udom'}, + 'assesscalc',$usy,$ufn,$r); + my $index=0; + foreach my $col ('A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') { + if (defined($assessdata[$index])) { + if ($assessdata[$index]=~/\D/) { + $constants{$col.$row}="'".$assessdata[$index]."'"; + } else { + $constants{$col.$row}=$assessdata[$index]; + } + $formulas{$col.$row}='import' if ($col ne 'A'); + } + $index++; } } $cachedassess=''; undef %cachedstores; - &setformulas($safeeval,%f); - &setconstants($safeeval,%c); + $sheet->{'f'} = \%formulas; + &setformulas($sheet); + &setconstants($sheet,\%constants); } # --------------------------------------------------- Load data for one student - +# sub loadcourse { - my ($safeeval,$r)=@_; - my %c=(); - my %f=&getformulas($safeeval); + my ($sheet,$r,$c)=@_; + # + my %constants=(); + my %formulas=&getformulas($sheet); + # my $total=0; - foreach (keys(%f)) { + foreach (keys(%formulas)) { if ($_=~/^A(\d+)/) { - unless ($f{$_}=~/^\!/) { $total++; } + unless ($formulas{$_}=~/^[\!\~\-]/) { $total++; } } } my $now=0; @@ -1922,194 +2426,167 @@ sub loadcourse { ENDPOP $r->rflush(); - foreach (keys(%f)) { - if ($_=~/^A(\d+)/) { - my $row=$1; - unless (($f{$_}=~/^\!/) || ($row==0)) { - my @studentdata=&exportsheet(split(/\:/,$f{$_}), - 'studentcalc'); - undef %userrdatas; - $now++; - $r->print(''); - $r->rflush(); - - my $index=0; - foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', - 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') { - if ($studentdata[$index]) { - my $col=$_; - if ($studentdata[$index]=~/\D/) { - $c{$col.$row}="'".$studentdata[$index]."'"; - } else { - $c{$col.$row}=$studentdata[$index]; - } - unless ($col eq 'A') { - $f{$col.$row}='import'; - } - } - $index++; - } - } + ' secs remaining";'); + $r->rflush(); + # + my $index=0; + foreach ('A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z') { + if (defined($studentdata[$index])) { + my $col=$_; + if ($studentdata[$index]=~/\D/) { + $constants{$col.$row}="'".$studentdata[$index]."'"; + } else { + $constants{$col.$row}=$studentdata[$index]; + } + unless ($col eq 'A') { + $formulas{$col.$row}='import'; + } + } + $index++; } } - &setformulas($safeeval,%f); - &setconstants($safeeval,%c); + $sheet->{'f'}=\%formulas; + &setformulas($sheet); + &setconstants($sheet,\%constants); $r->print(''); $r->rflush(); } # ------------------------------------------------ Load data for one assessment - +# sub loadassessment { - my $safeeval=shift; + my ($sheet,$r,$c)=@_; - my $uhome=&getuhome($safeeval); - my $uname=&getuname($safeeval); - my $udom=&getudom($safeeval); - my $symb=&getusymb($safeeval); - my $cid=&getcid($safeeval); - my $cnum=&getcnum($safeeval); - my $cdom=&getcdom($safeeval); - my $chome=&getchome($safeeval); + my $uhome = $sheet->{'uhome'}; + my $uname = $sheet->{'uname'}; + my $udom = $sheet->{'udom'}; + my $symb = $sheet->{'usymb'}; + my $cid = $sheet->{'cid'}; + my $cnum = $sheet->{'cnum'}; + my $cdom = $sheet->{'cdom'}; + my $chome = $sheet->{'chome'}; my $namespace; unless ($namespace=$cid) { return ''; } - -# ----------------------------------------------------------- Get stored values - - my %returnhash=(); - - if ($cachedassess eq $uname.':'.$udom) { -# -# get data out of the dumped stores -# - - my $version=$cachedstores{'version:'.$symb}; - my $scope; - for ($scope=1;$scope<=$version;$scope++) { - foreach (split(/\:/,$cachedstores{$scope.':keys:'.$symb})) { - $returnhash{$_}=$cachedstores{$scope.':'.$symb.':'.$_}; - } - } - - } else { -# -# restore individual -# - - my $answer=&Apache::lonnet::reply( - "restore:$udom:$uname:". - &Apache::lonnet::escape($namespace).":". - &Apache::lonnet::escape($symb),$uhome); - foreach (split(/\&/,$answer)) { - my ($name,$value)=split(/\=/,$_); - $returnhash{&Apache::lonnet::unescape($name)}= - &Apache::lonnet::unescape($value); - } - my $version; - for ($version=1;$version<=$returnhash{'version'};$version++) { - foreach (split(/\:/,$returnhash{$version.':keys'})) { - $returnhash{$_}=$returnhash{$version.':'.$_}; - } + # Get stored values + my %returnhash=(); + if ($cachedassess eq $uname.':'.$udom) { + # + # get data out of the dumped stores + # + my $version=$cachedstores{'version:'.$symb}; + my $scope; + for ($scope=1;$scope<=$version;$scope++) { + foreach (split(/\:/,$cachedstores{$scope.':keys:'.$symb})) { + $returnhash{$_}=$cachedstores{$scope.':'.$symb.':'.$_}; + } + } + } else { + # + # restore individual + # + %returnhash = &Apache::lonnet::restore($symb,$namespace,$udom,$uname); + for (my $version=1;$version<=$returnhash{'version'};$version++) { + foreach (split(/\:/,$returnhash{$version.':keys'})) { + $returnhash{$_}=$returnhash{$version.':'.$_}; + } + } } - } -# ----------------------------- returnhash now has all stores for this resource - -# --------- convert all "_" to "." to be able to use libraries, multiparts, etc - + # + # returnhash now has all stores for this resource + # convert all "_" to "." to be able to use libraries, multiparts, etc + # + # This is dumb. It is also necessary :( my @oldkeys=keys %returnhash; - - foreach (@oldkeys) { - my $name=$_; - my $value=$returnhash{$_}; - delete $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 + # initialize coursedata and userdata for this user undef %courseopt; undef %useropt; my $userprefix=$uname.'_'.$udom.'_'; unless ($uhome eq 'no_host') { -# -------------------------------------------------------------- Get coursedata - unless - ((time-$courserdatas{$cid.'.last_cache'})<240) { - my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum. - ':resourcedata',$chome); - if ($reply!~/^error\:/) { - $courserdatas{$cid}=$reply; + # Get coursedata + unless ((time-$courserdatas{$cid.'.last_cache'})<240) { + my %Tmp = &Apache::lonnet::dump('resourcedata',$cdom,$cnum); + $courserdatas{$cid}=\%Tmp; $courserdatas{$cid.'.last_cache'}=time; - } - } - foreach (split(/\&/,$courserdatas{$cid})) { - my ($name,$value)=split(/\=/,$_); - $courseopt{$userprefix.&Apache::lonnet::unescape($name)}= - &Apache::lonnet::unescape($value); - } -# --------------------------------------------------- Get userdata (if present) - unless - ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) { - my $reply= - &Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome); - if ($reply!~/^error\:/) { - $userrdatas{$uname.'___'.$udom}=$reply; - $userrdatas{$uname.'___'.$udom.'.last_cache'}=time; - } - } - foreach (split(/\&/,$userrdatas{$uname.'___'.$udom})) { - my ($name,$value)=split(/\=/,$_); - $useropt{$userprefix.&Apache::lonnet::unescape($name)}= - &Apache::lonnet::unescape($value); - } + } + while (my ($name,$value) = each(%{$courserdatas{$cid}})) { + $courseopt{$userprefix.$name}=$value; + } + # Get userdata (if present) + unless ((time-$userrdatas{$uname.'@'.$udom.'.last_cache'})<240) { + my %Tmp = &Apache::lonnet::dump('resourcedata',$udom,$uname); + $userrdatas{$cid} = \%Tmp; + # Most of the time the user does not have a 'resourcedata.db' + # file. We need to cache that we got nothing instead of bothering + # with requesting it every time. + $userrdatas{$uname.'@'.$udom.'.last_cache'}=time; + } + while (my ($name,$value) = each(%{$userrdatas{$cid}})) { + $useropt{$userprefix.$name}=$value; + } } -# ----------------- now courseopt, useropt initialized for this user and course -# (used by parmval) - -# -# Load keys for this assessment only -# + # now courseopt, useropt initialized for this user and course + # (used by parmval) + # + # Load keys for this assessment only + # my %thisassess=(); my ($symap,$syid,$srcf)=split(/\_\_\_/,$symb); - foreach (split(/\,/,&Apache::lonnet::metadata($srcf,'keys'))) { $thisassess{$_}=1; } -# -# Load parameters -# - my %c=(); - - if (tie(%parmhash,'GDBM_File', - &getcfn($safeeval).'_parms.db',&GDBM_READER,0640)) { - my %f=&getformulas($safeeval); - foreach (keys(%f)) { - if ($_=~/^A/) { - unless ($f{$_}=~/^\!/) { - if ($f{$_}=~/^parameter/) { - if ($thisassess{$f{$_}}) { - my $val=&parmval($f{$_},$safeeval); - $c{$_}=$val; - $c{$f{$_}}=$val; - } - } else { - my $key=$f{$_}; - my $ckey=$key; - $key=~s/^stores\_/resource\./; - $key=~s/\_/\./g; - $c{$_}=$returnhash{$key}; - $c{$ckey}=$returnhash{$key}; - } - } + # + # Load parameters + # + my %c=(); + if (tie(%parmhash,'GDBM_File', + $sheet->{'coursefilename'}.'_parms.db',&GDBM_READER(),0640)) { + my %f=&getformulas($sheet); + foreach my $cell (keys(%f)) { + next if ($cell !~ /^A/); + next if ($f{$cell} =~/^[\!\~\-]/); + if ($f{$cell}=~/^parameter/) { + if (defined($thisassess{$f{$cell}})) { + my $val = &parmval($f{$cell},$sheet); + $c{$cell} = $val; + $c{$f{$cell}} = $val; + } + } else { + my $key=$f{$cell}; + my $ckey=$key; + $key=~s/^stores\_/resource\./; + $key=~s/\_/\./g; + $c{$cell}=$returnhash{$key}; + $c{$ckey}=$returnhash{$key}; + } } + untie(%parmhash); } - untie(%parmhash); - } - &setconstants($safeeval,%c); + &setconstants($sheet,\%c); } # --------------------------------------------------------- Various form fields @@ -2117,7 +2594,7 @@ sub loadassessment { sub textfield { my ($title,$name,$value)=@_; return "\n

$title:
". - ''; + ''; } sub hiddenfield { @@ -2142,12 +2619,11 @@ sub selectbox { # sub updatesheet { - my $safeeval=shift; - my $stype=&gettype($safeeval); - if ($stype eq 'classcalc') { - return &updateclasssheet($safeeval); + my ($sheet)=@_; + if ($sheet->{'sheettype'} eq 'classcalc') { + return &updateclasssheet($sheet); } else { - return &updatestudentassesssheet($safeeval); + return &updatestudentassesssheet($sheet); } } @@ -2157,14 +2633,15 @@ sub updatesheet { # sub loadrows { - my ($safeeval,$r)=@_; - my $stype=&gettype($safeeval); + my ($sheet,$r)=@_; + my $c = $r->connection; + my $stype=$sheet->{'sheettype'}; if ($stype eq 'classcalc') { - &loadcourse($safeeval,$r); + &loadcourse($sheet,$r,$c); } elsif ($stype eq 'studentcalc') { - &loadstudent($safeeval); + &loadstudent($sheet,$r,$c); } else { - &loadassessment($safeeval); + &loadassessment($sheet,$r,$c); } } @@ -2172,8 +2649,13 @@ sub loadrows { sub checkthis { my ($keyname,$time)=@_; - return ($time<$expiredates{$keyname}); + if (! exists($expiredates{$keyname})) { + return 0; + } else { + return ($time<$expiredates{$keyname}); + } } + sub forcedrecalc { my ($uname,$udom,$stype,$usymb)=@_; my $key=$uname.':'.$udom.':'.$stype.':'.$usymb; @@ -2181,7 +2663,7 @@ sub forcedrecalc { if ($ENV{'form.forcerecalc'}) { return 1; } unless ($time) { return 1; } if ($stype eq 'assesscalc') { - my $map=(split(/\_\_\_/,$usymb))[0]; + my $map=(split(/___/,$usymb))[0]; if (&checkthis('::assesscalc:',$time) || &checkthis('::assesscalc:'.$map,$time) || &checkthis('::assesscalc:'.$usymb,$time) || @@ -2200,150 +2682,145 @@ sub forcedrecalc { } # ============================================================== Export handler +# exportsheet +# returns the export row for a spreadsheet. # -# Non-interactive call from with program -# - sub exportsheet { - my ($uname,$udom,$stype,$usymb,$fn)=@_; - my @exportarr=(); - - if (($usymb=~/^\_(\w+)/) && (!$fn)) { - $fn='default_'.$1; - } - -# -# Check if cached -# - - my $key=$uname.':'.$udom.':'.$stype.':'.$usymb; - my $found=''; - - if ($oldsheets{$key}) { - foreach (split(/\_\_\_\&\_\_\_/,$oldsheets{$key})) { - my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_); - if ($name eq $fn) { - $found=$value; - } - } - } - - unless ($found) { - &cachedssheets($uname,$udom,&Apache::lonnet::homeserver($uname,$udom)); - if ($oldsheets{$key}) { - foreach (split(/\_\_\_\&\_\_\_/,$oldsheets{$key})) { - my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_); + my ($sheet,$uname,$udom,$stype,$usymb,$fn,$r)=@_; + my $flag = 0; + $uname = $uname || $sheet->{'uname'}; + $udom = $udom || $sheet->{'udom'}; + $stype = $stype || $sheet->{'sheettype'}; + my @exportarr=(); + if (defined($usymb) && ($usymb=~/^\_(\w+)/) && + (!defined($fn) || $fn eq '')) { + $fn='default_'.$1; + } + # + # Check if cached + # + my $key=$uname.':'.$udom.':'.$stype.':'.$usymb; + my $found=''; + if ($oldsheets{$key}) { + foreach (split(/___&\___/,$oldsheets{$key})) { + my ($name,$value)=split(/___=___/,$_); if ($name eq $fn) { - $found=$value; + $found=$value; } - } - } - } -# -# Check if still valid -# - if ($found) { - if (&forcedrecalc($uname,$udom,$stype,$usymb)) { - $found=''; - } - } - - 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)); - + } + } + unless ($found) { + &cachedssheets($sheet,$uname,$udom); + if ($oldsheets{$key}) { + foreach (split(/___&\___/,$oldsheets{$key})) { + my ($name,$value)=split(/___=___/,$_); + if ($name eq $fn) { + $found=$value; + } + } + } + } + # + # Check if still valid + # + if ($found) { + if (&forcedrecalc($uname,$udom,$stype,$usymb)) { + $found=''; + } + } + if ($found) { + # + # Return what was cached + # + @exportarr=split(/___;___/,$found); + return @exportarr; } + # + # Not cached + # + my ($newsheet)=&makenewsheet($uname,$udom,$stype,$usymb); + &readsheet($newsheet,$fn); + &updatesheet($newsheet); + &loadrows($newsheet,$r); + &calcsheet($newsheet); + @exportarr=&exportdata($newsheet); + ## + ## Store now + ## + # + # load in the old value + # my %currentlystored=(); - unless ($current=~/^error\:/) { - foreach (split(/\_\_\_\&\_\_\_/,&Apache::lonnet::unescape($current))) { - my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_); - $currentlystored{$name}=$value; - } + if ($stype eq 'studentcalc') { + my @tmp = &Apache::lonnet::get('nohist_calculatedsheets', + [$key], + $sheet->{'cdom'},$sheet->{'cnum'}); + if ($tmp[0]!~/^error/) { + # We only got one key, so we will access it directly. + foreach (split('___&___',$tmp[1])) { + my ($key,$value) = split('___=___',$_); + $key = '' if (! defined($key)); + $currentlystored{$key} = $value; + } + } + } else { + my @tmp = &Apache::lonnet::get('nohist_calculatedsheets_'. + $sheet->{'cid'},[$key], + $sheet->{'udom'},$sheet->{'uname'}); + if ($tmp[0]!~/^error/) { + # We only got one key, so we will access it directly. + foreach (split('___&___',$tmp[1])) { + my ($key,$value) = split('___=___',$_); + $key = '' if (! defined($key)); + $currentlystored{$key} = $value; + } + } } + # + # Add the new line + # $currentlystored{$fn}=join('___;___',@exportarr); - + # + # Stick everything back together + # my $newstore=''; foreach (keys(%currentlystored)) { if ($newstore) { $newstore.='___&___'; } $newstore.=$_.'___=___'.$currentlystored{$_}; } my $now=time; + # + # Store away the new value + # + my $timekey = $key.'.time'; if ($stype eq 'studentcalc') { - &Apache::lonnet::reply('put:'. - $ENV{'course.'.$cid.'.domain'}.':'. - $ENV{'course.'.$cid.'.num'}. - ':nohist_calculatedsheets:'. - &Apache::lonnet::escape($key).'='. - &Apache::lonnet::escape($newstore).'&'. - &Apache::lonnet::escape($key).'.time='.$now, - $ENV{'course.'.$cid.'.home'}); - } else { - &Apache::lonnet::reply('put:'. - &getudom($thissheet).':'. - &getuname($thissheet). - ':nohist_calculatedsheets_'. - $ENV{'request.course.id'}.':'. - &Apache::lonnet::escape($key).'='. - &Apache::lonnet::escape($newstore).'&'. - &Apache::lonnet::escape($key).'.time='.$now, - &getuhome($thissheet)); - } - } - return @exportarr; + my $result = &Apache::lonnet::put('nohist_calculatedsheets', + { $key => $newstore, + $timekey => $now }, + $sheet->{'cdom'}, + $sheet->{'cnum'}); + } else { + my $result = &Apache::lonnet::put('nohist_calculatedsheets_'.$sheet->{'cid'}, + { $key => $newstore, + $timekey => $now }, + $sheet->{'udom'}, + $sheet->{'uname'}); + } + return @exportarr; } + # ============================================================ Expiration Dates # # Load previously cached student spreadsheets for this course # - -sub expirationdates { +sub load_spreadsheet_expirationdates { undef %expiredates; my $cid=$ENV{'request.course.id'}; - my $reply=&Apache::lonnet::reply('dump:'. - $ENV{'course.'.$cid.'.domain'}.':'. - $ENV{'course.'.$cid.'.num'}. - ':nohist_expirationdates', - $ENV{'course.'.$cid.'.home'}); - unless ($reply=~/^error\:/) { - foreach (split(/\&/,$reply)) { - my ($name,$value)=split(/\=/,$_); - $expiredates{&Apache::lonnet::unescape($name)} - =&Apache::lonnet::unescape($value); - } + my @tmp = &Apache::lonnet::dump('nohist_expirationdates', + $ENV{'course.'.$cid.'.domain'}, + $ENV{'course.'.$cid.'.num'}); + if (lc($tmp[0]) !~ /^error/){ + %expiredates = @tmp; } } @@ -2354,16 +2831,13 @@ sub expirationdates { sub cachedcsheets { my $cid=$ENV{'request.course.id'}; - my $reply=&Apache::lonnet::reply('dump:'. - $ENV{'course.'.$cid.'.domain'}.':'. - $ENV{'course.'.$cid.'.num'}. - ':nohist_calculatedsheets', - $ENV{'course.'.$cid.'.home'}); - unless ($reply=~/^error\:/) { - foreach ( split(/\&/,$reply)) { - my ($name,$value)=split(/\=/,$_); - $oldsheets{&Apache::lonnet::unescape($name)} - =&Apache::lonnet::unescape($value); + my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets', + $ENV{'course.'.$cid.'.domain'}, + $ENV{'course.'.$cid.'.num'}); + if ($tmp[0] !~ /^error/) { + my %StupidTempHash = @tmp; + while (my ($key,$value) = each %StupidTempHash) { + $oldsheets{$key} = $value; } } } @@ -2374,22 +2848,24 @@ sub cachedcsheets { # sub cachedssheets { - my ($sname,$sdom,$shome)=@_; - unless (($loadedcaches{$sname.'_'.$sdom}) || ($shome eq 'no_host')) { - my $cid=$ENV{'request.course.id'}; - my $reply=&Apache::lonnet::reply('dump:'.$sdom.':'.$sname. - ':nohist_calculatedsheets_'. - $ENV{'request.course.id'}, - $shome); - unless ($reply=~/^error\:/) { - foreach ( split(/\&/,$reply)) { - my ($name,$value)=split(/\=/,$_); - $oldsheets{&Apache::lonnet::unescape($name)} - =&Apache::lonnet::unescape($value); + my ($sheet,$uname,$udom) = @_; + $uname = $uname || $sheet->{'uname'}; + $udom = $udom || $sheet->{'udom'}; + if (! $loadedcaches{$uname.'_'.$udom}) { + my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets', + $sheet->{'udom'}, + $sheet->{'uname'}); + if ($tmp[0] !~ /^error/) { + my %TempHash = @tmp; + my $count = 0; + while (my ($key,$value) = each %TempHash) { + $oldsheets{$key} = $value; + $count++; + } + $loadedcaches{$sheet->{'uname'}.'_'.$sheet->{'udom'}}=1; } } - $loadedcaches{$sname.'_'.$sdom}=1; - } + } # ===================================================== Calculated sheets cache @@ -2402,81 +2878,137 @@ sub cachedssheets { # Interactive call to screen # # - - sub handler { my $r=shift; + my ($sheettype) = ($r->uri=~/\/(\w+)$/); + + if (! exists($ENV{'form.Status'})) { + $ENV{'form.Status'} = 'Active'; + } + if ( ! exists($ENV{'form.output'}) || + ($sheettype ne 'classcalc' && + lc($ENV{'form.output'}) eq 'recursive excel')) { + $ENV{'form.output'} = 'HTML'; + } + # + # Overload checking + # + # Check this server + my $loaderror=&Apache::lonnet::overloaderror($r); + if ($loaderror) { return $loaderror; } + # Check the course homeserver + $loaderror= &Apache::lonnet::overloaderror($r, + $ENV{'course.'.$ENV{'request.course.id'}.'.home'}); + if ($loaderror) { return $loaderror; } + # + # HTML Header + # if ($r->header_only) { - $r->content_type('text/html'); - $r->send_http_header; - return OK; + $r->content_type('text/html'); + $r->send_http_header; + return OK; } - -# ---------------------------------------------------- Global directory configs - -$includedir=$r->dir_config('lonIncludes'); -$tmpdir=$r->dir_config('lonDaemons').'/tmp/'; - -# ----------------------------------------------------- Needs to be in a course - - if ($ENV{'request.course.fn'}) { - -# --------------------------- Get query string for limited number of parameters - - foreach (split(/&/,$ENV{'QUERY_STRING'})) { - 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; - } - } + # + # Global directory configs + # + $includedir = $r->dir_config('lonIncludes'); + $tmpdir = $r->dir_config('lonDaemons').'/tmp/'; + # + # Roles Checking + # + # Needs to be in a course + if (! $ENV{'request.course.fn'}) { + # Not in a course, or not allowed to modify parms + $ENV{'user.error.msg'}= + $r->uri.":opa:0:0:Cannot modify spreadsheet"; + return HTTP_NOT_ACCEPTABLE; } - + # + # Get query string for limited number of parameters + # + &Apache::loncommon::get_unprocessed_cgi + ($ENV{'QUERY_STRING'},['uname','udom','usymb','ufn','mapid','resid']); + # + # Deal with restricted student permissions + # + if ($ENV{'request.role'} =~ /^st\./) { + delete $ENV{'form.unewfield'} if (exists($ENV{'form.unewfield'})); + delete $ENV{'form.unewformula'} if (exists($ENV{'form.unewformula'})); + } + # + # Clean up symb and spreadsheet filename + # if (($ENV{'form.usymb'}=~/^\_(\w+)/) && (!$ENV{'form.ufn'})) { - $ENV{'form.ufn'}='default_'.$1; + $ENV{'form.ufn'}='default_'.$1; } - -# -------------------------------------- Interactive loading of specific sheet? + # + # Interactive loading of specific sheet? + # 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 $adom; - unless ($ENV{'form.uname'}) { - $aname=$ENV{'user.name'}; + $aname=$ENV{'user.name'}; $adom=$ENV{'user.domain'}; } else { $aname=$ENV{'form.uname'}; $adom=$ENV{'form.udom'}; } - -# ------------------------------------------------------------------- Open page - + # + # Open page, try to prevent browser cache. + # $r->content_type('text/html'); $r->header_out('Cache-control','no-cache'); $r->header_out('Pragma','no-cache'); $r->send_http_header; - -# --------------------------------------------------------------- Screen output - + # + # Header.... + # $r->print('LON-CAPA Spreadsheet'); - $r->print(<print(< - function celledit(cn,cf) { - var cnf=prompt(cn,cf); - if (cnf!=null) { - document.sheet.unewfield.value=cn; - document.sheet.unewformula.value=cnf; - document.sheet.submit(); + var editwin; + + function celledit(cellname,cellformula) { + var edit_text = ''; + edit_text +='Cell Edit Window'; + edit_text += '

'; + edit_text += '

Cell '+cellname+'

'; + edit_text += '