# 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 Gerd Kortemeyer package Apache::lonspreadsheet; use strict; use Safe; use Safe::Hole; use Opcode; use Apache::lonnet; use Apache::Constants qw(:common :http); use GDBM_File; use HTML::TokeParser; # # These cache hashes need to be independent of user, resource and course # (user and course can/should be in the keys) # use vars qw(%spreadsheets %courserdatas %userrdatas %defaultsheets); # # These global hashes are dependent on user, course and resource, # and need to be initialized every time when a sheet is calculated # my %courseopt; my %useropt; my %parmhash; # ============================================================================= # ===================================== Implements an instance of a spreadsheet sub initsheet { my $safeeval = new Safe; my $safehole = new Safe::Hole; $safeeval->permit("entereval"); $safeeval->permit(":base_math"); $safeeval->permit("sort"); $safeeval->deny(":base_io"); $safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&EXT'); my $code=<<'ENDDEFS'; # ---------------------------------------------------- Inside of the safe space # # f: formulas # t: intermediate format (variable references expanded) # v: output values # c: preloaded constants (A-column) # rl: row label %v=(); %t=(); %f=(); %c=(); %rl=(); $maxrow=0; $sheettype=''; # filename/reference of the sheet $filename=''; # user data $uname=''; $uhome=''; $udom=''; # course data $csec=''; $chome=''; $cnum=''; $cdom=''; # symb $usymb=''; 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; my $alpha=''; my $num=''; if (($la eq '*') || ($ua eq '*')) { $alpha='[A-Za-z]'; } else { if (($la=~/[A-Z]/) && ($ua=~/[A-Z]/) || ($la=~/[a-z]/) && ($ua=~/[a-z]/)) { $alpha='['.$la.'-'.$ua.']'; } else { $alpha='['.$la.'-Za-'.$ua.']'; } } if (($ld eq '*') || ($ud eq '*')) { $num='\d+'; } else { if (length($ld)!=length($ud)) { $num.='('; map { $num.='['.$_.'-9]'; } ($ld=~m/\d/g); if (length($ud)-length($ld)>1) { $num.='|\d{'.(length($ld)+1).','.(length($ud)-1).'}'; } $num.='|'; map { $num.='[0-'.$_.']'; } ($ud=~m/\d/g); $num.=')'; } else { my @lda=($ld=~m/\d/g); my @uda=($ud=~m/\d/g); my $i; $j=0; $notdone=1; for ($i=0;($i<=$#lda)&&($notdone);$i++) { if ($lda[$i]==$uda[$i]) { $num.=$lda[$i]; $j=$i; } else { $notdone=0; } } if ($j<$#lda-1) { $num.='('.$lda[$j+1]; for ($i=$j+2;$i<=$#lda;$i++) { $num.='['.$lda[$i].'-9]'; } if ($uda[$j+1]-$lda[$j+1]>1) { $num.='|['.($lda[$j+1]+1).'-'.($uda[$j+1]-1).']\d{'. ($#lda-$j-1).'}'; } $num.='|'.$uda[$j+1]; for ($i=$j+2;$i<=$#uda;$i++) { $num.='[0-'.$uda[$i].']'; } $num.=')'; } else { if ($lda[$#lda]!=$uda[$#uda]) { $num.='['.$lda[$#lda].'-'.$uda[$#uda].']'; } } } } return '^'.$alpha.$num."\$"; } sub NUM { my $mask=mask(@_); my $num=0; map { $num++; } grep /$mask/,keys %v; return $num; } sub BIN { my ($low,$high,$lower,$upper)=@_; my $mask=mask($lower,$upper); my $num=0; map { if (($v{$_}>=$low) && ($v{$_}<=$high)) { $num++; } } grep /$mask/,keys %v; return $num; } sub SUM { my $mask=mask(@_); my $sum=0; map { $sum+=$v{$_}; } grep /$mask/,keys %v; return $sum; } sub MEAN { my $mask=mask(@_); my $sum=0; my $num=0; map { $sum+=$v{$_}; $num++; } grep /$mask/,keys %v; if ($num) { return $sum/$num; } else { return undef; } } sub STDDEV { my $mask=mask(@_); my $sum=0; my $num=0; map { $sum+=$v{$_}; $num++; } grep /$mask/,keys %v; unless ($num>1) { return undef; } my $mean=$sum/$num; $sum=0; map { $sum+=($v{$_}-$mean)**2; } grep /$mask/,keys %v; return sqrt($sum/($num-1)); } sub PROD { my $mask=mask(@_); my $prod=1; map { $prod*=$v{$_}; } grep /$mask/,keys %v; return $prod; } sub MAX { my $mask=mask(@_); my $max='-'; map { unless ($max) { $max=$v{$_}; } if (($v{$_}>$max) || ($max eq '-')) { $max=$v{$_}; } } grep /$mask/,keys %v; return $max; } sub MIN { my $mask=mask(@_); my $min='-'; map { unless ($max) { $max=$v{$_}; } if (($v{$_}<$min) || ($min eq '-')) { $min=$v{$_}; } } grep /$mask/,keys %v; return $min; } sub SUMMAX { my ($num,$lower,$upper)=@_; my $mask=mask($lower,$upper); my @inside=(); map { $inside[$#inside+1]=$v{$_}; } grep /$mask/,keys %v; @inside=sort(@inside); my $sum=0; my $i; for ($i=$#inside;(($i>$#inside-$num) && ($i>=0));$i--) { $sum+=$inside[$i]; } return $sum; } sub SUMMIN { my ($num,$lower,$upper)=@_; my $mask=mask($lower,$upper); my @inside=(); map { $inside[$#inside+1]=$v{$_}; } grep /$mask/,keys %v; @inside=sort(@inside); my $sum=0; my $i; for ($i=0;(($i<$num) && ($i<=$#inside));$i++) { $sum+=$inside[$i]; } return $sum; } sub sett { %t=(); my $pattern=''; if ($sheettype eq 'assesscalc') { $pattern='A'; } else { $pattern='[A-Z]'; } map { 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; } } } keys %f; } } } keys %f; map { if (($f{$_}) && ($_!~/template\_/)) { if ($_=~/^$pattern/) { unless ($f{$_}=~/^\!/) { $t{$_}=$c{$_}; } } else { $t{$_}=$f{$_}; $t{$_}=~s/\.\.+/\,/g; $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g; } } } keys %f; $t{'A0'}=$f{'A0'}; $t{'A0'}=~s/\.\.+/\,/g; $t{'A0'}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g; } sub calc { %v=(); &sett(); my $notfinished=1; my $depth=0; while ($notfinished) { $notfinished=0; map { my $old=$v{$_}; $v{$_}=eval($t{$_}); if ($@) { %v=(); return $@; } if ($v{$_} ne $old) { $notfinished=1; } } keys %t; $depth++; if ($depth>100) { %v=(); return 'Maximum calculation depth exceeded'; } } 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) { $cols[0]=$rl{$f{'A'.$n}}; } 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=(); if ($n) { $cols[0]=$rl{$f{'A'.$n}}; } 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 exportrowa { my $rowa=''; map { $rowa.=$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'); $rowa=~s/\_\_\_\;\_\_\_$//; return $rowa; } # ------------------------------------------- End of "Inside of the safe space" ENDDEFS $safeeval->reval($code); return $safeeval; } # ------------------------------------------------ Add or change formula values sub setformulas { my ($safeeval,@f)=@_; $safeeval->reval('%f='."('".join("','",@f)."');"); } # ------------------------------------------------ Add or change formula values sub setconstants { my ($safeeval,@c)=@_; $safeeval->reval('%c='."('".join("','",@c)."');"); } # ------------------------------------------------ Add or change formula values sub setrowlabels { my ($safeeval,@rl)=@_; $safeeval->reval('%rl='."('".join("','",@rl)."');"); } # ------------------------------------------------------- Calculate spreadsheet sub calcsheet { my $safeeval=shift; $safeeval->reval('&calc();'); } # ------------------------------------------------------------------ Get values sub getvalues { my $safeeval=shift; return $safeeval->reval('%v'); } # ---------------------------------------------------------------- Get formulas sub getformulas { my $safeeval=shift; return $safeeval->reval('%f'); } # -------------------------------------------------------------------- Set type sub settype { my ($safeeval,$type)=@_; $safeeval->reval('$sheettype="'.$type.'";'); } # -------------------------------------------------------------------- Get type sub gettype { my $safeeval=shift; return $safeeval->reval('$sheettype'); } # ------------------------------------------------------------------ Set maxrow sub setmaxrow { my ($safeeval,$row)=@_; $safeeval->reval('$maxrow='.$row.';'); } # ------------------------------------------------------------------ Get maxrow sub getmaxrow { my $safeeval=shift; return $safeeval->reval('$maxrow'); } # ---------------------------------------------------------------- Set filename sub setfilename { my ($safeeval,$fn)=@_; $safeeval->reval('$filename="'.$fn.'";'); } # ---------------------------------------------------------------- Get filename sub getfilename { my $safeeval=shift; return $safeeval->reval('$filename'); } # ----------------------------------------------------------- Get course 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 exportrow { 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; my $rowdata=''; unless ($n eq '-') { $defaultbg=((($n-1)/5)==int(($n-1)/5))?'#E0E0':'#FFFF'; } else { $defaultbg='#E0FF'; } if ((($n-1)/25)==int(($n-1)/25)) { my $what='Student'; if (&gettype($safeeval) eq 'assesscalc') { $what='Item'; } elsif (&gettype($safeeval) eq 'studentcalc') { $what='Assessment'; } $rowdata.="\n
". ''; map { $rowdata.=''; } ('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'); $rowdata.=''; } $rowdata.="\n"; my $showf=0; my $proc; my $maxred; if (&gettype($safeeval) eq 'assesscalc') { $proc='&outrowassess'; $maxred=1; } else { $proc='&outrow'; $maxred=26; } if ($n eq '-') { $proc='&templaterow'; $n=-1; } map { my $bgcolor=$defaultbg.((($showf-1)/5==int(($showf-1)/5))?'99':'DD'); my ($fm,$vl)=split(/\_\_\_eq\_\_\_/,$_); if ($showf==0) { $vl=$_; } if ($showf<=$maxred) { $bgcolor='#FFDDDD'; } if (($n==0) && ($showf<=26)) { $bgcolor='#CCCCFF'; } if (($showf>$maxred) || ((!$n) && ($showf>0))) { if ($vl eq '') { $vl='#'; } $rowdata.= ''; } else { $rowdata.=''; } $showf++; } $safeeval->reval($proc.'('.$n.')'); return $rowdata.''; } # ------------------------------------------------------------- Print out sheet 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= '
 '.$what.''.$_.'
$n'.$vl. ' '.$vl.' 
'. ''. ''; my $showf=0; map { $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); $tabledata.=&rown($safeeval,'-'); $r->print($tabledata); for ($row=0;$row<=$maxrow;$row++) { $r->print(&rown($safeeval,$row)); } $r->print('
'. $realm.'ImportCalculations
'; } else { $tabledata.=''; } $tabledata.="$_
'); } # # -------------------------------------- Read spreadsheet formulas for a course # sub readsheet { my ($safeeval,$fn)=@_; my $stype=&gettype($safeeval); my $cnum=&getcnum($safeeval); # --------- There is no filename. Look for defaults in course and global, cache unless($fn) { unless ($fn=$defaultsheets{$cnum.'_'.$stype}) { $fn=&Apache::lonnet::reply('get:'. $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'. $ENV{'course.'.$ENV{'request.course.id'}.'.num'}. ':environment:spreadsheet_default_'.&gettype($safeeval), $ENV{'course.'.$ENV{'request.course.id'}.'.home'}); unless (($fn) && ($fn!~/^error\:/)) { $fn='default_'.$stype; } $defaultsheets{$cnum.'_'.$stype}=$fn; } } # ---------------------------------------------------------- fn now has a value &setfilename($safeeval,$fn); # ------------------------------------------------------ see if sheet is cached my $fstring=''; if ($fstring=$spreadsheets{$cnum.'_'.$stype.'_'.$fn}) { &setformulas($sheetone,split(/\_\_\_\;\_\_\_/,$fstring)); } else { # ---------------------------------------------------- Not cached, need to read my %f=(); if ($fn=~/^default\_/) { my $sheetxml=''; { my $fh; if ($fh=Apache::File->new($r->dir_config('lonIncludes'). '/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:'. $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'. $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.':'.$fn, $ENV{'course.'.$ENV{'request.course.id'}.'.home'}); 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.'_'.$stype.'_'.$fn}=join('___;___',%f); &setformulas($safeeval,%f); } } # ------------------------------------------------------------ Save spreadsheet sub writesheet { my $safeeval=shift; if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { my %f=&getformulas($safeeval); my $sheetdata=''; map { $sheetdata.=&Apache::lonnet::escape($_).'='. &Apache::lonnet::escape($f{$_}).'&'; } keys %f; $sheetdata=~s/\&$//; my $reply=&Apache::lonnet::reply('put:'. $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'. $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.':'. &getfilename($safeeval).':'. $sheetdata, $ENV{'course.'.$ENV{'request.course.id'}.'.home'}); if ($reply eq 'ok') { return &Apache::lonnet::reply('put:'. $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'. $ENV{'course.'.$ENV{'request.course.id'}.'.num'}.':'. &gettype($safeeval).'_spreadsheets:'. &Apache::lonnet::escape(&getfilename($safeeval)).'='. $ENV{'user.name'}, $ENV{'course.'.$ENV{'request.course.id'}.'.home'}); } else { return $reply; } } return 'unauthorized'; } # ----------------------------------------------- Make a temp copy of the sheet sub tmpwrite { my ($safeeval,$tmpdir,$symb)=@_; my $fn=$uname.'_'.$udom.'_spreadsheet_'.$symb.'_'.&getfilename($safeeval); $fn=~s/\W/\_/g; $fn=$tmpdir.$fn.'.tmp'; my $fh; if ($fh=Apache::File->new('>'.$fn)) { print $fh join("\n",&getformulas($safeeval)); } } # ---------------------------------------------------------- Read the temp copy sub tmpread { my ($safeeval,$tmpdir,$symb,$nfield,$nform)=@_; my $fn=$uname.'_'.$udom.'_spreadsheet_'.$symb.'_'.&getfilename($safeeval); $fn=~s/\W/\_/g; $fn=$tmpdir.$fn.'.tmp'; my $fh; my %fo=(); if ($fh=Apache::File->new($fn)) { my $name; while ($name=<$fh>) { chomp($name); my $value=<$fh>; chomp($value); $fo{$name}=$value; } } if ($nfield) { $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>); } 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; } # ================================================================== Parameters # -------------------------------------------- Figure out a cascading parameter sub parmval { my ($what,$symb)=@_; unless ($symb) { return ''; } my $result=''; my ($mapname,$id,$fn)=split(/\_\_\_/,$symb); # ----------------------------------------------------- Cascading lookup scheme my $rwhat=$what; $what=~s/^parameter\_//; $what=~s/\_/\./; my $symbparm=$symb.'.'.$what; my $mapparm=$mapname.'___(all).'.$what; my $seclevel= $ENV{'request.course.id'}.'.['. $csec.'].'.$what; my $seclevelr= $ENV{'request.course.id'}.'.['. $csec.'].'.$symbparm; my $seclevelm= $ENV{'request.course.id'}.'.['. $csec.'].'.$mapparm; my $courselevel= $ENV{'request.course.id'}.'.'.$what; my $courselevelr= $ENV{'request.course.id'}.'.'.$symbparm; my $courselevelm= $ENV{'request.course.id'}.'.'.$mapparm; # ---------------------------------------------------------- fourth, check user if ($uname) { 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 return &Apache::lonnet::metadata($fn,$rwhat.'.default'); } # ---------------------------------------------- Update rows for course listing sub updatestudentrows { my $safeeval=shift; my $cid=$ENV{'request.course.id'}; my $classlst=&Apache::lonnet::reply ('dump:'.$ENV{'course.'.$cid.'.domain'}.':'. $ENV{'course.'.$cid.'.num'}.':classlist', $ENV{'course.'.$cid.'.home'}); 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 ($cname,$cdom)=split(/\:/,$name); my $csec= &Apache::lonnet::usection($cdom,$cname,$ENV{'request.course.id'}); if ($csec==-1) { $rowlabel='Data not available: '.$name. ''; } else { my %reply=&Apache::lonnet::idrget($cdom,$cname); my $reply=&Apache::lonnet::reply('get:'.$cdom.':'.$cname. ':environment:firstname&middlename&lastname&generation', &Apache::lonnet::homeserver($cname,$cdom)); $rowlabel=$csec.' '.$reply{$cname}.'
'; map { $rowlabel.=&Apache::lonnet::unescape($_).' '; } split(/\&/,$reply); } $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); } &setmaxrow($safeeval,$maxrow); &setrowlabels($safeeval,%currentlist); } else { return 'Could not access course data'; } } # ----------------------------------------------------------------- Update rows sub updaterows { my $safeeval=shift; my %bighash; # -------------------------------------------------------------------- Tie hash if (tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db', &GDBM_READER,0640)) { # --------------------------------------------------------- Get all assessments my %allkeys=(); my %allassess=(); my $stype=&gettype($safeeval); map { 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') { map { if (($_=~/^stores\_(.*)/) || ($_=~/^parameter\_(.*)/)) { my $key=$_; my $display= &Apache::lonnet::metadata($srcf,$key.'.display'); unless ($display) { $display= &Apache::lonnet::metadata($srcf,$key.'.name'); } $allkeys{$key}=$display; } } split(/\,/,&Apache::lonnet::metadata($srcf,'keys')); } } } } 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 # # -------------------- 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; } my $maxrow=0; my %existing=(); # ----------------------------------------------------------- Now obsolete rows map { if ($_=~/^A(\d+)/) { $maxrow=($1>$maxrow)?$1:$maxrow; $existing{$f{$_}}=1; unless ((defined($current{$f{$_}})) || (!$1)) { $f{$_}='!!! Obsolete'; $changed=1; } } } keys %f; # -------------------------------------------------------- New and unknown keys map { unless ($existing{$_}) { $changed=1; $maxrow++; $f{'A'.$maxrow}=$_; } } keys %current; if ($changed) { &setformulas($safeeval,%f); } &setmaxrow($safeeval,$maxrow); &setrowlabels($safeeval,%current); } else { return 'Could not access course data'; } } # ------------------------------------------------ Load data for one assessment sub rowazstudent { my $safeeval=shift; my %c=(); my %f=&getformulas($safeeval); map { if ($_=~/^A(\d+)/) { my $row=$1; unless ($f{$_}=~/^\!/) { my @assessdata=split(/\_\_\_\;\_\_\_/, &Apache::lonnet::ssi( '/adm/assesscalc',('utarget' => 'export', 'uname' => $uname, 'udom' => $udom, 'usymb' => $f{$_}))); my $index=0; map { if ($assessdata[$index]) { $c{$_.$row}=$assessdata[$index]; unless ($_ eq 'A') { $f{$_.$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); } # --------------------------------------------------- Load data for one student sub rowazclass { my $safeeval=shift; my %c=(); my %f=&getformulas($safeeval); map { if ($_=~/^A(\d+)/) { my $row=$1; unless ($f{$_}=~/^\!/) { my ($tname,$tdom)=split(/\:/,$_); my @assessdata=split(/\_\_\_\;\_\_\_/, &Apache::lonnet::ssi( '/adm/studentcalc',('utarget' => 'export', 'uname' => $tname, 'udom' => $tdom))); my $index=0; map { if ($assessdata[$index]) { $c{$_.$row}=$assessdata[$index]; unless ($_ eq 'A') { $f{$_.$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); } # ------------------------------------------------ Load data for one assessment sub rowaassess { my ($safeeval,$symb)=@_; my $uhome=&Apache::lonnet::homeserver($uname,$udom); my $namespace; unless ($namespace=$ENV{'request.course.id'}) { return ''; } # ----------------------------------------------------------- Get stored values my $answer=&Apache::lonnet::reply( "restore:$udom:$uname:". &Apache::lonnet::escape($namespace).":". &Apache::lonnet::escape($symb),$uhome); my %returnhash=(); map { my ($name,$value)=split(/\=/,$_); $returnhash{&Apache::lonnet::unescape($name)}= &Apache::lonnet::unescape($value); } split(/\&/,$answer); my $version; for ($version=1;$version<=$returnhash{'version'};$version++) { map { $returnhash{$_}=$returnhash{$version.':'.$_}; } split(/\:/,$returnhash{$version.':keys'}); } # ----------------------------- returnhash now has all stores for this resource # ---------------------------- initialize coursedata and userdata for this user %courseopt=(); %useropt=(); my $uhome=&Apache::lonnet::homeserver($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'}); if ($reply!~/^error\:/) { $courserdatas{$ENV{'request.course.id'}}=$reply; $courserdatas{$ENV{'request.course.id'}.'.last_cache'}=time; } } map { my ($name,$value)=split(/\=/,$_); $courseopt{&Apache::lonnet::unescape($name)}= &Apache::lonnet::unescape($value); } split(/\&/,$courserdatas{$ENV{'request.course.id'}}); # --------------------------------------------------- Get userdata (if present) unless ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<120) { my $reply= &Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome); if ($reply!~/^error\:/) { $userrdatas{$uname.'___'.$udom}=$reply; $userrdatas{$uname.'___'.$udom.'.last_cache'}=time; } } map { my ($name,$value)=split(/\=/,$_); $useropt{&Apache::lonnet::unescape($name)}= &Apache::lonnet::unescape($value); } split(/\&/,$userrdatas{$uname.'___'.$udom}); } # -- now courseopt, useropt initialized for this user and course (used parmval) my %c=(); my %f=&getformulas($safeeval); map { if ($_=~/^A/) { unless ($f{$_}=~/^\!/) { if ($f{$_}=~/^parameter/) { $c{$_}=&parmval($f{$_},$symb); } else { my $key=$f{$_}; $key=~s/^stores\_/resource\./; $key=~s/\_/\./; $c{$_}=$returnhash{$key}; } } } } keys %f; &setconstants($safeeval,%c); } # --------------------------------------------------------- Various form fields sub textfield { my ($title,$name,$value)=@_; return "\n

$title:
". ''; } sub hiddenfield { my ($name,$value)=@_; return "\n".''; } sub selectbox { my ($title,$name,$value,%options)=@_; my $selout="\n

$title:
".''; } # ================================================================ Main handler sub handler { my $r=shift; $uname=''; $udom=''; $csec=''; if ($r->header_only) { $r->content_type('text/html'); $r->send_http_header; return OK; } # ----------------------------------------------------- Needs to be in a course if (($ENV{'request.course.fn'}) || ($ENV{'request.state'} eq 'construct')) { # --------------------------- Get query string for limited number of parameters map { my ($name, $value) = split(/=/,$_); $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg; if (($name eq 'uname') || ($name eq 'udom') || ($name eq 'usymb') || ($name eq 'ufn')) { unless ($ENV{'form.'.$name}) { $ENV{'form.'.$name}=$value; } } } (split(/&/,$ENV{'QUERY_STRING'})); # ------------------------------------------- Nothing there? Must be login user unless ($ENV{'form.uname'}) { $uname=$ENV{'user.name'}; $udom=$ENV{'user.domain'}; } else { $uname=$ENV{'form.uname'}; $udom=$ENV{'form.udom'}; } # ----------------------------------------------------------- Change of target? my $reroute=($ENV{'form.utarget'} eq 'export'); # ------------------------------------------------------------------- Open page $r->content_type('text/html'); $r->header_out('Cache-control','no-cache'); $r->header_out('Pragma','no-cache'); $r->send_http_header; # --------------------------------------------------------------- Screen output unless ($reroute) { $r->print('LON-CAPA Spreadsheet'); $r->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(); } } 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','')); } $r->rflush(); # ---------------------------------------- Read new sheet or modified worksheet my $sheetone=initsheet(); $r->uri=~/\/(\w+)$/; &settype($sheetone,$1); if ($ENV{'form.unewfield'}) { $r->print('

Modified Workcopy

'); $ENV{'form.unewformula'}=~s/\'/\"/g; $r->print('

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

'); &setfilename($sheetone,$ENV{'form.ufn'}); &tmpread($sheetone,$r->dir_config('lonDaemons').'/tmp/', $ENV{'form.usymb'}, $ENV{'form.unewfield'},$ENV{'form.unewformula'}); } elsif ($ENV{'form.saveas'}) { &setfilename($sheetone,$ENV{'form.ufn'}); &tmpread($sheetone,$r->dir_config('lonDaemons').'/tmp/', $ENV{'form.usymb'}); } else { unless ($ENV{'form.ufn'}) { } if (&gettype($sheetone) eq 'classcalc') { # ---------------------------------- For course view: get courselist and update &updatestudentrows($sheetone); } else { # ----------------- For assessment and student: See if all import rows uptodate 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)

'); } } # ---------------------------------------------------- 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($sheetone); &setfilename($sheetone,$fname); $ENV{'form.ufn'}=$fname; my $reply=&writesheet($sheetone); unless ($reroute) { $r->print('

Saving spreadsheet: '.$reply.'

'); } if ($ENV{'form.makedefufn'}) { my $reply=&Apache::lonnet::reply('put:'. $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}.':'. $ENV{'course.'.$ENV{'request.course.id'}.'.num'}. ':environment:spreadsheet_default_'. &gettype($sheetone).'='. &Apache::lonnet::escape($fname), $ENV{'course.'.$ENV{'request.course.id'}.'.home'}); unless ($reroute) { $r->print('

Making default spreadsheet: '.$reply.'

'); } } } } # ------------------------------------------------ Write the modified worksheet &tmpwrite($sheetone,$r->dir_config('lonDaemons').'/tmp/', $ENV{'form.usymb'}); # ----------------------------------------------------- Print user, course, etc unless ($reroute) { 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',$ENV{'form.ufn'})); unless (&gettype($sheetone) eq 'classcalc') { $r->print('
User: '.$uname.'
Domain: '.$udom); } $r->print('

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

'); if ($csec) { $r->print('

Group/Section: '.$csec.'

'); } } # -------------------------------------------------------- Import and calculate if (&gettype($sheetone) eq 'assesscalc') { &rowaassess($sheetone,$ENV{'form.usymb'}); } elsif (&gettype($sheetone) eq 'studentcalc') { &rowazstudent($sheetone); } else { &rowazclass($sheetone); } my $calcoutput=&calcsheet($sheetone); unless ($reroute) { $r->print('

'.$calcoutput.'

'); } # ------------------------------------------------------- Print or export sheet unless ($reroute) { &outsheet($r,$sheetone); $r->print('
'); } else { $r->print(&exportrow($sheetone)); } # ------------------------------------------------------------------------ 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__