--- loncom/interface/Attic/lonspreadsheet.pm 2002/10/01 07:09:05 1.115 +++ loncom/interface/Attic/lonspreadsheet.pm 2002/11/08 20:18:29 1.137 @@ -1,5 +1,5 @@ # -# $Id: lonspreadsheet.pm,v 1.115 2002/10/01 07:09:05 albertel Exp $ +# $Id: lonspreadsheet.pm,v 1.137 2002/11/08 20:18:29 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -61,6 +61,10 @@ use Apache::Constants qw(:common :http); use GDBM_File; use HTML::TokeParser; use Apache::lonhtmlcommon; +use Apache::loncoursedata; +use Apache::File(); +use Spreadsheet::WriteExcel; + # # Caches for coursewide information # @@ -90,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, @@ -116,75 +120,21 @@ my $tmpdir; # ============================================================================= # ===================================== 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'); - $safeeval->share('$@'); - my $code=<<'ENDDEFS'; -# ---------------------------------------------------- Inside of the safe space - -# -# f: formulas -# t: intermediate format (variable references expanded) -# v: output values -# c: preloaded constants (A-column) -# rl: row label -# os: other spreadsheets (for student spreadsheet only) - -undef %sheet_values; -undef %t; -undef %f; -undef %c; -undef %rowlabel; -undef @os; - -$maxrow = 0; -$sheettype = ''; - -# filename/reference of the sheet -$filename = ''; - -# user data -$uname = ''; -$uhome = ''; -$udom = ''; - -# course data - -$csec = ''; -$chome= ''; -$cnum = ''; -$cdom = ''; -$cid = ''; -$coursefilename = ''; - -# symb - -$usymb = ''; - -# error messages -$errormsg = ''; - +## +## 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]/)) { @@ -193,7 +143,6 @@ sub mask { $alpha='['.$la.'-Za-'.$ua.']'; } } - if (($ld eq '*') || ($ud eq '*')) { $num='\d+'; } else { @@ -213,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]; @@ -237,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].']'; } } } @@ -246,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) @@ -570,7 +593,8 @@ compute the average of the items in the #------------------------------------------------------- sub MEAN { my $mask=mask(@_); - my $sum=0; my $num=0; + my $sum=0; + my $num=0; foreach (grep /$mask/,keys(%sheet_values)) { $sum+=$sheet_values{$_}; $num++; @@ -807,9 +831,11 @@ sub expandnamed { foreach $parameter (keys(%c)) { push @matches,$parameter if ($parameter =~ /$expression/); } - if ($#matches == 0) { + if (scalar(@matches) == 0) { + $returnvalue = 'unmatched parameter: '.$parameter; + } elsif (scalar(@matches) == 1) { $returnvalue = '$c{\''.$matches[0].'\'}'; - } elsif ($#matches > 0) { + } elsif (scalar(@matches) > 0) { # more than one match. Look for a concise one $returnvalue = "'non-unique parameter name : $expression'"; foreach (@matches) { @@ -818,7 +844,9 @@ sub expandnamed { } } } else { - $returnvalue = "'bad parameter name : $expression'"; + # There was a negative number of matches, which indicates + # something is wrong with reality. Better warn the user. + $returnvalue = 'bizzare parameter: '.$parameter; } return $returnvalue; } @@ -901,6 +929,7 @@ sub calc { while ($notfinished) { $notfinished=0; foreach (keys(%t)) { + #$errorlog .= "$_:".$t{$_}; my $old=$sheet_values{$_}; $sheet_values{$_}=eval $t{$_}; if ($@) { @@ -908,6 +937,7 @@ sub calc { return $_.': '.$@; } if ($sheet_values{$_} ne $old) { $notfinished=1; $lastcalc=$_; } + #$errorlog .= ":".$sheet_values{$_}."\n"; } $depth++; if ($depth>100) { @@ -918,439 +948,665 @@ sub calc { 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; - push(@cols,"'template_$_','$fm'".'___eq___'.$fm); + push(@cols,{ name => 'template_'.$_, + formula => $fm, + value => $fm }); } - return @cols; + return ($rowlabel,@cols); } -# -# This is actually used for the student spreadsheet, not the assessment sheet -# Do not be fooled by the name! -# sub outrowassess { # $n is the current row number - my $n=shift; + my ($sheet,$n) = @_; my @cols=(); + my $rowlabel=''; if ($n) { - my ($usy,$ufn)=split(/__&&&\__/,$f{'A'.$n}); - if ($rowlabel{$usy}) { - $cols[0]=$rowlabel{$usy}.'
'. - ''; } 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___'.$sheet_values{$_.$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]=$rowlabel{$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; - push(@cols,"'$_$n','$fm'".'___eq___'.$sheet_values{$_.$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') { - push(@exportarray,$sheet_values{$_.'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,%rowlabel)=@_; - %{$safeeval->varglob('rowlabel')}=%rowlabel; + my $sheet=shift; + my ($rowlabel) = @_; + if (! ref($rowlabel)) { + my %tmp = @_; + $rowlabel = \%tmp; + } + $sheet->{'rowlabel'}=$rowlabel; } # ------------------------------------------------------- Calculate spreadsheet - sub calcsheet { - my $safeeval=shift; - return $safeeval->reval('&calc();'); -} - -# ------------------------------------------------------------------ Get values - -sub getvalues { - my $safeeval=shift; - return $safeeval->reval('%sheet_values'); + 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 value of $f{'A'.$n} - -sub getfa { - my ($safeeval,$n)=@_; - return $safeeval->reval('$f{"A'.$n.'"}'); + my $sheet = shift; + return %{$sheet->{'safe'}->varglob('f')}; } -# -------------------------------------------------------------------- Get type - -sub gettype { - my $safeeval=shift; - return $safeeval->reval('$sheettype'); +sub geterrorlog { + my $sheet = shift; + return ${$sheet->{'safe'}->varglob('errorlog')}; } -# ------------------------------------------------------------------ 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 getcoursefilename { - my $safeeval=shift; - return $safeeval->reval('$coursefilename'); -} - -# ----------------------------------------------------------- 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 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 = shift; + # 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); + # 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=1; - my $sheettype=&gettype($safeeval); - if ($sheettype eq 'studentcalc') { - $proc='&outrowassess'; - $maxred=26; - } else { - $proc='&outrow'; - } - if ($sheettype eq 'assesscalc') { - $maxred=1; - } else { - $maxred=26; - } - if (&getfa($safeeval,$n)=~/^[\~\-]/) { $maxred=1; } +sub get_row { + my ($sheet,$n) = @_; + my ($rowlabel,@rowdata); 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.=''; - if ($ENV{'request.role'} =~ /^st\./) { - $rowdata.=$vl; - } else { - $rowdata.=''. - $vl.''; - } - $rowdata.=''; - } else { - $rowdata.=' '.$vl.' '; - } - } else { - $rowdata.=',"'.$vl.'"'; - } - $showf++; - } # End of foreach($safeval...) - if ($ENV{'form.showall'} || ($dataflag)) { - return $rowdata.($ENV{'form.showcsv'}?'':''); + ($rowlabel,@rowdata) = &templaterow($sheet); + } elsif ($sheet->{'sheettype'} eq 'studentcalc') { + ($rowlabel,@rowdata) = &outrowassess($sheet,$n); } else { - return ''; + ($rowlabel,@rowdata) = &outrow($sheet,$n); } + return ($rowlabel,@rowdata); } -# ------------------------------------------------------------- Print out sheet - -sub outsheet { - my ($r,$safeeval,$sheetdata)=@_; - my $maxred = 26; # The maximum number of cells to show as - # red (uneditable) - # To make student sheets uneditable could we - # set $maxred = 52? - # - my $realm='Course'; # 'assessment', 'user', or 'course' sheet - if ($sheetdata->{'sheettype'} eq 'assesscalc') { - $maxred=1; - $realm='Assessment'; - } elsif ($sheetdata->{'sheettype'} eq 'studentcalc') { - $maxred=26; - $realm='User'; - } - # - # Column label - my $tabledata; - if ($ENV{'form.showcsv'}) { - $tabledata='
';
-    } else { 
-        $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.='";
-        }
-        $tabledata.=''.&rown($safeeval,'-').&rown($safeeval,0);
-    }
-    $r->print($tabledata);
+########################################################################
+########################################################################
+sub sort_indicies {
+    my $sheet = shift;
     #
-    # Prepare to output rows
-    my $row;
-    my $maxrow=&getmaxrow($safeeval);
+    # Sort the rows in some manner
     #
     my @sortby=();
     my @sortidx=();
-    for ($row=1;$row<=$maxrow;$row++) {
-        push (@sortby, $safeeval->reval('$f{"A'.$row.'"}'));
-        push (@sortidx, $row-1);
+    for (my $row=1;$row<=$sheet->{'maxrow'};$row++) {
+        push (@sortby, $sheet->{'safe'}->reval('$f{"A'.$row.'"}'));
+        push (@sortidx, $row);
     }
     @sortidx=sort { lc($sortby[$a]) cmp lc($sortby[$b]); } @sortidx;
-    #
-    # Determine the type of child spreadsheets
-    my $what='Student';
-    if ($sheetdata->{'sheettype'} eq 'assesscalc') {
-        $what='Item';
-    } elsif ($sheetdata->{'sheettype'} eq 'studentcalc') {
-        $what='Assessment';
+    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 = '#';
+    }
+    $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";
+
'. - ''.$realm.'ImportCalculations
'; - } else { - $tabledata.=''; - } - $tabledata.="$_
+ + + + + +END + my $label_num = 0; + foreach (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')){ + if ($label_num<$num_uneditable) { + $tabledata.='"; + $label_num++; } + $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 .= ''; + } + $row_html.= "\n"; + $r->print($row_html); + #################################### + # Print out summary/export row + #################################### + my ($rowlabel,@rowdata) = &get_row($sheet,'0'); + my $rowcount = 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 .= '
'.&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 $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'})) { + my $rows_output=0; + foreach my $rownum (@Rows) { + my ($rowlabel,@rowdata) = &get_row($sheet,$rownum); + next if ($rowlabel =~ /^\s*$/); + # + 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(''); - $r->print('
 '.$what.''. + $r->print(''. + ''. + '\n"); } - $n++; - $r->print($thisrow); + $rows_output++; + $r->print($row_html); } } - $r->print($ENV{'form.showcsv'}?'':'
 '.$row_type.''. join('', (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. 'abcdefghijklmnopqrstuvwxyz'))). "
'); + # + $r->print('
'); + # + # Debugging code (be sure to uncomment errorlog code in safe space): + # + # $r->print("\n
");
+    # $r->print(&geterrorlog($sheet));
+    # $r->print("\n
"); + return 1; } -# -# ----------------------------------------------- Read list of available sheets -# +############################################ +## csv output routines ## +############################################ +sub outsheet_csv { + my ($sheet,$r) = @_; + my $csvdata = ''; + my @Values; + #################################### + # Prepare to output rows + #################################### + my @Rows = &sort_indicies($sheet); + # + # Loop through the rows and output them one at a time + my $rows_output=0; + foreach my $rownum (@Rows) { + my ($rowlabel,@rowdata) = &get_row($sheet,$rownum); + next if ($rowlabel =~ /^\s*$/); + push (@Values,&format_csv_rowlabel($rowlabel)); + foreach my $cell (@rowdata) { + push (@Values,'"'.$cell->{'value'}.'"'); + } + $csvdata.= join(',',@Values)."\n"; + @Values = (); + } + # + # Write the CSV data to a file and serve up a link + # + my $filename = '/prtspool/'. + $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'. + time.'_'.rand(1000000000).'.csv'; + my $file; + unless ($file = Apache::File->new('>'.'/home/httpd'.$filename)) { + $r->log_error("Couldn't open $filename for output $!"); + $r->print("Problems occured in writing the csv file. ". + "This error has been logged. ". + "Please alert your LON-CAPA administrator."); + $r->print("
\n".$csvdata."
\n"); + return 0; + } + print $file $csvdata; + close($file); + $r->print('

'. + 'Your CSV spreadsheet.'."\n"); + # + return 1; +} + +############################################ +## Excel output routines ## +############################################ +sub outsheet_recursive_excel { + my ($sheet,$r) = @_; + my $c = $r->connection; + return undef if ($sheet->{'sheettype'} ne 'classcalc'); + my ($workbook,$filename) = &create_excel_spreadsheet($sheet,$r); + return undef if (! defined($workbook)); + # + # Create main worksheet + my $main_worksheet = $workbook->addworksheet('main'); + # + # Figure out who the students are + my %f=&getformulas($sheet); + my $count = 0; + $r->print(< +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 (keys(%f)) { + next if ($_!~/^A(\d+)/ || $1 == 0 || ($f{$_}=~/^[!~-]/)); + $count++; + my ($sname,$sdom) = split(':',$f{$_}); + my $student_excel_worksheet=$workbook->addworksheet($sname.'@'.$sdom); + # Create a new spreadsheet + my $studentsheet = &makenewsheet($sname,$sdom,'studentcalc',undef); + # Read in the spreadsheet definition + &update_student_sheet($studentsheet); + # 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(); + } + 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; + } + # + # The spreadsheet stores temporary data in files, then put them + # together. If needed we should be able to disable this (memory only). + # The temporary directory must be specified before calling 'addworksheet'. + # File::Temp is used to determine the temporary directory. + $workbook->set_tempdir('/home/httpd/perl/tmp'); + # + # Determine the name to give the worksheet + return ($workbook,$filename); +} + +sub export_sheet_as_excel { + my $sheet = shift; + my $worksheet = shift; + #################################### + # 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*$/); + my $cols_output = 0; + my $label = &format_excel_rowlabel($rowlabel); + $worksheet->write($rows_output,$cols_output++,$label); + if (ref($label)) { + $cols_output = (scalar(@$label)); + } + foreach my $cell (@rowdata) { + $worksheet->write($rows_output,$cols_output++, + $cell->{'value'}); + } + $rows_output++; + } + return; +} + +############################################ +## XML output routines ## +############################################ +sub outsheet_xml { + my ($sheet,$r) = @_; + ## Someday XML + ## Will be rendered for the user + ## But not on this day +} + +## +## Outsheet - calls other outsheet_* functions +## +sub outsheet { + my ($r,$sheet)=@_; + if (! exists($ENV{'form.output'})) { + $ENV{'form.output'} = 'HTML'; + } + if (lc($ENV{'form.output'}) eq 'csv') { + &outsheet_csv($sheet,$r); + } elsif (lc($ENV{'form.output'}) eq 'excel') { + &outsheet_excel($sheet,$r); + } elsif (lc($ENV{'form.output'}) eq 'recursive excel') { + &outsheet_recursive_excel($sheet,$r); +# } elsif (lc($ENV{'form.output'}) eq 'xml' ) { +# &outsheet_xml($sheet,$r); + } else { + &outsheet_html($sheet,$r); + } +} + +######################################################################## +######################################################################## sub othersheets { - 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); @@ -1361,7 +1617,6 @@ sub othersheets { return @alternatives; } - # # -------------------------------------- Parse a spreadsheet # @@ -1394,12 +1649,12 @@ sub parse_sheet { # -------------------------------------- Read spreadsheet formulas for a course # sub readsheet { - my ($safeeval,$sheetdata,$fn)=@_; + my ($sheet,$fn)=@_; # - my $stype = $sheetdata->{'sheettype'}; - my $cnum = $sheetdata->{'cnum'}; - my $cdom = $sheetdata->{'cdom'}; - my $chome = $sheetdata->{'chome'}; + 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 @@ -1420,11 +1675,13 @@ sub readsheet { } } # $fn now has a value - &setfilename($safeeval,$fn); + $sheet->{'filename'} = $fn; # see if sheet is cached my $fstring=''; if ($fstring=$spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}) { - &setformulas($safeeval,split(/\_\_\_\;\_\_\_/,$fstring)); + my %tmp = split(/___;___/,$fstring); + $sheet->{'f'} = \%tmp; + &setformulas($sheet); } else { # Not cached, need to read my %f=(); @@ -1459,74 +1716,76 @@ sub readsheet { } # Cache and set $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f); - &setformulas($safeeval,%f); + $sheet->{'f'}=\%f; + &setformulas($sheet); } } # -------------------------------------------------------- Make new spreadsheet sub makenewsheet { my ($uname,$udom,$stype,$usymb)=@_; - my %sheetdata=(); - $sheetdata{'uname'} = $uname; - $sheetdata{'udom'} = $udom; - $sheetdata{'sheettype'} = $stype; - $sheetdata{'usymb'} = $usymb; - $sheetdata{'cid'} = $ENV{'request.course.id'}; - $sheetdata{'csec'} = $Section{$uname.':'.$udom}; - $sheetdata{'coursefilename'} = $ENV{'request.course.fn'}; - $sheetdata{'cnum'} = $ENV{'course.'.$ENV{'request.course.id'}.'.num'}; - $sheetdata{'cdom'} = $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; - $sheetdata{'chome'} = $ENV{'course.'.$ENV{'request.course.id'}.'.home'}; - $sheetdata{'uhome'} = &Apache::lonnet::homeserver($uname,$udom); - - my $safeeval=initsheet($stype); + my $sheet={}; + $sheet->{'uname'} = $uname; + $sheet->{'udom'} = $udom; + $sheet->{'sheettype'} = $stype; + $sheet->{'usymb'} = $usymb; + $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 (keys(%sheetdata)) { - $initstring.= qq{\$$_="$sheetdata{$_}";}; + foreach (qw/uname udom sheettype usymb cid csec coursefilename + cnum cdom chome uhome/) { + $initstring.= qq{\$$_="$sheet->{$_}";}; } - $safeeval->reval($initstring); - return $safeeval,\%sheetdata; + $sheet->{'safe'}->reval($initstring); + return $sheet; } # ------------------------------------------------------------ Save spreadsheet sub writesheet { - my ($safeeval,$makedef)=@_; - my $cid=&getcid($safeeval); + my ($sheet,$makedef)=@_; + my $cid=$sheet->{'cid'}; 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); + 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 - my $sheetdata=''; foreach (keys(%f)) { - unless ($f{$_} eq 'import') { - $sheetdata.=&Apache::lonnet::escape($_).'='. - &Apache::lonnet::escape($f{$_}).'&'; - } + delete($f{$_}) if ($f{$_} eq 'import'); } - $sheetdata=~s/\&$//; - my $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'.$fn.':'. - $sheetdata,$chome); + my $reply = &Apache::lonnet::put($fn,\%f,$cdom,$cnum); if ($reply eq 'ok') { - $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'. - $stype.'_spreadsheets:'. - &Apache::lonnet::escape($fn). - '='.$ENV{'user.name'}.'@'. - $ENV{'user.domain'}, - $chome); + $reply = &Apache::lonnet::put($stype.'_spreadsheets', + {$fn => $ENV{'user.name'}.'@'.$ENV{'user.domain'}}, + $cdom,$cnum); if ($reply eq 'ok') { if ($makedef) { - return &Apache::lonnet::reply('put:'.$cdom.':'.$cnum. - ':environment:'. - 'spreadsheet_default_'. - $stype.'='. - &Apache::lonnet::escape($fn), - $chome); + return &Apache::lonnet::put('environment', + {'spreadsheet_default_'.$stype => $fn }, + $cdom,$cnum); } return $reply; } @@ -1541,24 +1800,24 @@ sub writesheet { # "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; @@ -1579,7 +1838,7 @@ sub tmpread { } } 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}; } @@ -1594,7 +1853,8 @@ sub tmpread { } else { if ($nfield) { $fo{$nfield}=$nform; } } - &setformulas($safeeval,%fo); + $sheet->{'f'}=\%fo; + &setformulas($sheet); } ################################################## @@ -1606,7 +1866,7 @@ sub tmpread { Determine the value of a parameter. -Inputs: $what, the parameter needed, $safeeval, the safe space +Inputs: $what, the parameter needed, $sheet, the safe space Returns: The value of a parameter, or '' if none. @@ -1622,14 +1882,14 @@ this user and course. ################################################## ################################################## sub parmval { - my ($what,$safeeval,$sheetdata)=@_; - my $symb = $sheetdata->{'usymb'}; + my ($what,$sheet)=@_; + my $symb = $sheet->{'usymb'}; unless ($symb) { return ''; } # - my $cid = $sheetdata->{'cid'}; - my $csec = $sheetdata->{'csec'}; - my $uname = $sheetdata->{'uname'}; - my $udom = $sheetdata->{'udom'}; + my $cid = $sheet->{'cid'}; + my $csec = $sheet->{'csec'}; + my $uname = $sheet->{'uname'}; + my $udom = $sheet->{'udom'}; my $result=''; # my ($mapname,$id,$fn)=split(/\_\_\_/,$symb); @@ -1672,94 +1932,122 @@ sub parmval { return &Apache::lonnet::metadata($fn,$rwhat.'.default'); } + +################################################################## +## Row label formatting routines ## +################################################################## +sub format_html_rowlabel { + my $rowlabel = shift; + return '' if ($rowlabel eq ''); + my ($type,$labeldata) = split(':',$rowlabel,2); + my $result = ''; + if ($type eq 'symb') { + my ($symb,$uname,$udom,$title) = split(':',$labeldata); + $symb = &Apache::lonnet::unescape($symb); + $result = ''.$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,$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,$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,$sheetdata) = @_; - my $cnum =$sheetdata->{'cnum'}; - my $cdom =$sheetdata->{'cdom'}; - my $cid =$sheetdata->{'cid'}; - my $chome =$sheetdata->{'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; - my @tmp = &Apache::lonnet::dump('classlist',$cdom,$cnum); - if ($tmp[0] !~ /^error/) { - %classlist = @tmp; - } else { - return 'Could not access course data'; - } - undef @tmp; + my $classlist = &Apache::loncoursedata::get_classlist(); + if (! defined($classlist)) { + return 'Could not access course classlist'; + } # my %currentlist=(); - my $now=time; - foreach my $student (keys(%classlist)) { - my ($end,$start)=split(/\:/,$classlist{$student}); - my $active=1; - $active = 0 if (($end) && ($now>$end)); - $active = 1 if ($ENV{'form.Status'} eq 'Any'); - $active = !$active if ($ENV{'form.Status'} eq 'Expired'); - if ($active) { - my $rowlabel=''; - my ($studentName,$studentDomain)=split(/\:/,$student); - my $studentSection=&Apache::lonnet::usection($studentDomain, - $studentName,$cid); - $Section{$studentName.':'.$studentDomain} = $studentSection; -# if ($studentSection==-1) { -# unless ($ENV{'form.showcsv'}) { -# $rowlabel='Data not available: '. -# $studentName.''; -# } else { -# $rowlabel='ERROR","'.$studentName. -# '","Data not available","","","'; -# } -# } else { - my %reply=&Apache::lonnet::idrget($studentDomain,$studentName); - my %studentInformation=&Apache::lonnet::get - ('environment', - ['lastname','generation','firstname','middlename','id'], - $studentDomain,$studentName); - if (! $ENV{'form.showcsv'}) { - $rowlabel=''. - $studentSection.' '; - foreach ('id','firstname','middlename', - 'lastname','generation'){ - $rowlabel.=$studentInformation{$_}." "; - } - $rowlabel.=''; - } else { - $rowlabel= '"'.join('","', - ($studentSection, - $studentInformation{'id'}, - $studentInformation{'firstname'}, - $studentInformation{'middlename'}, - $studentInformation{'lastname'}, - $studentInformation{'generation'}) - ).'"'; - } - # } - $currentlist{$student}=$rowlabel; - } # end of if ($active) - } # end of foreach my $student (keys(%classlist)) + foreach my $student (keys(%$classlist)) { + my ($studentDomain,$studentName,$end,$start,$id,$studentSection, + $fullname,$status) = @{$classlist->{$student}}; + 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($safeeval); + my %f=&getformulas($sheet); my $changed=0; # - my $maxrow=0; + $sheet->{'maxrow'}=0; my %existing=(); # # Now obsolete rows foreach (keys(%f)) { if ($_=~/^A(\d+)/) { - $maxrow=($1>$maxrow)?$1:$maxrow; + if ($1 > $sheet->{'maxrow'}) { + $sheet->{'maxrow'}= $1; + } $existing{$f{$_}}=1; unless ((defined($currentlist{$f{$_}})) || (!$1) || - ($f{$_}=~/^(\~\~\~|\-\-\-)/)) { + ($f{$_}=~/^(~~~|---)/)) { $f{$_}='!!! Obsolete'; $changed=1; } @@ -1767,79 +2055,115 @@ sub updateclasssheet { } # # New and unknown keys - foreach (sort keys(%currentlist)) { - unless ($existing{$_}) { + foreach my $student (sort keys(%currentlist)) { + unless ($existing{$student}) { $changed=1; - $maxrow++; - $f{'A'.$maxrow}=$_; + $sheet->{'maxrow'}++; + $f{'A'.$sheet->{'maxrow'}}=$student; } } - if ($changed) { &setformulas($safeeval,%f); } + if ($changed) { + $sheet->{'f'} = \%f; + &setformulas($sheet,%f); + } # - &setmaxrow($safeeval,$maxrow); - &setrowlabels($safeeval,%currentlist); + &setrowlabels($sheet,\%currentlist); } # ----------------------------------- Update rows for student and assess sheets -sub updatestudentassesssheet { - my ($safeeval,$sheetdata) = @_; - my %bighash; - my $stype=$sheetdata->{'sheettype'}; - my $uname=$sheetdata->{'uname'}; - my $udom =$sheetdata->{'udom'}; - my %current=(); - if ($updatedata - {$ENV{'request.course.fn'}.'_'.$stype.'_'.$uname.'_'.$udom}) { - %current=split(/\_\_\_\;\_\_\_/, - $updatedata{$ENV{'request.course.fn'}. - '_'.$stype.'_'.$uname.'_'.$udom}); +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(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db', + tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db', &GDBM_READER(),0640); - if (! tied(%bighash)) { + if (! tied(%course_db)) { return 'Could not access course data'; } - # 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 (($uname ne $ENV{'user.name'}) || ($udom ne $ENV{'user.domain'})){ - $adduserstr='&uname='.$uname.'&udom='.$udom; - } - my %allassess = - ('_feedback' =>'Feedback', - '_evaluation' =>'Evaluation', - '_tutoring' =>'Tutoring', - '_discussion' =>'Discussion' - ); - while (($_,undef) = each(%bighash)) { - next if ($_!~/^src\_(\d+)\.(\d+)$/); - my $mapid=$1; - my $resid=$2; - my $id=$mapid.'.'.$resid; - my $srcf=$bighash{$_}; + # + my %assesslist; + foreach ('Feedback','Evaluation','Tutoring','Discussion') { + my $symb = '_'.lc($_); + $assesslist{$symb} = join(':',('symb',$symb,$uname,$udom,$_)); + } + # + 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($bighash{'map_id_'.$mapid}). + &Apache::lonnet::declutter($course_db{'map_id_'.$mapid}). '___'.$resid.'___'.&Apache::lonnet::declutter($srcf); - $allassess{$symb}= - ''. - $bighash{'title_'.$id}.''; - next if ($stype ne 'assesscalc'); - foreach my $key (split(/\,/, - &Apache::lonnet::metadata($srcf,'keys') - )) { + $assesslist{$symb}='symb:'.&Apache::lonnet::escape($symb).':' + .$uname.':'.$udom.':'.$course_db{'title_'.$id}; + } + } + untie(%course_db); + # Store away the data + $sheet->{'rowlabel'} = \%assesslist; + $rowlabel_cache{$identifier}=join('___;___',%{$sheet->{'rowlabel'}}); + } + +} + +sub get_assess_rowlabels { + my ($sheet) = @_; + # + my %course_db; + # + my $stype = $sheet->{'sheettype'}; + my $uname = $sheet->{'uname'}; + my $udom = $sheet->{'udom'}; + my $usymb = $sheet->{'usymb'}; + # + $sheet->{'rowlabel'} = {}; + my $identifier =$sheet->{'coursefilename'}.'_'.$stype.'_'.$usymb; + # + if ($rowlabel_cache{$identifier}) { + %{$sheet->{'rowlabel'}}=split(/___;___/,$rowlabel_cache{$identifier}); + } else { + # Get the data and store it in the cache + # Tie hash + tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db', + &GDBM_READER(),0640); + if (! tied(%course_db)) { + return 'Could not access course data'; + } + # + my %parameter_labels= + ('timestamp' => + 'parameter:Timestamp of Last Transaction
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'); @@ -1848,73 +2172,73 @@ sub updatestudentassesssheet { &Apache::lonnet::metadata($srcf,$key.'.name'); } $display.='
'.$key; - $allkeys{$key}=$display; + $parameter_labels{$key}='parameter:'.$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; - } - $updatedata{$ENV{'request.course.fn'}.'_'.$stype.'_'.$uname.'_'.$udom}= - join('___;___',%current); - # Get current from cache + } + untie(%course_db); + # Store away the results + $sheet->{'rowlabel'} = \%parameter_labels; + $rowlabel_cache{$identifier}=join('___;___',%{$sheet->{'rowlabel'}}); } - # Find discrepancies between the course row table and this - # - my %f=&getformulas($safeeval); + +} + +sub updatestudentassesssheet { + my $sheet = shift; + if ($sheet->{'sheettype'} eq 'studentcalc') { + &get_student_rowlabels($sheet); + } else { + &get_assess_rowlabels($sheet); + } + # Determine if any of the information has changed + my %f=&getformulas($sheet); my $changed=0; - my $maxrow=0; + $sheet->{'maxrow'} = 0; my %existing=(); # Now obsolete rows - foreach (keys(%f)) { - next if ($_!~/^A(\d+)/); - $maxrow=($1>$maxrow)?$1:$maxrow; - my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{$_}); + while (my ($cell, $formula) = each (%f)) { + next if ($cell !~ /^A(\d+)/); + $sheet->{'maxrow'} = $1 if ($1 > $sheet->{'maxrow'}); + my ($usy,$ufn)=split(/__&&&\__/,$formula); $existing{$usy}=1; - unless ((defined($current{$usy})) || (!$1) || - ($f{$_}=~/^(\~\~\~|\-\-\-)/)){ + unless ((exists($sheet->{'rowlabel'}->{$usy}) && + (defined($sheet->{'rowlabel'}->{$usy})) || (!$1) || + ($formula =~ /^(~~~|---)/) )) { $f{$_}='!!! Obsolete'; $changed=1; } elsif ($ufn) { - $current{$usy} - =~s/assesscalc\?usymb\=/assesscalc\?ufn\=$ufn\&usymb\=/; + # I do not think this works any more + $sheet->{'rowlabel'}->{$usy} + =~s/assesscalc\?usymb\=/assesscalc\?ufn\=$ufn&\usymb\=/; } } # New and unknown keys - foreach (keys(%current)) { + foreach (keys(%{$sheet->{'rowlabel'}})) { unless ($existing{$_}) { $changed=1; - $maxrow++; - $f{'A'.$maxrow}=$_; + $sheet->{'maxrow'}++; + $f{'A'.$sheet->{'maxrow'}}=$_; } } - if ($changed) { &setformulas($safeeval,%f); } - &setmaxrow($safeeval,$maxrow); - &setrowlabels($safeeval,%current); - # - undef %current; - undef %existing; + if ($changed) { + $sheet->{'f'} = \%f; + &setformulas($sheet); + } } # ------------------------------------------------ Load data for one assessment -sub loadstudent { - my ($safeeval,$sheetdata)=@_; +sub loadstudent{ + my ($sheet)=@_; my %c=(); - my %f=&getformulas($safeeval); - $cachedassess=$sheetdata->{'uname'}.':'.$sheetdata->{'udom'}; + my %f=&getformulas($sheet); + $cachedassess=$sheet->{'uname'}.':'.$sheet->{'udom'}; # Get ALL the student preformance data - my @tmp = &Apache::lonnet::dump($sheetdata->{'cid'}, - $sheetdata->{'udom'}, - $sheetdata->{'uname'}, + my @tmp = &Apache::lonnet::dump($sheet->{'cid'}, + $sheet->{'udom'}, + $sheet->{'uname'}, undef); if ($tmp[0] !~ /^error:/) { %cachedstores = @tmp; @@ -1922,18 +2246,18 @@ sub loadstudent { undef @tmp; # my @assessdata=(); - foreach (keys(%f)) { - next if ($_!~/^A(\d+)/); + while (my ($cell,$value) = each (%f)) { + next if ($cell !~ /^A(\d+)/); my $row=$1; - next if (($f{$_}=~/^[\!\~\-]/) || ($row==0)); - my ($usy,$ufn)=split(/__&&&\__/,$f{$_}); - @assessdata=&exportsheet($sheetdata->{'uname'}, - $sheetdata->{'udom'}, + next if (($value =~ /^[!~-]/) || ($row==0)); + my ($usy,$ufn)=split(/__&&&\__/,$value); + @assessdata=&exportsheet($sheet,$sheet->{'uname'}, + $sheet->{'udom'}, '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]) { + if (defined($assessdata[$index])) { my $col=$_; if ($assessdata[$index]=~/\D/) { $c{$col.$row}="'".$assessdata[$index]."'"; @@ -1949,16 +2273,19 @@ sub loadstudent { } $cachedassess=''; undef %cachedstores; - &setformulas($safeeval,%f); - &setconstants($safeeval,%c); + $sheet->{'f'} = \%f; + &setformulas($sheet); + &setconstants($sheet,\%c); } # --------------------------------------------------- Load data for one student # sub loadcourse { - my ($safeeval,$sheetdata,$r)=@_; + my ($sheet,$r)=@_; + # my %c=(); - my %f=&getformulas($safeeval); + my %f=&getformulas($sheet); + # my $total=0; foreach (keys(%f)) { if ($_=~/^A(\d+)/) { @@ -1983,8 +2310,8 @@ ENDPOP next if ($_!~/^A(\d+)/); my $row=$1; next if (($f{$_}=~/^[\!\~\-]/) || ($row==0)); - my @studentdata=&exportsheet(split(/\:/,$f{$_}), - 'studentcalc'); + my ($sname,$sdom) = split(':',$f{$_}); + my @studentdata=&exportsheet($sheet,$sname,$sdom,'studentcalc'); undef %userrdatas; $now++; $r->print(''); $r->rflush(); } @@ -2018,16 +2346,16 @@ ENDPOP # ------------------------------------------------ Load data for one assessment # sub loadassessment { - my ($safeeval,$sheetdata)=@_; + my ($sheet)=@_; - my $uhome = $sheetdata->{'uhome'}; - my $uname = $sheetdata->{'uname'}; - my $udom = $sheetdata->{'udom'}; - my $symb = $sheetdata->{'usymb'}; - my $cid = $sheetdata->{'cid'}; - my $cnum = $sheetdata->{'cnum'}; - my $cdom = $sheetdata->{'cdom'}; - my $chome = $sheetdata->{'chome'}; + 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 ''; } @@ -2062,10 +2390,9 @@ sub loadassessment { # 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; } @@ -2074,38 +2401,28 @@ sub loadassessment { 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; - $courserdatas{$cid.'.last_cache'}=time; - } + 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); + while (my ($name,$value) = each(%{$courserdatas{$cid}})) { + $courseopt{$userprefix.$name}=$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; - } + 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; + $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(%{$userrdatas{$cid}})) { + $useropt{$userprefix.$name}=$value; } } # now courseopt, useropt initialized for this user and course @@ -2123,29 +2440,29 @@ sub loadassessment { # my %c=(); if (tie(%parmhash,'GDBM_File', - &getcoursefilename($safeeval).'_parms.db',&GDBM_READER(),0640)) { - my %f=&getformulas($safeeval); - foreach (keys(%f)) { - next if ($_!~/^A/); - next if ($f{$_}=~/^[\!\~\-]/); - if ($f{$_}=~/^parameter/) { - if ($thisassess{$f{$_}}) { - my $val=&parmval($f{$_},$safeeval,$sheetdata); - $c{$_}=$val; - $c{$f{$_}}=$val; + $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{$_}; + my $key=$f{$cell}; my $ckey=$key; $key=~s/^stores\_/resource\./; $key=~s/\_/\./g; - $c{$_}=$returnhash{$key}; + $c{$cell}=$returnhash{$key}; $c{$ckey}=$returnhash{$key}; } } untie(%parmhash); } - &setconstants($safeeval,%c); + &setconstants($sheet,\%c); } # --------------------------------------------------------- Various form fields @@ -2178,12 +2495,11 @@ sub selectbox { # sub updatesheet { - my ($safeeval,$sheetdata)=@_; - my $stype=$sheetdata->{'sheettype'}; - if ($stype eq 'classcalc') { - return &updateclasssheet($safeeval,$sheetdata); + my ($sheet)=@_; + if ($sheet->{'sheettype'} eq 'classcalc') { + return &updateclasssheet($sheet); } else { - return &updatestudentassesssheet($safeeval,$sheetdata); + return &updatestudentassesssheet($sheet); } } @@ -2193,14 +2509,14 @@ sub updatesheet { # sub loadrows { - my ($safeeval,$sheetdata,$r)=@_; - my $stype=$sheetdata->{'sheettype'}; + my ($sheet,$r)=@_; + my $stype=$sheet->{'sheettype'}; if ($stype eq 'classcalc') { - &loadcourse($safeeval,$sheetdata,$r); + &loadcourse($sheet,$r); } elsif ($stype eq 'studentcalc') { - &loadstudent($safeeval,$sheetdata); + &loadstudent($sheet); } else { - &loadassessment($safeeval,$sheetdata); + &loadassessment($sheet); } } @@ -2218,7 +2534,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) || @@ -2237,14 +2553,17 @@ 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 ($sheet,$uname,$udom,$stype,$usymb,$fn)=@_; + $uname = $uname || $sheet->{'uname'}; + $udom = $udom || $sheet->{'udom'}; + $stype = $stype || $sheet->{'sheettype'}; my @exportarr=(); - if (($usymb=~/^\_(\w+)/) && (!$fn)) { + if (defined($usymb) && ($usymb=~/^\_(\w+)/) && + (!defined($fn) || $fn eq '')) { $fn='default_'.$1; } # @@ -2253,18 +2572,18 @@ sub exportsheet { my $key=$uname.':'.$udom.':'.$stype.':'.$usymb; my $found=''; if ($oldsheets{$key}) { - foreach (split(/\_\_\_\&\_\_\_/,$oldsheets{$key})) { - my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_); + foreach (split(/___&\___/,$oldsheets{$key})) { + my ($name,$value)=split(/___=___/,$_); if ($name eq $fn) { $found=$value; } } } unless ($found) { - &cachedssheets($uname,$udom,&Apache::lonnet::homeserver($uname,$udom)); + &cachedssheets($sheet,$uname,$udom); if ($oldsheets{$key}) { - foreach (split(/\_\_\_\&\_\_\_/,$oldsheets{$key})) { - my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_); + foreach (split(/___&\___/,$oldsheets{$key})) { + my ($name,$value)=split(/___=___/,$_); if ($name eq $fn) { $found=$value; } @@ -2283,73 +2602,68 @@ sub exportsheet { # # Return what was cached # - @exportarr=split(/\_\_\_\;\_\_\_/,$found); - } else { - # - # Not cached - # - my ($thissheet,$sheetdata)=&makenewsheet($uname,$udom,$stype,$usymb); - &readsheet($thissheet,$sheetdata,$fn); - &updatesheet($thissheet,$sheetdata); - &loadrows($thissheet,$sheetdata); - &calcsheet($thissheet,$sheetdata); - @exportarr=&exportdata($thissheet,$sheetdata); - # - # 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:'.$sheetdata->{'udom'}.':'. - $sheetdata->{'uname'}. - ':nohist_calculatedsheets_'. - $ENV{'request.course.id'}.':'. - &Apache::lonnet::escape($key), - $sheetdata->{'uhome'}); - } - my %currentlystored=(); - unless ($current=~/^error\:/) { - foreach (split(/___&\___/,&Apache::lonnet::unescape($current))) { - my ($name,$value)=split(/___=___/,$_); - $currentlystored{$name}=$value; - } + @exportarr=split(/___;___/,$found); + return @exportarr; + } + # + # Not cached + # + my ($newsheet)=&makenewsheet($uname,$udom,$stype,$usymb); + &readsheet($newsheet,$fn); + &updatesheet($newsheet); + &loadrows($newsheet); + &calcsheet($newsheet); + @exportarr=&exportdata($newsheet); + ## + ## Store now + ## + # + # load in the old value + # + my %currentlystored=(); + if ($stype eq 'studentcalc') { + my @tmp = &Apache::lonnet::get('nohist_calculatedsheets', + [$key], + $sheet->{'cdom'},$sheet->{'cnum'}); + if ($tmp[0]!~/^error/) { + %currentlystored = @tmp; } - $currentlystored{$fn}=join('___;___',@exportarr); - # - my $newstore=''; - foreach (keys(%currentlystored)) { - if ($newstore) { $newstore.='___&___'; } - $newstore.=$_.'___=___'.$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:'. - $sheetdata->{'udom'}.':'. - $sheetdata->{'uname'}. - ':nohist_calculatedsheets_'. - $ENV{'request.course.id'}.':'. - &Apache::lonnet::escape($key).'='. - &Apache::lonnet::escape($newstore).'&'. - &Apache::lonnet::escape($key).'.time='.$now, - $sheetdata->{'uhome'}); + } else { + my @tmp = &Apache::lonnet::get('nohist_calculatedsheets_'. + $sheet->{'cid'},[$key], + $sheet->{'udom'},$sheet->{'uname'}); + if ($tmp[0]!~/^error/) { + %currentlystored = @tmp; } } + # + # Add the new line + # + $currentlystored{$fn}=join('___;___',@exportarr); + # + # Stick everything back together + # + my $newstore=''; + foreach (keys(%currentlystored)) { + if ($newstore) { $newstore.='___&___'; } + $newstore.=$_.'___=___'.$currentlystored{$_}; + } + my $now=time; + # + # Store away the new value + # + if ($stype eq 'studentcalc') { + &Apache::lonnet::put('nohist_calculatedsheets', + { $key => $newstore, + $key.time => $now }, + $sheet->{'cdom'},$sheet->{'cnum'}); + } else { + &Apache::lonnet::put('nohist_calculatedsheets_'.$sheet->{'cid'}, + { $key => $newstore, + $key.time => $now }, + $sheet->{'udom'}, + $sheet->{'uname'}) + } return @exportarr; } @@ -2357,20 +2671,14 @@ sub exportsheet { # # 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; } } @@ -2381,16 +2689,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; } } } @@ -2401,22 +2706,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 @@ -2432,20 +2739,42 @@ sub cachedssheets { 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; } + # # 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 @@ -2453,17 +2782,27 @@ sub handler { $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']); + # + # 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; } + # # Interactive loading of specific sheet? + # if (($ENV{'form.load'}) && ($ENV{'form.loadthissheet'} ne 'Default')) { $ENV{'form.ufn'}=$ENV{'form.loadthissheet'}; } @@ -2479,12 +2818,15 @@ sub handler { $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'); if ($ENV{'request.role'} !~ /^st\./) { $r->print(<rflush(); # # Full recalc? + # if ($ENV{'form.forcerecalc'}) { $r->print('

Completely Recalculating Sheet ...

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

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

'); - &setfilename($asheet,$ENV{'form.ufn'}); - &tmpread($asheet,$ENV{'form.unewfield'},$ENV{'form.unewformula'}); + $sheet->{'filename'} = $ENV{'form.ufn'}; + &tmpread($sheet,$ENV{'form.unewfield'},$ENV{'form.unewformula'}); } elsif ($ENV{'form.saveas'}) { - &setfilename($asheet,$ENV{'form.ufn'}); - &tmpread($asheet); + $sheet->{'filename'} = $ENV{'form.ufn'}; + &tmpread($sheet); } else { - &readsheet($asheet,$asheetdata,$ENV{'form.ufn'}); + &readsheet($sheet,$ENV{'form.ufn'}); } # Print out user information - unless ($asheetdata->{'sheettype'} eq 'classcalc') { - $r->print('

User: '.$asheetdata->{'uname'}. - '
Domain: '.$asheetdata->{'udom'}); -# if (&getcsec($asheet) eq '-1') { -# $r->print('

'. -# 'Not a student in this course

'); -# } else { - $r->print('
Section/Group: '.$asheetdata->{'csec'}); -# } + if ($sheet->{'sheettype'} ne 'classcalc') { + $r->print('

User: '.$sheet->{'uname'}. + '
Domain: '.$sheet->{'udom'}); + $r->print('
Section/Group: '.$sheet->{'csec'}); if ($ENV{'form.usymb'}) { $r->print('
Assessment: '. $ENV{'form.usymb'}.''); @@ -2567,10 +2904,10 @@ ENDSCRIPT } # # Check user permissions - if (($asheetdata->{'sheettype'} eq 'classcalc' ) || - ($asheetdata->{'uname'} ne $ENV{'user.name'} ) || - ($asheetdata->{'udom'} ne $ENV{'user.domain'})) { - unless (&Apache::lonnet::allowed('vgr',$asheetdata->{'cid'})) { + if (($sheet->{'sheettype'} eq 'classcalc' ) || + ($sheet->{'uname'} ne $ENV{'user.name'} ) || + ($sheet->{'udom'} ne $ENV{'user.domain'})) { + unless (&Apache::lonnet::allowed('vgr',$sheet->{'cid'})) { $r->print('

Access Permission Denied

'. ''); return OK; @@ -2580,15 +2917,15 @@ ENDSCRIPT $r->print('
'. '

'); - if ($asheetdata->{'sheettype'} eq 'assesscalc') { + if ($sheet->{'sheettype'} eq 'assesscalc') { $r->print('

'. ''. + 'uname='.$sheet->{'uname'}. + '&udom='.$sheet->{'udom'}.'">'. 'Level up: Student Sheet

'); } - if (($asheetdata->{'sheettype'} eq 'studentcalc') && - (&Apache::lonnet::allowed('vgr',$asheetdata->{'cid'}))) { + if (($sheet->{'sheettype'} eq 'studentcalc') && + (&Apache::lonnet::allowed('vgr',$sheet->{'cid'}))) { $r->print ('

'. 'Level up: Course Sheet

'); } @@ -2601,13 +2938,13 @@ ENDSCRIPT ''. 'make default:

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

'. '

'); - if ($asheetdata->{'sheettype'} eq 'studentcalc') { - &setothersheets($asheet,&othersheets($asheet,'assesscalc')); + if ($sheet->{'sheettype'} eq 'studentcalc') { + &setothersheets($sheet, + &othersheets($sheet,'assesscalc')); } } - # Cached sheets - &expirationdates(); - undef %oldsheets; - undef %loadedcaches; - if ($asheetdata->{'sheettype'} eq 'classcalc') { + # + # Set up caching mechanisms + # + &load_spreadsheet_expirationdates(); + # Clear out old caches if we have not seen this class before. + if (exists($oldsheets{'course'}) && + $oldsheets{'course'} ne $sheet->{'cid'}) { + undef %oldsheets; + undef %loadedcaches; + } + $oldsheets{'course'} = $sheet->{'cid'}; + # + if ($sheet->{'sheettype'} eq 'classcalc') { $r->print("Loading previously calculated student sheets ...\n"); $r->rflush(); &cachedcsheets(); - } elsif ($asheetdata->{'sheettype'} eq 'studentcalc') { + } elsif ($sheet->{'sheettype'} eq 'studentcalc') { $r->print("Loading previously calculated assessment sheets ...\n"); $r->rflush(); - &cachedssheets($asheetdata->{'uname'},$asheetdata->{'udom'}, - $asheetdata->{'uhome'}); + &cachedssheets($sheet); } # Update sheet, load rows $r->print("Loaded sheet(s), updating rows ...
\n"); $r->rflush(); # - &updatesheet($asheet,$asheetdata); + &updatesheet($sheet); $r->print("Updated rows, loading row data ...\n"); $r->rflush(); # - &loadrows($asheet,$asheetdata,$r); + &loadrows($sheet,$r); $r->print("Loaded row data, calculating sheet ...
\n"); $r->rflush(); # - my $calcoutput=&calcsheet($asheet); + my $calcoutput=&calcsheet($sheet); $r->print('

'.$calcoutput.'

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

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

'); + &writesheet($sheet,$ENV{'form.makedefufn'}). + '

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

'); - &tmpwrite($asheet); - if ($asheetdata->{'sheettype'} eq 'studentcalc') { + # Write the modified worksheet + $r->print('Current sheet: '.$sheet->{'filename'}.'

'); + &tmpwrite($sheet); + if ($sheet->{'sheettype'} eq 'studentcalc') { $r->print('
Show rows with empty A column: '); } else { $r->print('
Show empty rows: '); - } + } # $r->print(&hiddenfield('userselhidden','true'). '{'cid'}.'.hideemptyrows'} eq 'yes') { $r->print(' checked'); $ENV{'form.showall'}=1; } @@ -2687,26 +3033,40 @@ ENDSCRIPT $r->print('>'); # # CSV format checkbox (classcalc sheets only) - if ($asheetdata->{'sheettype'} eq 'classcalc') { - $r->print(' Output CSV format: print(' checked'); } - $r->print('>'); + $r->print(' Output as \n"); + # + if ($sheet->{'sheettype'} eq 'classcalc') { + $r->print(' Student Status: '. + &Apache::lonhtmlcommon::StatusOptions + ($ENV{'form.Status'},'sheet')); } # # Buttons to insert rows - $r->print(' Student Status: '. - &Apache::lonhtmlcommon::StatusOptions - ($ENV{'form.Status'},'sheet')); - $r->print(< - -
-ENDINSERTBUTTONS +# $r->print(< +# +#
+#ENDINSERTBUTTONS # Print out sheet - &outsheet($r,$asheet,$asheetdata); + &outsheet($r,$sheet); $r->print(''); # Done return OK; @@ -2714,7 +3074,3 @@ ENDINSERTBUTTONS 1; __END__ - - - -