--- loncom/interface/Attic/lonspreadsheet.pm 2000/12/11 18:33:24 1.13 +++ loncom/interface/Attic/lonspreadsheet.pm 2001/10/16 20:50:28 1.68 @@ -1,24 +1,50 @@ # 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 Gerd Kortemeyer +# 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 Gerd Kortemeyer package Apache::lonspreadsheet; - + use strict; use Safe; use Safe::Hole; use Opcode; use Apache::lonnet; use Apache::Constants qw(:common :http); -use HTML::TokeParser; use GDBM_File; +use HTML::TokeParser; + +# +# Caches for previously calculated spreadsheets +# + +my %oldsheets; +my %loadedcaches; +my %expiredates; + +# +# Cache for stores of an individual user +# + +my $cachedassess; +my %cachedstores; # # These cache hashes need to be independent of user, resource and course -# (user and course are in the keys) +# (user and course can/should be in the keys) # -use vars qw(%spreadsheets %courserdatas %userrdatas); + +my %spreadsheets; +my %courserdatas; +my %userrdatas; +my %defaultsheets; +my %updatedata; + # # These global hashes are dependent on user, course and resource, # and need to be initialized every time when a sheet is calculated @@ -26,15 +52,17 @@ use vars qw(%spreadsheets %courserdatas my %courseopt; my %useropt; my %parmhash; -my $csec; -my $uname; -my $udom; + +# Stuff that only the screen handler can know + +my $includedir; +my $tmpdir; # ============================================================================= # ===================================== Implements an instance of a spreadsheet sub initsheet { - my $safeeval = new Safe; + my $safeeval = new Safe(shift); my $safehole = new Safe::Hole; $safeeval->permit("entereval"); $safeeval->permit(":base_math"); @@ -50,17 +78,40 @@ sub initsheet { # v: output values # c: preloaded constants (A-column) # rl: row label +# os: other spreadsheets (for student spreadsheet only) -%v=(); -%t=(); -%f=(); -%c=(); -%rl=(); +undef %v; +undef %t; +undef %f; +undef %c; +undef %rl; +undef @os; $maxrow=0; $sheettype=''; + +# filename/reference of the sheet + $filename=''; +# user data +$uname=''; +$uhome=''; +$udom=''; + +# course data + +$csec=''; +$chome=''; +$cnum=''; +$cdom=''; +$cid=''; +$cfn=''; + +# symb + +$usymb=''; + sub mask { my ($lower,$upper)=@_; @@ -257,11 +308,74 @@ sub SUMMIN { return $sum; } +sub expandnamed { + my $expression=shift; + if ($expression=~/^\&/) { + my ($func,$var,$formula)=($expression=~/^\&(\w+)\(([^\;]+)\;(.*)\)/); + my @vars=split(/\W+/,$formula); + my %values=(); + undef %values; + map { + my $varname=$_; + if ($varname=~/\D/) { + $formula=~s/$varname/'$c{\''.$varname.'\'}'/ge; + $varname=~s/$var/\(\\w\+\)/g; + map { + if ($_=~/$varname/) { + $values{$1}=1; + } + } keys %c; + } + } @vars; + if ($func eq 'EXPANDSUM') { + my $result=''; + map { + my $thissum=$formula; + $thissum=~s/$var/$_/g; + $result.=$thissum.'+'; + } keys %values; + $result=~s/\+$//; + return $result; + } else { + return 0; + } + } else { + return '$c{\''.$expression.'\'}'; + } +} + sub sett { %t=(); + my $pattern=''; + if ($sheettype eq 'assesscalc') { + $pattern='A'; + } else { + $pattern='[A-Z]'; + } map { - if ($f{$_}) { - if ($_=~/^A/) { + if ($_=~/template\_(\w)/) { + my $col=$1; + unless ($col=~/^$pattern/) { + map { + 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; + } + } + } keys %f; + } + } + } keys %f; + map { + if (($f{$_}) && ($_!~/template\_/)) { + my $matches=($_=~/^$pattern(\d+)/); + if (($matches) && ($1)) { unless ($f{$_}=~/^\!/) { $t{$_}=$c{$_}; } @@ -269,9 +383,14 @@ sub sett { $t{$_}=$f{$_}; $t{$_}=~s/\.\.+/\,/g; $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g; + $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge; } } } keys %f; + $t{'A0'}=$f{'A0'}; + $t{'A0'}=~s/\.\.+/\,/g; + $t{'A0'}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g; + $t{'A0'}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge; } sub calc { @@ -299,6 +418,50 @@ sub calc { return ''; } +sub templaterow { + my @cols=(); + $cols[0]='Template'; + map { + my $fm=$f{'template_'.$_}; + $fm=~s/[\'\"]/\&\#34;/g; + $cols[$#cols+1]="'template_$_','$fm'".'___eq___'.$fm; + } ('A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m', + 'n','o','p','q','r','s','t','u','v','w','x','y','z'); + return @cols; +} + +sub outrowassess { + my $n=shift; + my @cols=(); + if ($n) { + my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{'A'.$n}); + $cols[0]=$rl{$usy}.'
'. + ''; + } else { + $cols[0]='Export'; + } + map { + my $fm=$f{$_.$n}; + $fm=~s/[\'\"]/\&\#34;/g; + $cols[$#cols+1]="'$_$n','$fm'".'___eq___'.$v{$_.$n}; + } ('A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m', + 'n','o','p','q','r','s','t','u','v','w','x','y','z'); + return @cols; +} + sub outrow { my $n=shift; my @cols=(); @@ -318,6 +481,15 @@ sub outrow { return @cols; } +sub exportrowa { + my @exportarray=(); + map { + $exportarray[$#exportarray+1]=$v{$_.'0'}; + } ('A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z'); + return @exportarray; +} + # ------------------------------------------- End of "Inside of the safe space" ENDDEFS $safeeval->reval($code); @@ -327,22 +499,29 @@ ENDDEFS # ------------------------------------------------ Add or change formula values sub setformulas { - my ($safeeval,@f)=@_; - $safeeval->reval('%f='."('".join("','",@f)."');"); + my ($safeeval,%f)=@_; + %{$safeeval->varglob('f')}=%f; } # ------------------------------------------------ Add or change formula values sub setconstants { - my ($safeeval,@c)=@_; - $safeeval->reval('%c='."('".join("','",@c)."');"); + my ($safeeval,%c)=@_; + %{$safeeval->varglob('c')}=%c; +} + +# --------------------------------------------- Set names of other spreadsheets + +sub setothersheets { + my ($safeeval,@os)=@_; + @{$safeeval->varglob('os')}=@os; } # ------------------------------------------------ Add or change formula values sub setrowlabels { - my ($safeeval,@rl)=@_; - $safeeval->reval('%rl='."('".join("','",@rl)."');"); + my ($safeeval,%rl)=@_; + %{$safeeval->varglob('rl')}=%rl; } # ------------------------------------------------------- Calculate spreadsheet @@ -363,14 +542,7 @@ sub getvalues { sub getformulas { my $safeeval=shift; - return $safeeval->reval('%f'); -} - -# -------------------------------------------------------------------- Set type - -sub settype { - my ($safeeval,$type)=@_; - $safeeval->reval('$sheettype="'.$type.'";'); + return %{$safeeval->varglob('f')}; } # -------------------------------------------------------------------- Get type @@ -379,6 +551,7 @@ sub gettype { my $safeeval=shift; return $safeeval->reval('$sheettype'); } + # ------------------------------------------------------------------ Set maxrow sub setmaxrow { @@ -406,25 +579,129 @@ 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'); +} + +# ------------------------------------------------------------- Get user domain + +sub getudom { + my $safeeval=shift; + return $safeeval->reval('$udom'); +} + +# --------------------------------------------------------------- Get user home + +sub getuhome { + my $safeeval=shift; + return $safeeval->reval('$uhome'); +} + +# -------------------------------------------------------------------- Get symb + +sub getusymb { + my $safeeval=shift; + return $safeeval->reval('$usymb'); +} + +# ------------------------------------------------------------- Export of A-row + +sub exportdata { + my $safeeval=shift; + return $safeeval->reval('&exportrowa()'); +} + + # ========================================================== End of Spreadsheet # ============================================================================= - +# +# Procedures for screen output +# # --------------------------------------------- Produce output row n from sheet sub rown { my ($safeeval,$n)=@_; - my $defaultbg=((($n-1)/5)==int(($n-1)/5))?'#E0E0':'#FFFF'; - my $rowdata="\n$n"; + my $defaultbg; + my $rowdata=''; + my $dataflag=0; + unless ($n eq '-') { + $defaultbg=((($n-1)/5)==int(($n-1)/5))?'#E0E0':'#FFFF'; + } else { + $defaultbg='#E0FF'; + } + $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'; + } + if ($sheettype eq 'assesscalc') { + $maxred=1; + } else { + $maxred=26; + } + if ($n eq '-') { $proc='&templaterow'; $n=-1; $dataflag=1; } map { my $bgcolor=$defaultbg.((($showf-1)/5==int(($showf-1)/5))?'99':'DD'); my ($fm,$vl)=split(/\_\_\_eq\_\_\_/,$_); + if ((($vl ne '') || ($vl eq '0')) && + (($showf==1) || ($sheettype ne 'studentcalc'))) { $dataflag=1; } if ($showf==0) { $vl=$_; } - if ($showf<=1) { $bgcolor='#FFDDDD'; } + if ($showf<=$maxred) { $bgcolor='#FFDDDD'; } if (($n==0) && ($showf<=26)) { $bgcolor='#CCCCFF'; } - if ($showf>1) { + if (($showf>$maxred) || ((!$n) && ($showf>0))) { if ($vl eq '') { $vl='#'; } @@ -435,70 +712,280 @@ sub rown { $rowdata.=' '.$vl.' '; } $showf++; - } $safeeval->reval('&outrow('.$n.')'); - return $rowdata.''; + } $safeeval->reval($proc.'('.$n.')'); + if ($ENV{'form.showall'} || ($dataflag)) { + return $rowdata.''; + } else { + return ''; + } } # ------------------------------------------------------------- Print out sheet sub outsheet { - my $safeeval=shift; - my $tabledata=''. - ''; + 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= + '
 A Import
'. + ''. + ''; + my $showf=0; map { - $tabledata.=""; - } ('B','C','D','E','F','G','H','I','J','K','L','M', + $showf++; + if ($showf<=$maxred) { + $tabledata.='"; + } ('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'); $tabledata.=''; my $row; my $maxrow=&getmaxrow($safeeval); - for ($row=0;$row<=$maxrow;$row++) { - $tabledata.=&rown($safeeval,$row); + $tabledata.=&rown($safeeval,'-').&rown($safeeval,0); + $r->print($tabledata); + + 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)) { + $r->print("
'. + $realm.'ImportCalculations
$_'; + } else { + $tabledata.=''; + } + $tabledata.="$_
\n
\n"); + $r->rflush(); + $r->print(''); + map { + $r->print(''); + } ('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(''); + } + $n++; + $r->print($thisrow); + } } - $tabledata.='
 '.$what.''.$_.'
'; + $r->print(''); } +# +# ----------------------------------------------- Read list of available sheets +# +sub othersheets { + my ($safeeval,$stype)=@_; -# --------------------------------------- Read spreadsheet formulas from a file + my $cnum=&getcnum($safeeval); + my $cdom=&getcdom($safeeval); + my $chome=&getchome($safeeval); + + my @alternatives=(); + my $result=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.':'. + $stype.'_spreadsheets',$chome); + if ($result!~/^error\:/) { + map { + $alternatives[$#alternatives+1]= + &Apache::lonnet::unescape((split(/\=/,$_))[0]); + } split(/\&/,$result); + } + return @alternatives; +} + +# +# -------------------------------------- Read spreadsheet formulas for a course +# sub readsheet { - my ($safeeval,$fn)=@_; - &setfilename($safeeval,$fn); - $fn=~/\.(\w+)/; - &settype($safeeval,$1); - my %f=(); - unless ($spreadsheets{$fn}) { - $spreadsheets{$fn}=''; + my ($safeeval,$fn)=@_; + my $stype=&gettype($safeeval); + my $cnum=&getcnum($safeeval); + my $cdom=&getcdom($safeeval); + my $chome=&getchome($safeeval); + +# --------- There is no filename. Look for defaults in course and global, cache + + unless($fn) { + unless ($fn=$defaultsheets{$cnum.'_'.$cdom.'_'.$stype}) { + $fn=&Apache::lonnet::reply('get:'.$cdom.':'.$cnum. + ':environment:spreadsheet_default_'.$stype, + $chome); + unless (($fn) && ($fn!~/^error\:/)) { + $fn='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; - if ($fh=Apache::File->new($fn)) { - $spreadsheets{$fn}=join('',<$fh>); - } + if ($fh=Apache::File->new($includedir. + '/default.'.&gettype($safeeval))) { + $sheetxml=join('',<$fh>); + } } + my $parser=HTML::TokeParser->new(\$sheetxml); + my $token; + while ($token=$parser->get_token) { + if ($token->[0] eq 'S') { + if ($token->[1] eq 'field') { + $f{$token->[2]->{'col'}.$token->[2]->{'row'}}= + $parser->get_text('/field'); + } + if ($token->[1] eq 'template') { + $f{'template_'.$token->[2]->{'col'}}= + $parser->get_text('/template'); + } + } + } + } else { + my $sheet=''; + my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.':'.$fn, + $chome); + unless ($reply=~/^error\:/) { + $sheet=$reply; + } + map { + my ($name,$value)=split(/\=/,$_); + $f{&Apache::lonnet::unescape($name)}= + &Apache::lonnet::unescape($value); + } split(/\&/,$sheet); + } +# --------------------------------------------------------------- Cache and set + $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f); + &setformulas($safeeval,%f); } - { - my $parser=HTML::TokeParser->new(\$spreadsheets{$fn}); - my $token; - while ($token=$parser->get_token) { - if ($token->[0] eq 'S') { - if ($token->[1] eq 'field') { - $f{$token->[2]->{'col'}.$token->[2]->{'row'}}= - $parser->get_text('/field'); - } - } +} + +# -------------------------------------------------------- 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; +} + +# ------------------------------------------------------------ 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=''; + map { + unless ($f{$_} eq 'import') { + $sheetdata.=&Apache::lonnet::escape($_).'='. + &Apache::lonnet::escape($f{$_}).'&'; + } + } keys %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; } - } - &setformulas($safeeval,%f); + } + return 'unauthorized'; } # ----------------------------------------------- Make a temp copy of the sheet +# "Modified workcopy" - interactive only +# sub tmpwrite { - my ($safeeval,$tmpdir,$symb)=@_; - my $fn=$uname.'_'.$udom.'_spreadsheet_'.&getfilename($safeeval); + my $safeeval=shift; + my $fn=$ENV{'user.name'}.'_'. + $ENV{'user.domain'}.'_spreadsheet_'.&getusymb($safeeval).'_'. + &getfilename($safeeval); $fn=~s/\W/\_/g; $fn=$tmpdir.$fn.'.tmp'; my $fh; @@ -510,8 +997,10 @@ sub tmpwrite { # ---------------------------------------------------------- Read the temp copy sub tmpread { - my ($safeeval,$tmpdir,$symb,$nfield,$nform)=@_; - my $fn=$uname.'_'.$udom.'_spreadsheet_'.&getfilename($safeeval); + my ($safeeval,$nfield,$nform)=@_; + my $fn=$ENV{'user.name'}.'_'. + $ENV{'user.domain'}.'_spreadsheet_'.&getusymb($safeeval).'_'. + &getfilename($safeeval); $fn=~s/\W/\_/g; $fn=$tmpdir.$fn.'.tmp'; my $fh; @@ -525,42 +1014,33 @@ sub tmpread { $fo{$name}=$value; } } - $fo{$nfield}=$nform; - &setformulas($safeeval,%fo); -} - -# --------------------------------------------------------------- Read metadata - -sub readmeta { - my $fn=shift; - unless ($fn=~/\.meta$/) { $fn.='meta'; } - my $content; - my %returnhash=(); - { - my $fh=Apache::File->new($fn); - $content=join('',<$fh>); + if ($nform eq 'changesheet') { + $fo{'A'.$nfield}=(split(/\_\_\&\&\&\_\_/,$fo{'A'.$nfield}))[0]; + unless ($ENV{'form.sel_'.$nfield} eq 'Default') { + $fo{'A'.$nfield}.='__&&&__'.$ENV{'form.sel_'.$nfield}; + } + } else { + if ($nfield) { $fo{$nfield}=$nform; } } - my $parser=HTML::TokeParser->new(\$content); - my $token; - while ($token=$parser->get_token) { - if ($token->[0] eq 'S') { - my $entry=$token->[1]; - if (($entry eq 'stores') || ($entry eq 'parameter')) { - my $unikey=$entry; - $unikey.='_'.$token->[2]->{'part'}; - $unikey.='_'.$token->[2]->{'name'}; - $returnhash{$unikey}=$token->[2]->{'display'}; - } - } - } - return %returnhash; + &setformulas($safeeval,%fo); } # ================================================================== 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,$symb)=@_; + my ($what,$safeeval)=@_; + my $cid=&getcid($safeeval); + my $csec=&getcsec($safeeval); + my $uname=&getuname($safeeval); + my $udom=&getudom($safeeval); + my $symb=&getusymb($safeeval); unless ($symb) { return ''; } my $result=''; @@ -569,27 +1049,28 @@ sub parmval { # ----------------------------------------------------- Cascading lookup scheme my $rwhat=$what; $what=~s/^parameter\_//; - $what=~s/\_/\./; + $what=~s/\_([^\_]+)$/\.$1/; my $symbparm=$symb.'.'.$what; my $mapparm=$mapname.'___(all).'.$what; + my $usercourseprefix=$uname.'_'.$udom.'_'.$cid; my $seclevel= - $ENV{'request.course.id'}.'.['. + $usercourseprefix.'.['. $csec.'].'.$what; my $seclevelr= - $ENV{'request.course.id'}.'.['. + $usercourseprefix.'.['. $csec.'].'.$symbparm; my $seclevelm= - $ENV{'request.course.id'}.'.['. + $usercourseprefix.'.['. $csec.'].'.$mapparm; my $courselevel= - $ENV{'request.course.id'}.'.'.$what; + $usercourseprefix.'.'.$what; my $courselevelr= - $ENV{'request.course.id'}.'.'.$symbparm; + $usercourseprefix.'.'.$symbparm; my $courselevelm= - $ENV{'request.course.id'}.'.'.$mapparm; + $usercourseprefix.'.'.$mapparm; # ---------------------------------------------------------- fourth, check user @@ -632,21 +1113,115 @@ sub parmval { } +# ---------------------------------------------- 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 %currentlist=(); + my $now=time; + unless ($classlst=~/^error\:/) { + map { + 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) { + $rowlabel='Data not available: '.$name. + ''; + } 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)); + $rowlabel=''. + $ssec.' '.$reply{$sname}.'
'; + map { + $rowlabel.=&Apache::lonnet::unescape($_).' '; + } split(/\&/,$reply); + $rowlabel.='
'; + } + $currentlist{&Apache::lonnet::unescape($name)}=$rowlabel; + } + } split(/\&/,$classlst); +# +# -------------------- Find discrepancies between the course row table and this +# + my %f=&getformulas($safeeval); + my $changed=0; + + my $maxrow=0; + my %existing=(); + +# ----------------------------------------------------------- Now obsolete rows + map { + if ($_=~/^A(\d+)/) { + $maxrow=($1>$maxrow)?$1:$maxrow; + $existing{$f{$_}}=1; + unless ((defined($currentlist{$f{$_}})) || (!$1)) { + $f{$_}='!!! Obsolete'; + $changed=1; + } + } + } keys %f; + +# -------------------------------------------------------- New and unknown keys + + map { + unless ($existing{$_}) { + $changed=1; + $maxrow++; + $f{'A'.$maxrow}=$_; + } + } sort keys %currentlist; + + if ($changed) { &setformulas($safeeval,%f); } -# ----------------------------------------------------------------- Update rows + &setmaxrow($safeeval,$maxrow); + &setrowlabels($safeeval,%currentlist); -sub updaterows { + } else { + return 'Could not access course data'; + } +} + +# ----------------------------------- 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=(); + my %allkeys=('timestamp' => + 'Timestamp of Last Transaction
timestamp'); my %allassess=(); - my $stype=&gettype($safeeval); + my $adduserstr=''; + if ((&getuname($safeeval) ne $ENV{'user.name'}) || + (&getudom($safeeval) ne $ENV{'user.domain'})) { + $adduserstr='&uname='.&getuname($safeeval). + '&udom='.&getudom($safeeval); + } map { if ($_=~/^src\_(\d+)\.(\d+)$/) { @@ -659,8 +1234,9 @@ sub updaterows { &Apache::lonnet::declutter($bighash{'map_id_'.$mapid}). '___'.$resid.'___'. &Apache::lonnet::declutter($srcf); - $allassess{$symb}=$bighash{'title_'.$id}; - + $allassess{$symb}= + ''. + $bighash{'title_'.$id}.''; if ($stype eq 'assesscalc') { map { if (($_=~/^stores\_(.*)/) || ($_=~/^parameter\_(.*)/)) { @@ -668,9 +1244,10 @@ sub updaterows { my $display= &Apache::lonnet::metadata($srcf,$key.'.display'); unless ($display) { - $display= + $display.= &Apache::lonnet::metadata($srcf,$key.'.name'); } + $display.='
'.$key; $allkeys{$key}=$display; } } split(/\,/,&Apache::lonnet::metadata($srcf,'keys')); @@ -684,17 +1261,26 @@ sub updaterows { # %allkeys has a list of storage and parameter displays by unikey # %allassess has a list of all resource displays by symb # -# -------------------- Find discrepancies between the course row table and this -# - my %f=&getformulas($safeeval); - my $changed=0; - my %current=(); if ($stype eq 'assesscalc') { %current=%allkeys; } elsif ($stype eq 'studentcalc') { %current=%allassess; } + $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}); + } +# -------------------- Find discrepancies between the course row table and this +# + my %f=&getformulas($safeeval); + my $changed=0; my $maxrow=0; my %existing=(); @@ -703,10 +1289,14 @@ sub updaterows { map { if ($_=~/^A(\d+)/) { $maxrow=($1>$maxrow)?$1:$maxrow; - $existing{$f{$_}}=1; - unless (defined($current{$f{$_}})) { + my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{$_}); + $existing{$usy}=1; + unless ((defined($current{$usy})) || (!$1)) { $f{$_}='!!! Obsolete'; $changed=1; + } elsif ($ufn) { + $current{$usy} + =~s/assesscalc\?usymb\=/assesscalc\?ufn\=$ufn\&usymb\=/; } } } keys %f; @@ -720,29 +1310,178 @@ sub updaterows { $f{'A'.$maxrow}=$_; } } keys %current; - + if ($changed) { &setformulas($safeeval,%f); } &setmaxrow($safeeval,$maxrow); &setrowlabels($safeeval,%current); + + undef %current; + undef %existing; +} - } else { - return 'Could not access course data'; +# ------------------------------------------------ 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\:/) { + map { + my ($name,$value)=split(/\=/,$_); + $cachedstores{&Apache::lonnet::unescape($name)}= + &Apache::lonnet::unescape($value); + } split(/\&/,$reply); + } } + my @assessdata=(); + map { + 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; + map { + 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++; + } ('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'); + } + } + } keys %f; + $cachedassess=''; + undef %cachedstores; + &setformulas($safeeval,%f); + &setconstants($safeeval,%c); +} + +# --------------------------------------------------- Load data for one student + +sub loadcourse { + my ($safeeval,$r)=@_; + my %c=(); + my %f=&getformulas($safeeval); + my $total=0; + map { + if ($_=~/^A(\d+)/) { + unless ($f{$_}=~/^\!/) { $total++; } + } + } keys %f; + my $now=0; + my $since=time; + $r->print(< + popwin=open('','popwin','width=400,height=100'); + popwin.document.writeln(''+ + '

Spreadsheet Calculation Progress

'+ + '
'+ + '
'+ + ''); + popwin.document.close(); + +ENDPOP + $r->rflush(); + map { + 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; + map { + 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++; + } ('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'); + } + } + } keys %f; + &setformulas($safeeval,%f); + &setconstants($safeeval,%c); + $r->print(''); + $r->rflush(); } # ------------------------------------------------ Load data for one assessment -sub rowaassess { - my ($safeeval,$symb)=@_; - my $uhome=&Apache::lonnet::homeserver($uname,$udom); +sub loadassessment { + my $safeeval=shift; + + 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 $namespace; - unless ($namespace=$ENV{'request.course.id'}) { return ''; } + 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++) { + map { + $returnhash{$_}=$cachedstores{$scope.':'.$symb.':'.$_}; + } split(/\:/,$cachedstores{$scope.':keys:'.$symb}); + } + + } else { +# +# restore individual +# + my $answer=&Apache::lonnet::reply( - "restore:$udom:$uname:$namespace:$symb",$uhome); - my %returnhash=(); + "restore:$udom:$uname:". + &Apache::lonnet::escape($namespace).":". + &Apache::lonnet::escape($symb),$uhome); map { my ($name,$value)=split(/\=/,$_); $returnhash{&Apache::lonnet::unescape($name)}= @@ -754,33 +1493,34 @@ sub rowaassess { $returnhash{$_}=$returnhash{$version.':'.$_}; } split(/\:/,$returnhash{$version.':keys'}); } + } # ----------------------------- returnhash now has all stores for this resource # ---------------------------- initialize coursedata and userdata for this user - %courseopt=(); - %useropt=(); - my $uhome=&Apache::lonnet::homeserver($uname,$udom); + undef %courseopt; + undef %useropt; + + my $userprefix=$uname.'_'.$udom.'_'; + unless ($uhome eq 'no_host') { # -------------------------------------------------------------- Get coursedata unless - ((time-$courserdatas{$ENV{'request.course.id'}.'.last_cache'})<120) { - my $reply=&Apache::lonnet::reply('dump:'. - $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'. - $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.':resourcedata', - $ENV{'course.'.$ENV{'request.course.id'}.'.home'}); + ((time-$courserdatas{$cid.'.last_cache'})<240) { + my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum. + ':resourcedata',$chome); if ($reply!~/^error\:/) { - $courserdatas{$ENV{'request.course.id'}}=$reply; - $courserdatas{$ENV{'request.course.id'}.'.last_cache'}=time; + $courserdatas{$cid}=$reply; + $courserdatas{$cid.'.last_cache'}=time; } } map { my ($name,$value)=split(/\=/,$_); - $courseopt{&Apache::lonnet::unescape($name)}= + $courseopt{$userprefix.&Apache::lonnet::unescape($name)}= &Apache::lonnet::unescape($value); - } split(/\&/,$courserdatas{$ENV{'request.course.id'}}); + } split(/\&/,$courserdatas{$cid}); # --------------------------------------------------- Get userdata (if present) unless - ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<120) { + ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) { my $reply= &Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome); if ($reply!~/^error\:/) { @@ -790,27 +1530,53 @@ sub rowaassess { } map { my ($name,$value)=split(/\=/,$_); - $useropt{&Apache::lonnet::unescape($name)}= - &Apache::lonnet::unescape($value); + $useropt{$userprefix.&Apache::lonnet::unescape($name)}= + &Apache::lonnet::unescape($value); } split(/\&/,$userrdatas{$uname.'___'.$udom}); - } -# -- now courseopt, useropt initialized for this user and course (used parmval) + } +# ----------------- now courseopt, useropt initialized for this user and course +# (used by parmval) - my %c=(); +# +# Load keys for this assessment only +# + my %thisassess=(); + my ($symap,$syid,$srcf)=split(/\_\_\_/,$symb); + + map { + $thisassess{$_}=1; + } split(/\,/,&Apache::lonnet::metadata($srcf,'keys')); +# +# Load parameters +# + my %c=(); + + if (tie(%parmhash,'GDBM_File', + &getcfn($safeeval).'_parms.db',&GDBM_READER,0640)) { my %f=&getformulas($safeeval); map { if ($_=~/^A/) { unless ($f{$_}=~/^\!/) { if ($f{$_}=~/^parameter/) { - $c{$_}=&parmval($f{$_},$symb); + if ($thisassess{$f{$_}}) { + my $val=&parmval($f{$_},$safeeval); + $c{$_}=$val; + $c{$f{$_}}=$val; + } } else { - $c{$_}=$returnhash{$f{$_}}; + my $key=$f{$_}; + my $ckey=$key; + $key=~s/^stores\_/resource\./; + $key=~s/\_/\./; + $c{$_}=$returnhash{$key}; + $c{$ckey}=$returnhash{$key}; } } } } keys %f; - - &setconstants($safeeval,%c); + untie(%parmhash); + } + &setconstants($safeeval,%c); } # --------------------------------------------------------- Various form fields @@ -837,49 +1603,318 @@ sub selectbox { return $selout.''; } -# ==================================== Sub handler to get export of assessments +# =============================================== Update information in a sheet +# +# Add new users or assessments, etc. +# + +sub updatesheet { + my $safeeval=shift; + my $stype=&gettype($safeeval); + if ($stype eq 'classcalc') { + return &updateclasssheet($safeeval); + } else { + return &updatestudentassesssheet($safeeval); + } +} + +# =================================================== Load the rows for a sheet +# +# Import the data for rows +# + +sub loadrows { + my ($safeeval,$r)=@_; + my $stype=&gettype($safeeval); + if ($stype eq 'classcalc') { + &loadcourse($safeeval,$r); + } elsif ($stype eq 'studentcalc') { + &loadstudent($safeeval); + } else { + &loadassessment($safeeval); + } +} + +# ======================================================= Forced recalculation? + +sub checkthis { + my ($keyname,$time)=@_; + return ($time<$expiredates{$keyname}); +} +sub forcedrecalc { + my ($uname,$udom,$stype,$usymb)=@_; + my $key=$uname.':'.$udom.':'.$stype.':'.$usymb; + my $time=$oldsheets{$key.'.time'}; + if ($ENV{'form.forcerecalc'}) { return 1; } + unless ($time) { return 1; } + if ($stype eq 'assesscalc') { + my $map=(split(/\_\_\_/,$usymb))[0]; + if (&checkthis('::assesscalc:',$time) || + &checkthis('::assesscalc:'.$map,$time) || + &checkthis('::assesscalc:'.$usymb,$time) || + &checkthis($uname.':'.$udom.':assesscalc:',$time) || + &checkthis($uname.':'.$udom.':assesscalc:'.$map,$time) || + &checkthis($uname.':'.$udom.':assesscalc:'.$usymb,$time)) { + return 1; + } + } else { + if (&checkthis('::studentcalc:',$time) || + &checkthis($uname.':'.$udom.':studentcalc:',$time)) { + return 1; + } + } + return 0; +} + +# ============================================================== Export handler +# +# Non-interactive call from with program +# + +sub exportsheet { + my ($uname,$udom,$stype,$usymb,$fn)=@_; + my @exportarr=(); +# +# Check if cached +# + + my $key=$uname.':'.$udom.':'.$stype.':'.$usymb; + my $found=''; + + if ($oldsheets{$key}) { + map { + my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_); + if ($name eq $fn) { + $found=$value; + } + } split(/\_\_\_\&\_\_\_/,$oldsheets{$key}); + } + + unless ($found) { + &cachedssheets($uname,$udom,&Apache::lonnet::homeserver($uname,$udom)); + if ($oldsheets{$key}) { + map { + my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_); + if ($name eq $fn) { + $found=$value; + } + } split(/\_\_\_\&\_\_\_/,$oldsheets{$key}); + } + } +# +# 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)); + + } + my %currentlystored=(); + unless ($current=~/^error\:/) { + map { + my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_); + $currentlystored{$name}=$value; + } split(/\_\_\_\&\_\_\_/,&Apache::lonnet::unescape($current)); + } + $currentlystored{$fn}=join('___;___',@exportarr); + + my $newstore=''; + map { + if ($newstore) { $newstore.='___&___'; } + $newstore.=$_.'___=___'.$currentlystored{$_}; + } keys %currentlystored; + my $now=time; + if ($stype eq 'studentcalc') { + &Apache::lonnet::reply('put:'. + $ENV{'course.'.$cid.'.domain'}.':'. + $ENV{'course.'.$cid.'.num'}. + ':nohist_calculatedsheets:'. + &Apache::lonnet::escape($key).'='. + &Apache::lonnet::escape($newstore).'&'. + &Apache::lonnet::escape($key).'.time='.$now, + $ENV{'course.'.$cid.'.home'}); + } else { + &Apache::lonnet::reply('put:'. + &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; +} +# ============================================================ Expiration Dates +# +# Load previously cached student spreadsheets for this course +# + +sub expirationdates { + undef %expiredates; + my $cid=$ENV{'request.course.id'}; + my $reply=&Apache::lonnet::reply('dump:'. + $ENV{'course.'.$cid.'.domain'}.':'. + $ENV{'course.'.$cid.'.num'}. + ':nohist_expirationdates', + $ENV{'course.'.$cid.'.home'}); + unless ($reply=~/^error\:/) { + map { + my ($name,$value)=split(/\=/,$_); + $expiredates{&Apache::lonnet::unescape($name)} + =&Apache::lonnet::unescape($value); + } split(/\&/,$reply); + } +} + +# ===================================================== Calculated sheets cache +# +# Load previously cached student spreadsheets for this course +# + +sub cachedcsheets { + my $cid=$ENV{'request.course.id'}; + my $reply=&Apache::lonnet::reply('dump:'. + $ENV{'course.'.$cid.'.domain'}.':'. + $ENV{'course.'.$cid.'.num'}. + ':nohist_calculatedsheets', + $ENV{'course.'.$cid.'.home'}); + unless ($reply=~/^error\:/) { + map { + my ($name,$value)=split(/\=/,$_); + $oldsheets{&Apache::lonnet::unescape($name)} + =&Apache::lonnet::unescape($value); + } split(/\&/,$reply); + } +} + +# ===================================================== Calculated sheets cache +# +# Load previously cached assessment spreadsheets for this student +# + +sub cachedssheets { + my ($sname,$sdom,$shome)=@_; + unless (($loadedcaches{$sname.'_'.$sdom}) || ($shome eq 'no_host')) { + my $cid=$ENV{'request.course.id'}; + my $reply=&Apache::lonnet::reply('dump:'.$sdom.':'.$sname. + ':nohist_calculatedsheets_'. + $ENV{'request.course.id'}, + $shome); + unless ($reply=~/^error\:/) { + map { + my ($name,$value)=split(/\=/,$_); + $oldsheets{&Apache::lonnet::unescape($name)} + =&Apache::lonnet::unescape($value); + } split(/\&/,$reply); + } + $loadedcaches{$sname.'_'.$sdom}=1; + } +} + +# ===================================================== Calculated sheets cache +# +# Load previously cached assessment spreadsheets for this student +# # ================================================================ Main handler +# +# Interactive call to screen +# +# + sub handler { my $r=shift; - $uname=''; - $udom=''; - $csec=''; - - if ($r->header_only) { + if ($r->header_only) { $r->content_type('text/html'); $r->send_http_header; return OK; - } + } + +# ---------------------------------------------------- Global directory configs + +$includedir=$r->dir_config('lonIncludes'); +$tmpdir=$r->dir_config('lonDaemons').'/tmp/'; # ----------------------------------------------------- Needs to be in a course - if (($ENV{'request.course.fn'}) || - ($ENV{'request.state'} eq 'construct')) { + if ($ENV{'request.course.fn'}) { # --------------------------- Get query string for limited number of parameters + map { my ($name, $value) = split(/=/,$_); $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg; - if (($name eq 'uname') || ($name eq 'udom') || ($name eq 'usymb')) { + if (($name eq 'uname') || ($name eq 'udom') || + ($name eq 'usymb') || ($name eq 'ufn')) { unless ($ENV{'form.'.$name}) { $ENV{'form.'.$name}=$value; } } } (split(/&/,$ENV{'QUERY_STRING'})); +# -------------------------------------- Interactive loading of specific sheet? + if (($ENV{'form.load'}) && ($ENV{'form.loadthissheet'} ne 'Default')) { + $ENV{'form.ufn'}=$ENV{'form.loadthissheet'}; + } # ------------------------------------------- Nothing there? Must be login user + my $aname; + my $adom; + unless ($ENV{'form.uname'}) { - $uname=$ENV{'user.name'}; - $udom=$ENV{'user.domain'}; + $aname=$ENV{'user.name'}; + $adom=$ENV{'user.domain'}; } else { - $uname=$ENV{'form.uname'}; - $udom=$ENV{'form.udom'}; + $aname=$ENV{'form.uname'}; + $adom=$ENV{'form.udom'}; } + # ------------------------------------------------------------------- Open page $r->content_type('text/html'); @@ -887,6 +1922,8 @@ sub handler { $r->header_out('Pragma','no-cache'); $r->send_http_header; +# --------------------------------------------------------------- Screen output + $r->print('LON-CAPA Spreadsheet'); $r->print(< @@ -900,83 +1937,228 @@ sub handler { } } + function changesheet(cn) { + document.sheet.unewfield.value=cn; + document.sheet.unewformula.value='changesheet'; + document.sheet.submit(); + } + ENDSCRIPT $r->print(''. + ''. + '

LON-CAPA Spreadsheet

'. '
'. &hiddenfield('uname',$ENV{'form.uname'}). &hiddenfield('udom',$ENV{'form.udom'}). &hiddenfield('usymb',$ENV{'form.usymb'}). &hiddenfield('unewfield',''). &hiddenfield('unewformula','')); - my $sheetone=initsheet(); + +# ---------------------- Make sure that this gets out, even if user hits "stop" + + $r->rflush(); + +# ---------------------------------------------------------------- Full recalc? + + + if ($ENV{'form.forcerecalc'}) { + $r->print('

Completely Recalculating Sheet ...

'); + undef %spreadsheets; + undef %courserdatas; + undef %userrdatas; + undef %defaultsheets; + undef %updatedata; + } + +# ---------------------------------------- Read new sheet or modified worksheet + + $r->uri=~/\/(\w+)$/; + + my $asheet=&makenewsheet($aname,$adom,$1,$ENV{'form.usymb'}); + +# ------------------------ If a new formula had been entered, go from work copy + if ($ENV{'form.unewfield'}) { $r->print('

Modified Workcopy

'); $ENV{'form.unewformula'}=~s/\'/\"/g; - $r->print('New formula: '.$ENV{'form.unewfield'}.'='. - $ENV{'form.unewformula'}.'
'); - &tmpread($sheetone,$r->dir_config('lonDaemons').'/tmp/', - $ENV{'form.usymb'}, + $r->print('

New formula: '.$ENV{'form.unewfield'}.'='. + $ENV{'form.unewformula'}.'

'); + &setfilename($asheet,$ENV{'form.ufn'}); + &tmpread($asheet, $ENV{'form.unewfield'},$ENV{'form.unewformula'}); - &setfilename($sheetone,$r->filename); - $r->filename=~/\.(\w+)/; - &settype($sheetone,$1); - } else { - &readsheet($sheetone,$r->filename); - } - if (tie(%parmhash,'GDBM_File', - $ENV{'request.course.fn'}.'_parms.db',&GDBM_READER,0640)) { - $csec=&Apache::lonnet::usection($udom,$uname,$ENV{'request.course.id'}); - if ($csec eq '-1') { - $r->print('

'. - "User '$uname' at domain '$udom' not a student in this course

"); - } - &updaterows($sheetone); - untie(%parmhash); - } else { - $r->print('

'. - 'Could not initialize import fields (not in a course)

'); - } - &tmpwrite($sheetone,$r->dir_config('lonDaemons').'/tmp/', - $ENV{'form.usymb'}); - $r->print("User '$uname' at domain '$udom' for '". - $ENV{'course.'.$ENV{'request.course.id'}.'.description'}."'"); - if ($csec) { - $r->print(", group/section '$csec'"); - } - $r->print("\n"); - if (&gettype($sheetone) eq 'assesscalc') { - &rowaassess($sheetone,$ENV{'form.usymb'}); + + } elsif ($ENV{'form.saveas'}) { + &setfilename($asheet,$ENV{'form.ufn'}); + &tmpread($asheet); + } else { + &readsheet($asheet,$ENV{'form.ufn'}); } - &calcsheet($sheetone); - $r->print(&outsheet($sheetone)); - $r->print('
'); +# -------------------------------------------------- Print out user information - } else { -# ----------------------------- Not in a course, or not allowed to modify parms - $ENV{'user.error.msg'}= - $r->uri.":opa:0:0:Cannot modify spreadsheet"; - return HTTP_NOT_ACCEPTABLE; - } - return OK; -} + unless (&gettype($asheet) eq 'classcalc') { + $r->print('

User: '.&getuname($asheet). + '
Domain: '.&getudom($asheet)); + if (&getcsec($asheet) eq '-1') { + $r->print('

'. + 'Not a student in this course

'); + } else { + $r->print('
Section/Group: '.&getcsec($asheet)); + } + } -1; -__END__ +# ---------------------------------------------------------------- Course title + + $r->print('

'. + $ENV{'course.'.$ENV{'request.course.id'}.'.description'}. + '

'.localtime().'

'); + +# ---------------------------------------------------- See if user can see this + + if ((&gettype($asheet) eq 'classcalc') || + (&getuname($asheet) ne $ENV{'user.name'}) || + (&getudom($asheet) ne $ENV{'user.domain'})) { + unless (&Apache::lonnet::allowed('vgr',&getcid($asheet))) { + $r->print( + '

Access Permission Denied

'); + return OK; + } + } + +# ---------------------------------------------------------- Additional options + + $r->print( + '

' + ); + if (&gettype($asheet) eq 'assesscalc') { + $r->print ('

Level up: Student Sheet

'); + } + + if ((&gettype($asheet) eq 'studentcalc') && + (&Apache::lonnet::allowed('vgr',&getcid($asheet)))) { + $r->print ( + '

'. + 'Level up: Course Sheet

'); + } + + +# ----------------------------------------------------------------- Save dialog + if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { + my $fname=$ENV{'form.ufn'}; + $fname=~s/\_[^\_]+$//; + if ($fname eq 'default') { $fname='course_default'; } + $r->print(''. + ' (make default: )

'); + } + + $r->print(&hiddenfield('ufn',&getfilename($asheet))); + +# ----------------------------------------------------------------- Load dialog + if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { + $r->print('

'. + '

'); + if (&gettype($asheet) eq 'studentcalc') { + &setothersheets($asheet,&othersheets($asheet,'assesscalc')); + } + } + +# --------------------------------------------------------------- Cached sheets + + &expirationdates(); + + undef %oldsheets; + undef %loadedcaches; + + if (&gettype($asheet) eq 'classcalc') { + $r->print("Loading previously calculated student sheets ...
\n"); + $r->rflush(); + &cachedcsheets(); + } elsif (&gettype($asheet) eq 'studentcalc') { + $r->print("Loading previously calculated assessment sheets ...
\n"); + $r->rflush(); + &cachedssheets(&getuname($asheet),&getudom($asheet), + &getuhome($asheet)); + } + +# ----------------------------------------------------- Update sheet, load rows + + $r->print("Loaded sheet(s), updating rows ...
\n"); + $r->rflush(); + + &updatesheet($asheet); + $r->print("Updated rows, loading row data ...
\n"); + $r->rflush(); + &loadrows($asheet,$r); + $r->print("Loaded row data, calculating sheet ...
\n"); + $r->rflush(); + my $calcoutput=&calcsheet($asheet); + $r->print('

'.$calcoutput.'

'); +# ---------------------------------------------------- See if something to save + if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { + my $fname=''; + if ($ENV{'form.saveas'} && ($fname=$ENV{'form.newfn'})) { + $fname=~s/\W/\_/g; + if ($fname eq 'default') { $fname='course_default'; } + $fname.='_'.&gettype($asheet); + &setfilename($asheet,$fname); + $ENV{'form.ufn'}=$fname; + $r->print('

Saving spreadsheet: '. + &writesheet($asheet,$ENV{'form.makedefufn'}).'

'); + } + } +# ------------------------------------------------ Write the modified worksheet + $r->print('Current sheet: '.&getfilename($asheet).'

'); + &tmpwrite($asheet); + if (&gettype($asheet) eq 'studentcalc') { + $r->print('
Show rows with empty A column: '); + } else { + $r->print('
Show empty rows: '); + } + $r->print('print(' checked'); } + $r->print('>'); +# ------------------------------------------------------------- Print out sheet + &outsheet($r,$asheet); + $r->print(''); +# ------------------------------------------------------------------------ Done + } else { +# ----------------------------- Not in a course, or not allowed to modify parms + $ENV{'user.error.msg'}= + $r->uri.":opa:0:0:Cannot modify spreadsheet"; + return HTTP_NOT_ACCEPTABLE; + } + return OK; +} + +1; +__END__