--- loncom/interface/spreadsheet/Spreadsheet.pm	2006/02/27 00:56:47	1.62
+++ loncom/interface/spreadsheet/Spreadsheet.pm	2024/11/16 02:41:05	1.86
@@ -1,5 +1,5 @@
 #
-# $Id: Spreadsheet.pm,v 1.62 2006/02/27 00:56:47 bowersj2 Exp $
+# $Id: Spreadsheet.pm,v 1.86 2024/11/16 02:41:05 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -60,6 +60,9 @@ use HTML::TokeParser;
 use Spreadsheet::WriteExcel;
 use Time::HiRes;
 use Apache::lonlocal;
+use lib '/home/httpd/lib/perl/';
+use LONCAPA;
+ 
 
 ##
 ## Package Variables
@@ -85,7 +88,7 @@ sub new {
     my $class = ref($this) || $this;
     my ($stype) = ($class =~ /Apache::(.*)$/);
     #
-    my ($name,$domain,$filename,$usymb)=@_;
+    my ($name,$domain,$filename,$usymb,$section,$groups)=@_;
     if (defined($usymb) && ref($usymb)) {
         $usymb = $usymb->symb;
     }
@@ -95,10 +98,22 @@ sub new {
     if (! defined($domain) || $domain eq '') {
         $domain = $env{'user.domain'};
     }
+    if (! defined($section) || $section eq '') {
+        $section = &Apache::lonnet::getsection($domain,$name,
+					       $env{'request.course.id'});
+    }
+    if (! defined($groups)) {
+
+        my @usersgroups = &Apache::lonnet::get_users_groups($domain,$name,
+                                                    $env{'request.course.id'});
+        $groups = \@usersgroups;
+    }
     #
     my $self = {
         name     => $name,
         domain   => $domain,
+        section  => $section,
+        groups   => $groups, 
         type     => $stype,
         symb     => $usymb,
         errorlog => '',
@@ -157,16 +172,20 @@ sub filename {
                 $newfilename = 'default_'.$self->{'type'};
             }
         }
-        if ($newfilename !~ /\w/ || $newfilename =~ /^\W*$/) {
-            $newfilename = 'default_'.$self->{'type'};
-        }
-        if ($newfilename !~ /^default\.$self->{'type'}$/ &&
-            $newfilename !~ /^\/res\/(.*)spreadsheet$/) {
-            if ($newfilename !~ /_$self->{'type'}$/) {
-                $newfilename =~ s/[\s_]*$//;
-                $newfilename .= '_'.$self->{'type'};
-            }
-        }
+	if ($newfilename eq &mt('LON-CAPA Standard')) {
+	    undef($newfilename);
+	} else {
+	    if ($newfilename !~ /\w/ || $newfilename =~ /^\W*$/) {
+		$newfilename = 'default_'.$self->{'type'};
+	    }
+	    if ($newfilename !~ /^default\.$self->{'type'}$/ &&
+		$newfilename !~ /^\/res\/(.*)spreadsheet$/) {
+		if ($newfilename !~ /_$self->{'type'}$/) {
+		    $newfilename =~ s/[\s_]*$//;
+		    $newfilename .= '_'.$self->{'type'};
+		}
+	    }
+	}
         $self->{'filename'} = $newfilename;
         return;
     }
@@ -191,8 +210,8 @@ sub make_default {
             {'spreadsheet_default_'.$self->{'type'} => $self->filename()},
                                      $self->{'cdom'},$self->{'cnum'});
     return $result if ($result ne 'ok');
-    &Apache::lonnet::appenv('course.'.$self->{'cid'}.'.spreadsheet_default_'.
-			    $self->{'type'} => $self->filename());
+    &Apache::lonnet::appenv({'course.'.$self->{'cid'}.'.spreadsheet_default_'.
+			    $self->{'type'} => $self->filename()});
     my $symb = $self->{'symb'};
     $symb = '' if (! defined($symb));
     &Apache::lonnet::expirespread('','',$self->{'type'},$symb);    
@@ -308,6 +327,7 @@ sub initialize_safe_space {
       $safeeval = new Safe(shift);
       my $safehole = new Safe::Hole;
       $safeeval->permit("entereval");
+      $safeeval->permit("hintseval"); 
       $safeeval->permit(":base_math");
       $safeeval->permit("sort");
       $safeeval->deny(":base_io");
@@ -656,6 +676,21 @@ sub MAXPARM {
     return $max;
 }
 
+
+=pod
+
+=item PARM(parametername)
+
+Returns the value of the parameter matching the input parameter name.
+parametername should be a string such as 'parameter_1_opendate'.
+
+=cut
+
+#-------------------------------------------------------
+sub PARM {
+    return $c{$_[0]};
+}
+
 #-------------------------------------------------------
 
 =pod
@@ -692,7 +727,7 @@ sub get_values {
 	my @num=($ld..$ud);
 	foreach my $a (@alpha) {
 	    foreach my $n (@num) {
-		if (exists($sheet_values{$a.$n})) {
+		if ((exists($sheet_values{$a.$n})) && ($sheet_values{$a.$n} ne '')) {
 		    push(@values,$sheet_values{$a.$n});
 		}
 	    }
@@ -708,8 +743,10 @@ sub get_values {
         $alpha=qq/[$la-$ua]/;
     }
     my $expression = '^'.$alpha.$num.'$';
-    foreach (grep /$expression/,keys(%sheet_values)) {
-	push(@values,$sheet_values{$_});
+    foreach my $item (grep(/$expression/,keys(%sheet_values))) {
+        unless ($sheet_values{$item} eq '') {
+	    push(@values,$sheet_values{$item});
+        }
     }
     return \@values;
 }
@@ -738,7 +775,7 @@ sub calc {
         $depth++;
         if ($depth>100) {
 	    undef %sheet_values;
-            return $lastcalc.': Maximum calculation depth exceeded';
+            return $lastcalc.': '.&mt('Maximum calculation depth exceeded');
         }
     }
     return 'okay';
@@ -786,18 +823,20 @@ sub expandnamed {
     my $self = shift;
     my $expression=shift;
     if ($expression=~/^\&/) {
-	my ($func,$var,$formula)=($expression=~/^\&(\w+)\(([^\;]+)\;(.*)\)/);
+	my ($func,$var,$formula)=($expression=~/^\&(\w+)\(([^\;]+)\;(.*)\)/s);
 	my @vars=split(/\W+/,$formula);
+	# make the list uniq
+	@vars = keys(%{{ map { $_ => 1 } @vars }});
         my %values=();
 	foreach my $varname ( @vars ) {
             if ($varname=~/^(parameter|stores|timestamp)/) {
                 $formula=~s/$varname/'$c{\''.$varname.'\'}'/ge;
-               $varname=~s/$var/\([\\w:\\- ]\+\)/g;
-	       foreach (keys(%{$self->{'constants'}})) {
-		  if ($_=~/$varname/) {
-		      $values{$1}=1;
-                  }
-               }
+		$varname=~s/$var/\([\\w:\\- ]\+\)/g;
+		foreach (keys(%{$self->{'constants'}})) {
+		    if ($_=~/$varname/) {
+			$values{$1}=1;
+		    }
+		}
 	    }
         }
         if ($func eq 'EXPANDSUM') {
@@ -808,7 +847,7 @@ sub expandnamed {
                 $result.=$thissum.'+';
             } 
             $result=~s/\+$//;
-            return $result;
+            return '('.$result.')';
         } else {
 	    return 0;
         }
@@ -835,7 +874,7 @@ sub expandnamed {
             $returnvalue = $values[0];
         } elsif (scalar(@matches) > 0) {
             # more than one match.  Look for a concise one
-            $returnvalue =  "'non-unique parameter name : $expression'";
+            $returnvalue =  "'".&mt('non-unique parameter name: [_1]',$expression).'"';
             for (my $i=0; $i<=$#matches;$i++) {
                 if ($matches[$i] =~ /^$expression$/) {
                     # why do we not do this lookup here?
@@ -845,7 +884,7 @@ sub expandnamed {
         } else {
             # There was a negative number of matches, which indicates 
             # something is wrong with reality.  Better warn the user.
-            $returnvalue = '"bizzare parameter: '.$expression.'"';
+            $returnvalue = "'".&mt('bizarre parameter: [_1]',$expression)."'";
         }
         return $returnvalue;
     }
@@ -871,7 +910,7 @@ sub sett {
             # Replace 'A0' with the value from 'A0'
             $t{$cell}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
             # Replace parameters
-            $t{$cell}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge;
+            $t{$cell}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/sge;
         }
     }
     #
@@ -883,7 +922,7 @@ sub sett {
             $t{$cell}=$formula;
             $t{$cell}=~s/\.\.+/\,/g;
             $t{$cell}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
-            $t{$cell}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge;
+            $t{$cell}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/sge;
         } elsif  ( $col  =~ /^[A-Z]$/  ) {
             if ($formula !~ /^\!/ && exists($self->{'constants'}->{$cell})
 		&& $self->{'constants'}->{$cell} ne '') {
@@ -894,7 +933,7 @@ sub sett {
             $t{$cell}=$formula;
             $t{$cell}=~s/\.\.+/\,/g;
             $t{$cell}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g;
-            $t{$cell}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge;
+            $t{$cell}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/sge;
         }
     }
     %{$self->{'safe'}->varglob('t')}=%t;
@@ -1229,9 +1268,9 @@ sub html_report_error {
     my $self = shift();
     my $Str = '';
     if ($self->badcalc()) {
-        $Str = '<h3 style="color:red">'.
+        $Str = '<p class="LC_error">'.
             &mt('An error occurred while calculating this spreadsheet').
-            "</h3>\n".
+            "</p>\n".
             '<pre>'.$self->calcerror()."</pre>\n";
     }
     return $Str;
@@ -1271,8 +1310,8 @@ sub html_template_row {
 	    $row_html .= '<td bgcolor="'.$importcolor.'">'.
                 &html_uneditable_cell($cell,'#FFDDDD',$allowed).'</td>';
         } else {
-	    $row_html .= '<td bgcolor="#EOFFDD">'.
-                &html_editable_cell($cell,'#EOFFDD',$allowed,
+	    $row_html .= '<td bgcolor="#E0FFDD">'.
+                &html_editable_cell($cell,'#E0FFDD',$allowed,
                                     $self->{outputmode} eq 'source').'</td>';
         }
     }
@@ -1290,15 +1329,13 @@ sub html_editable_cell {
     }
     $name    = '' if (! defined($name));
     $formula = '' if (! defined($formula));
-    if ($showsource)
-    {
+    if ($showsource) {
         if (!defined($formula) || $formula =~ /^\s*$/) {
             $value = '<font color="'.$bgcolor.'">#</font>';
         } else {
             $value = &HTML::Entities::encode($formula, '<>&"');
         }
-    }
-    elsif (! defined($value)) {
+    } elsif (! defined($value)) {
         $value = '<font color="'.$bgcolor.'">#</font>';
         if ($formula ne '') {
             $value = '<i>undefined value</i>';
@@ -1315,7 +1352,7 @@ sub html_editable_cell {
     #
     # The encoding string "^A-blah" is placed in []'s inside a regexp, so 
     # we specify the characters we want left alone by putting a '^' in front.
-    $formula = &HTML::Entities::encode($formula,'^A-z0-9 !#$%-;=?~');
+    $formula = &HTML::Entities::encode($formula,'^A-z0-9 !#$%;=?~-');
     # HTML::Entities::encode does not catch everything - we need '\' encoded
     $formula =~ s/\\/&\#092/g;
     # Escape it again - this time the only encodable character is '&'
@@ -1350,7 +1387,7 @@ sub html_row {
 	    $row_html .= '<td bgcolor="'.$color.'">';
 	    $row_html .= &html_uneditable_cell($cell,'#FFDDDD');
 	} else {
-	    $row_html .= '<td bgcolor="#EOFFDD">';
+	    $row_html .= '<td bgcolor="#E0FFDD">';
 	    $row_html .= &html_editable_cell($cell,'#E0FFDD',$allowed,
                                              $self->{outputmode} eq 'source');
 	}
@@ -1396,7 +1433,7 @@ sub output_selector {
     foreach  ($self->output_options()) {
         $output_selector.='<option value="'.$_->{'value'}.'"';
         if ($_->{'value'} eq $default) {
-            $output_selector .= ' selected';
+            $output_selector .= ' selected="selected"';
         }
         $output_selector .= ">".&mt($_->{'description'})."</option>\n";
     }
@@ -1473,7 +1510,7 @@ sub outsheet_excel {
     #
     # Write a link to allow them to download it
     $r->print('<br />'.
-              '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n");
+              '<a href="'.$filename.'">'.&mt('Your Excel spreadsheet').'</a>'."\n");
     return;
 }
 
@@ -1491,16 +1528,20 @@ sub outsheet_csv   {
     my $csvdata = '';
     my @Values;
     #
-    # Open the csv file
+    # Open the CSV file
     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(&mt("Problems occured in writing the csv file.  ".
-                  "This error has been logged.  ".
-                  "Please alert your LON-CAPA administrator."));
+        $r->print(
+            '<p class="LC_error">'
+           .&mt('Problems occurred in writing the CSV file.')
+           .' '.&mt('This error has been logged.')
+           .' '.&mt('Please alert your LON-CAPA administrator.')
+           .'</p>'
+        );
         $r->print("<pre>\n".$csvdata."</pre>\n");
         return 0;
     }
@@ -1513,7 +1554,7 @@ sub outsheet_csv   {
     # Output the body of the spreadsheet
     $self->csv_rows($connection,$file);
     #
-    # Close the csv file
+    # Close the CSV file
     close($file);
     $r->print('<br /><br />'.
               '<a href="'.$filename.'">'.&mt('Your CSV spreadsheet.').'</a>'."\n");
@@ -1655,8 +1696,10 @@ sub load {
             my $sheetxml=&Apache::lonnet::getfile
                 (&Apache::lonnet::filelocation('',$filename));
             if ($sheetxml == -1) {
-                $sheetxml='<field row="0" col="A">"Error loading spreadsheet '
-                    .$self->filename().'"</field>';
+                $sheetxml='<field row="0" col="A">'.
+                          &mt('Error loading spreadsheet [_1]',
+                                  '"'.$self->filename().'"').
+                          '</field>';
             }
             ($formulas,undef) = &parse_sheet(\$sheetxml);
             # Get just the filename and set the sheets filename
@@ -1671,7 +1714,8 @@ sub load {
             # Load the spreadsheet definition file from the save file
             my %tmphash = &Apache::lonnet::dump($filename,$cdom,$cnum);
             my ($tmp) = keys(%tmphash);
-            if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
+            if (%tmphash
+		&& $tmp !~ /^(con_lost|error|no_such_host)/i) {
                 while (my ($cell,$formula) = each(%tmphash)) {
                     $formulas->{$cell}=$formula;
                 }
@@ -1774,16 +1818,14 @@ sub save {
                                 {'spreadsheet_default_'.$stype => $filename },
                                           $cdom,$cnum);
             return $reply if ($reply ne 'ok');
-	    &Apache::lonnet::appenv('course.'.$self->{'cid'}.'.spreadsheet_default_'.
-				    $self->{'type'} => $self->filename());
+	    &Apache::lonnet::appenv({'course.'.$self->{'cid'}.'.spreadsheet_default_'.
+				    $self->{'type'} => $self->filename()});
         } 
-        if ($self->is_default()) {
-            if ($self->{'type'} eq 'studentcalc') {
-                &Apache::lonnet::expirespread('','','studentcalc','');
-            } elsif ($self->{'type'} eq 'assesscalc') {
-                &Apache::lonnet::expirespread('','','assesscalc','');
-                &Apache::lonnet::expirespread('','','studentcalc','');
-            }
+	if ($self->{'type'} eq 'studentcalc') {
+	    &Apache::lonnet::expirespread('','','studentcalc','');
+	} elsif ($self->{'type'} eq 'assesscalc') {
+	    &Apache::lonnet::expirespread('','','assesscalc','');
+	    &Apache::lonnet::expirespread('','','studentcalc','');
         }
         return $reply;
     }
@@ -1805,8 +1847,8 @@ sub save_tmp {
         my %f = $self->formulas();
         while( my ($cell,$formula) = each(%f)) {
             next if ($formula eq 'import');
-            print $fh &Apache::lonnet::escape($cell)."=".
-                &Apache::lonnet::escape($formula)."\n";
+            print $fh &escape($cell)."=".
+                &escape($formula)."\n";
         }
         $fh->close();
     }
@@ -1824,8 +1866,8 @@ sub load_tmp {
         while (<$spreadsheet_file>) {
 	    chomp;
             my ($cell,$formula) = split(/=/);
-            $cell    = &Apache::lonnet::unescape($cell);
-            $formula = &Apache::lonnet::unescape($formula);
+            $cell    = &unescape($cell);
+            $formula = &unescape($formula);
             $formulas{$cell} = $formula;
         }
         $spreadsheet_file->close();
@@ -1868,14 +1910,13 @@ sub othersheets {
     my ($stype) = @_;
     $stype = $self->{'type'} if (! defined($stype) || $stype !~ /calc$/);
     #
-    my @alternatives=();
+    my @alternatives=(&mt('Default'), &mt('LON-CAPA Standard'));
     my %results=&Apache::lonnet::dump($stype.'_spreadsheets',
                                       $self->{'cdom'}, $self->{'cnum'});
     my ($tmp) = keys(%results);
-    if ($tmp =~ /^(con_lost|error|no_such_host)/i ) {
-        @alternatives = (&mt('Default'));
-    } else {
-        @alternatives = (&mt('Default'), sort (keys(%results)));
+    if (%results
+	&& $tmp !~ /^(con_lost|error|no_such_host)/i ) {
+        push(@alternatives, sort(keys(%results)));
     }
     return @alternatives; 
 }