# # $Id: studentcalc.pm,v 1.13 2003/06/23 20:47:00 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 { &Apache::assesscalc::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 = '

'.$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 .= "

\n"; $title .= '

'.$desc."

\n"; $title .= '

'.$time.'

'; return $title; } sub parent_link { my $self = shift; my $link .= '

'. 'Course level sheet

'."\n"; return $link; } sub outsheet_html { my $self = shift; my ($r) = @_; my $importcolor = '#FFFFAA'; my $exportcolor = '#88FF88'; #################################### # 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";

END my $label_num = 0; foreach (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')){ if ($label_num<$num_uneditable) { $tableheader .='"; $label_num++; } $tableheader .="\n"; if ($self->blackout()) { $r->print('

'. 'Some computations are not available at this time.
'. 'There are problems whose status you are allowed to view.'. '

'."\n"); } else { $r->print($tableheader); # # Print out template row if (exists($ENV{'request.role.adv'}) && $ENV{'request.role.adv'}) { $r->print(''. $self->html_template_row($num_uneditable, $importcolor)."\n"); } # # Print out summary/export row $r->print(''. $self->html_export_row($exportcolor)."\n"); } $r->print("
Student Import Calculations
'; } else { $tableheader .=''; } $tableheader .="$_
Template 
Summary0
\n"); # # Prepare to output rows if (exists($ENV{'request.role.adv'}) && $ENV{'request.role.adv'}) { $tableheader =<<"END";

END } else { $tableheader =<<"END";

RowAssessment
END } foreach (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')){ if ($label_num<$num_uneditable) { $tableheader.='"; } $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("

".$Sequence->{'title'}."

\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 = ''; if ($editing_is_allowed) { $row_output .= ''; $row_output .= ''; } else { $row_output .= ''; } if ($self->blackout() && $self->{'blackout_rows'}->{$rownum}>0) { $row_output .= ''."\n"; } else { $row_output .= $self->html_row($num_uneditable,$rownum, $exportcolor,$importcolor). "\n"; } $r->print($row_output); } $r->print("
 Assessment'; } else { $tableheader.=''; } $tableheader.="$_
'.$rownum.''. ''.$resource->{'title'}.'
'; $row_output .= &assess_file_selector($rownum, $assess_filename, \@AssessFileNames). '
Go To'; $row_output .= ''.$resource->{'title'}.'Unavailable at this time
\n"); } $r->print("

\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 = '\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, ('Sequence or Folder','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__