File:
[LON-CAPA] /
loncom /
interface /
spreadsheet /
studentcalc.pm
Revision
1.9:
download - view:
text,
annotated -
select for diffs
Thu May 29 18:31:27 2003 UTC (21 years, 11 months ago) by
matthew
Branches:
MAIN
CVS tags:
HEAD
Fixes bugs 1513 and 1516. Every time a new row is added to a spreadsheet
the spreadsheet will be saved.
&set_row_numbers now actually keeps track of the maximum row found, which
can be useful if you need to add a new row...
Also fixed bug in caching of spreadsheets - was storing the 'join'd version
of the formulas when I expected a hash reference.
#
# $Id: studentcalc.pm,v 1.9 2003/05/29 18:31:27 matthew Exp $
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
#
# LON-CAPA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LON-CAPA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# /home/httpd/html/adm/gpl.txt
#
# http://www.lon-capa.org/
#
# The LearningOnline Network with CAPA
# Spreadsheet/Grades Display Handler
#
# POD required stuff:
=head1 NAME
studentcalc
=head1 SYNOPSIS
=head1 DESCRIPTION
=over 4
=cut
###################################################
### StudentSheet ###
###################################################
package Apache::studentcalc;
use strict;
use Apache::Constants qw(:common :http);
use Apache::lonnet;
use Apache::loncommon();
use Apache::loncoursedata();
use Apache::lonnavmaps;
use Apache::Spreadsheet();
use Apache::assesscalc();
use HTML::Entities();
use Spreadsheet::WriteExcel;
use Time::HiRes;
@Apache::studentcalc::ISA = ('Apache::Spreadsheet');
my @Sequences = ();
my %Exportrows = ();
my $current_course;
sub initialize {
&initialize_sequence_cache();
}
sub initialize_package {
$current_course = $ENV{'request.course.id'};
&initialize_sequence_cache();
&load_cached_export_rows();
}
sub ensure_correct_sequence_data {
if ($current_course ne $ENV{'request.course.id'}) {
&initialize_sequence_cache();
$current_course = $ENV{'request.course.id'};
}
return;
}
sub initialize_sequence_cache {
#
# Set up the sequences and assessments
@Sequences = ();
my ($top,$sequences,$assessments) =
&Apache::loncoursedata::get_sequence_assessment_data();
if (! defined($top) || ! ref($top)) {
# There has been an error, better report it
&Apache::lonnet::logthis('top is undefined (studentcalc.pm)');
return;
}
@Sequences = @{$sequences} if (ref($sequences) eq 'ARRAY');
}
sub clear_package {
@Sequences = undef;
%Exportrows = undef;
}
sub get_title {
my $self = shift;
my @title = ();
#
# Determine the students name
my %userenv = &Apache::loncoursedata::GetUserName($self->{'name'},
$self->{'domain'});
my $name = join(' ',
@userenv{'firstname','middlename','lastname','generation'});
$name =~ s/\s+$//;
push (@title,$name);
push (@title,$self->{'coursedesc'});
push (@title,scalar(localtime(time)));
return @title;
}
sub get_html_title {
my $self = shift;
my ($name,$desc,$time) = $self->get_title();
my $title = '<h1>'.$name;
if ($ENV{'user.name'} ne $self->{'name'} &&
$ENV{'user.domain'} ne $self->{'domain'}) {
$title .= &Apache::loncommon::aboutmewrapper
($self->{'name'}.'@'.$self->{'domain'},
$self->{'name'},$self->{'domain'});
}
$title .= "</h1>\n";
$title .= '<h2>'.$desc."</h2>\n";
$title .= '<h3>'.$time.'</h3>';
return $title;
}
sub parent_link {
my $self = shift;
my $link .= '<p><a href="/adm/classcalc?'.
'sname='.$self->{'name'}.
'&sdomain='.$self->{'domain'}.'">'.
'Course level sheet</a></p>'."\n";
return $link;
}
sub outsheet_html {
my $self = shift;
my ($r) = @_;
####################################
# Get the list of assessment files #
####################################
my @AssessFileNames = $self->othersheets('assesscalc');
my $editing_is_allowed = &Apache::lonnet::allowed('mgr',
$ENV{'request.course.id'});
####################################
# Determine table structure #
####################################
my $num_uneditable = 26;
my $num_left = 52-$num_uneditable;
my $tableheader =<<"END";
<p>
<table border="2">
<tr>
<th colspan="2" rowspan="2"><font size="+2">Student</font></th>
<td bgcolor="#FFDDDD" colspan="$num_uneditable">
<b><font size="+1">Import</font></b></td>
<td colspan="$num_left">
<b><font size="+1">Calculations</font></b></td>
</tr><tr>
END
my $label_num = 0;
foreach (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')){
if ($label_num<$num_uneditable) {
$tableheader .='<td bgcolor="#FFDDDD">';
} else {
$tableheader .='<td>';
}
$tableheader .="<b><font size=+1>$_</font></b></td>";
$label_num++;
}
$tableheader .="</tr>\n";
if ($self->blackout()) {
$r->print('<font color="red" size="+2"><p>'.
'Some computations are not available at this time.<br />'.
'There are problems whose status you are allowed to view.'.
'</font></p>'."\n");
} else {
$r->print($tableheader);
#
# Print out template row
if (exists($ENV{'request.role.adv'}) && $ENV{'request.role.adv'}) {
$r->print('<tr><td>Template</td><td> </td>'.
$self->html_template_row($num_uneditable)."</tr>\n");
}
#
# Print out summary/export row
$r->print('<tr><td>Summary</td><td>0</td>'.
$self->html_export_row()."</tr>\n");
}
$r->print("</table>\n");
#
# Prepare to output rows
if (exists($ENV{'request.role.adv'}) && $ENV{'request.role.adv'}) {
$tableheader =<<"END";
</p><p>
<table border="2">
<tr><th>Row</th><th>Assessment</th>
END
} else {
$tableheader =<<"END";
</p><p>
<table border="2">
<tr><th> </th><th>Assessment</th>
END
}
foreach (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')){
if ($label_num<$num_uneditable) {
$tableheader.='<td bgcolor="#FFDDDD">';
} else {
$tableheader.='<td>';
}
$tableheader.="<b><font size=+1>$_</font></b></td>";
}
$tableheader.="\n";
#
my $num_output = 1;
if (scalar(@Sequences)< 1) {
&initialize_sequence_cache();
}
foreach my $Sequence (@Sequences) {
next if ($Sequence->{'num_assess'} < 1);
$r->print("<h3>".$Sequence->{'title'}."</h3>\n");
$r->print($tableheader);
foreach my $resource (@{$Sequence->{'contents'}}) {
next if ($resource->{'type'} ne 'assessment');
my $rownum = $self->get_row_number_from_key($resource->{'symb'});
my $assess_filename = $self->{'row_source'}->{$rownum};
my $row_output = '<tr>';
if ($editing_is_allowed) {
$row_output .= '<td>'.$rownum.'</td>';
$row_output .= '<td>'.
'<a href="/adm/assesscalc?sname='.$self->{'name'}.
'&sdomain='.$self->{'domain'}.
'&filename='.$assess_filename.
'&usymb='.&Apache::lonnet::escape($resource->{'symb'}).
'">'.$resource->{'title'}.'</a><br />';
$row_output .= &assess_file_selector($rownum,
$assess_filename,
\@AssessFileNames).
'</td>';
} else {
$row_output .= '<td><a href="'.$resource->{'src'}.'?symb='.
&Apache::lonnet::escape($resource->{'symb'}).
'">Go To</a>';
$row_output .= '</td><td>'.$resource->{'title'}.'</td>';
}
if ($self->blackout() && $self->{'blackout_rows'}->{$rownum}>0) {
$row_output .=
'<td colspan="52">Unavailable at this time</td></tr>'."\n";
} else {
$row_output .= $self->html_row($num_uneditable,$rownum).
"</tr>\n";
}
$r->print($row_output);
}
$r->print("</table>\n");
}
$r->print("</p>\n");
return;
}
########################################################
########################################################
=pod
=item &assess_file_selector()
=cut
########################################################
########################################################
sub assess_file_selector {
my ($row,$default,$AssessFiles)=@_;
if (!defined($AssessFiles) || ! @$AssessFiles) {
return '';
}
return '' if (! &Apache::lonnet::allowed('mgr',$ENV{'request.course.id'}));
my $element_name = 'FileSelect_'.$row;
my $load_dialog = '<select size="1" name="'.$element_name.'" '.
'onchange="'.
"document.sheet.cell.value='source_$row';".
"document.sheet.newformula.value=document.sheet.$element_name\.value;".
'document.sheet.submit()" '.'>'."\n";
foreach my $file (@{$AssessFiles}) {
$load_dialog .= ' <option name="'.$file.'"';
$load_dialog .= ' selected' if ($default eq $file);
$load_dialog .= '>'.$file."</option>\n";
}
$load_dialog .= "</select>\n";
return $load_dialog;
}
sub modify_cell {
my $self = shift;
my ($cell,$formula) = @_;
if ($cell =~ /^source_(\d+)$/) {
# Need to make sure $formula is a valid filename....
my $row = $1;
$cell = 'A'.$row;
$self->{'row_source'}->{$row} = $formula;
my $original_source = $self->formula($cell);
if ($original_source =~ /__&&&__/) {
($original_source,undef) = split('__&&&__',$original_source);
}
$formula = $original_source.'__&&&__'.$formula;
} elsif ($cell =~ /([A-z])\-/) {
$cell = 'template_'.$1;
} elsif ($cell !~ /^([A-z](\d+)|template_[A-z])$/) {
return;
}
$self->set_formula($cell,$formula);
$self->rebuild_stats();
return;
}
sub csv_rows {
# writes the meat of the spreadsheet to an excel worksheet. Called
# by Spreadsheet::outsheet_excel;
my $self = shift;
my ($filehandle) = @_;
#
# Write a header row
$self->csv_output_row($filehandle,undef,
('Container','Assessment title'));
#
# Write each assessments row
if (scalar(@Sequences)< 1) {
&initialize_sequence_cache();
}
foreach my $Sequence (@Sequences) {
next if ($Sequence->{'num_assess'} < 1);
foreach my $resource (@{$Sequence->{'contents'}}) {
my $rownum = $self->get_row_number_from_key($resource->{'symb'});
my @assessdata = ($Sequence->{'title'},
$resource->{'title'});
$self->csv_output_row($filehandle,$rownum,@assessdata);
}
}
return;
}
sub excel_rows {
# writes the meat of the spreadsheet to an excel worksheet. Called
# by Spreadsheet::outsheet_excel;
my $self = shift;
my ($worksheet,$cols_output,$rows_output) = @_;
#
# Write a header row
$cols_output = 0;
foreach my $value ('Container','Assessment title') {
$worksheet->write($rows_output,$cols_output++,$value);
}
$rows_output++;
#
# Write each assessments row
if (scalar(@Sequences)< 1) {
&initialize_sequence_cache();
}
foreach my $Sequence (@Sequences) {
next if ($Sequence->{'num_assess'} < 1);
foreach my $resource (@{$Sequence->{'contents'}}) {
my $rownum = $self->get_row_number_from_key($resource->{'symb'});
my @assessdata = ($Sequence->{'title'},
$resource->{'title'});
$self->excel_output_row($worksheet,$rownum,$rows_output++,
@assessdata);
}
}
return;
}
sub outsheet_recursive_excel {
my $self = shift;
my ($r) = @_;
}
sub compute {
my $self = shift;
$self->logthis('computing');
if (! defined($current_course) ||
$current_course ne $ENV{'request.course.id'}) {
$current_course = $ENV{'request.course.id'};
&clear_package();
&initialize_sequence_cache();
}
$self->initialize_safe_space();
my @sequences = @Sequences;
if (@sequences < 1) {
my ($top,$sequences,$assessments) =
&Apache::loncoursedata::get_sequence_assessment_data();
if (! defined($top) || ! ref($top)) {
&Apache::lonnet::logthis('top is undefined');
return;
}
@sequences = @{$sequences} if (ref($sequences) eq 'ARRAY');
}
&Apache::assesscalc::initialize_package($self->{'name'},$self->{'domain'});
my %f = $self->formulas();
#
# Process the formulas list -
# the formula for the A column of a row is symb__&&__filename
my %c = $self->constants();
foreach my $seq (@sequences) {
next if ($seq->{'num_assess'}<1);
foreach my $resource (@{$seq->{'contents'}}) {
next if ($resource->{'type'} ne 'assessment');
my $rownum = $self->get_row_number_from_key($resource->{'symb'});
my $cell = 'A'.$rownum;
my $assess_filename = 'Default';
if (exists($self->{'row_source'}->{$rownum})) {
$assess_filename = $self->{'row_source'}->{$rownum};
} else {
$self->{'row_source'}->{$rownum} = $assess_filename;
}
$f{$cell} = $resource->{'symb'}.'__&&&__'.$assess_filename;
my $assessSheet = Apache::assesscalc->new($self->{'name'},
$self->{'domain'},
$assess_filename,
$resource->{'symb'});
my @exportdata = $assessSheet->export_data();
if ($assessSheet->blackout()) {
$self->blackout(1);
$self->{'blackout_rows'}->{$rownum} = 1;
}
#
# Be sure not to disturb the formulas in the 'A' column
my $data = shift(@exportdata);
$c{$cell} = $data if (defined($data));
#
# Deal with the remaining columns
my $i=0;
foreach (split(//,'BCDEFGHIJKLMNOPQRSTUVWXYZ')) {
my $cell = $_.$rownum;
my $data = shift(@exportdata);
if (defined($data)) {
$f{$cell} = 'import';
$c{$cell} = $data;
}
$i++;
}
}
}
$self->constants(\%c);
$self->formulas(\%f);
$self->calcsheet();
#
# Store export row in cache
my @exportarray=$self->exportrow();
my $student = $self->{'name'}.':'.$self->{'domain'};
$Exportrows{$student}->{'time'} = time;
$Exportrows{$student}->{'data'} = \@exportarray;
# save export row
$self->save_export_data();
#
$self->save() if ($self->need_to_save());
return;
}
sub set_row_sources {
my $self = shift;
while (my ($cell,$value) = each(%{$self->{'formulas'}})) {
next if ($cell !~ /^A(\d+)/ && $1 > 0);
my $row = $1;
(undef,$value) = split('__&&&__',$value);
$value = 'Default' if (! defined($value));
$self->{'row_source'}->{$row} = $value;
}
return;
}
sub set_row_numbers {
my $self = shift;
while (my ($cell,$formula) = each(%{$self->{'formulas'}})) {
next if ($cell !~ /^A(\d+)/);
my $row = $1;
next if ($row == 0);
my ($symb,undef) = split('__&&&__',$formula);
$self->{'row_numbers'}->{$symb} = $row;
$self->{'maxrow'} = $1 if ($1 > $self->{'maxrow'});
}
}
sub get_row_number_from_symb {
my $self = shift;
my ($key) = @_;
($key,undef) = split('__&&&__',$key) if ($key =~ /__&&&__/);
return $self->get_row_number_from_key($key);
}
#############################################
#############################################
=pod
=item &load_cached_export_rows
Retrieves and parsers the export rows of the student spreadsheets.
These rows are saved in the courses directory in the format:
sname:sdom:studentcalc:.time => time
sname:sdom:studentcalc => ___=___Adata___;___Bdata___;___Cdata___;___ .....
=cut
#############################################
#############################################
sub load_cached_export_rows {
%Exportrows = undef;
my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets',
$ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
$ENV{'course.'.$ENV{'request.course.id'}.'.num'},undef);
my %Selected_Assess_Sheet;
if ($tmp[0] =~ /^error/) {
&Apache::lonnet::logthis('unable to read cached student export rows '.
'for course '.$ENV{'request.course.id'});
return;
}
my %tmp = @tmp;
while (my ($key,$sheetdata) = each(%tmp)) {
my ($sname,$sdom,$sheettype,$remainder) = split(':',$key);
my $student = $sname.':'.$sdom;
if ($remainder =~ /\.time/) {
$Exportrows{$student}->{'time'} = $sheetdata;
} else {
$sheetdata =~ s/^___=___//;
my @Data = split('___;___',$sheetdata);
$Exportrows{$student}->{'data'} = \@Data;
}
}
}
#############################################
#############################################
=pod
=item &save_export_data()
Writes the export data for this student to the course cache.
=cut
#############################################
#############################################
sub save_export_data {
my $self = shift;
return if ($self->temporary());
my $student = $self->{'name'}.':'.$self->{'domain'};
return if (! exists($Exportrows{$student}));
return if (! $self->is_default());
my $key = join(':',($self->{'name'},$self->{'domain'},'studentcalc')).':';
my $timekey = $key.'.time';
my $newstore = join('___;___',
@{$Exportrows{$student}->{'data'}});
$newstore = '___=___'.$newstore;
&Apache::lonnet::put('nohist_calculatedsheets',
{ $key => $newstore,
$timekey => $Exportrows{$student}->{'time'} },
$self->{'cdom'},
$self->{'cnum'});
return;
}
#############################################
#############################################
=pod
=item &export_data()
Returns the export data associated with the spreadsheet. Computes the
spreadsheet only if necessary.
=cut
#############################################
#############################################
sub export_data {
my $self = shift;
my $student = $self->{'name'}.':'.$self->{'domain'};
if (! exists($Exportrows{$student}) ||
! $self->check_expiration_time($Exportrows{$student}->{'time'})) {
$self->compute();
}
my @Data = @{$Exportrows{$student}->{'data'}};
for (my $i=0; $i<=$#Data;$i++) {
$Data[$i]="'".$Data[$i]."'" if ($Data[$i]=~/\D/ && defined($Data[$i]));
}
return @Data;
}
1;
__END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>