--- loncom/interface/Attic/lonspreadsheet.pm 2002/11/15 18:59:28 1.141 +++ loncom/interface/Attic/lonspreadsheet.pm 2003/02/17 16:29:51 1.172 @@ -1,5 +1,5 @@ # -# $Id: lonspreadsheet.pm,v 1.141 2002/11/15 18:59:28 matthew Exp $ +# $Id: lonspreadsheet.pm,v 1.172 2003/02/17 16:29:51 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -50,20 +50,451 @@ built-in functions. =cut + package Apache::lonspreadsheet; use strict; use Apache::Constants qw(:common :http); use Apache::lonnet; use Apache::lonhtmlcommon; +use HTML::Entities(); + +# --------------------------------------------------------- Various form fields + +sub textfield { + my ($title,$name,$value)=@_; + return "\n<p><b>$title:</b><br>". + '<input type=text name="'.$name.'" size=80 value="'.$value.'">'; +} + +sub hiddenfield { + my ($name,$value)=@_; + return "\n".'<input type=hidden name="'.$name.'" value="'.$value.'">'; +} + +sub selectbox { + my ($title,$name,$value,%options)=@_; + my $selout="\n<p><b>$title:</b><br>".'<select name="'.$name.'">'; + foreach (sort keys(%options)) { + $selout.='<option value="'.$_.'"'; + if ($_ eq $value) { $selout.=' selected'; } + $selout.='>'.$options{$_}.'</option>'; + } + return $selout.'</select>'; +} + +my %oldsheets; +my %loadedcaches; + +# ================================================================ Main handler +# +# Interactive call to screen +# +# +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; + } + # + # Roles Checking + # + # Needs to be in a course + if (! $ENV{'request.course.fn'}) { + # Not in a course, or not allowed to modify parms + $ENV{'user.error.msg'}= + $r->uri.":opa:0:0:Cannot modify spreadsheet"; + return HTTP_NOT_ACCEPTABLE; + } + # + # Get query string for limited number of parameters + # + &Apache::loncommon::get_unprocessed_cgi + ($ENV{'QUERY_STRING'},['uname','udom','usymb','ufn','mapid','resid']); + # + # 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'})); + } + # + # Look for special assessment spreadsheets - '_feedback', etc. + # + if (($ENV{'form.usymb'}=~/^\_(\w+)/) && (!$ENV{'form.ufn'} || + $ENV{'form.ufn'} eq '' || + $ENV{'form.ufn'} eq 'default')) { + $ENV{'form.ufn'}='default_'.$1; + } + if (!$ENV{'form.ufn'} || $ENV{'form.ufn'} eq 'default') { + $ENV{'form.ufn'}='course_default_'.$sheettype; + } + # + # Interactive loading of specific sheet? + # + if (($ENV{'form.load'}) && ($ENV{'form.loadthissheet'} ne 'Default')) { + $ENV{'form.ufn'}=$ENV{'form.loadthissheet'}; + } + # + # Determine the user name and domain for the sheet. + my $aname; + my $adom; + unless ($ENV{'form.uname'}) { + $aname=$ENV{'user.name'}; + $adom=$ENV{'user.domain'}; + } else { + $aname=$ENV{'form.uname'}; + $adom=$ENV{'form.udom'}; + } + # + # 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; + # + # Header.... + # + $r->print('<html><head><title>LON-CAPA Spreadsheet</title>'); + my $nothing = "''"; + if ($ENV{'browser.type'} eq 'explorer') { + $nothing = "'javascript:void(0);'"; + } + + if ($ENV{'request.role'} !~ /^st\./) { + $r->print(<<ENDSCRIPT); +<script language="JavaScript"> + + var editwin; + + function celledit(cellname,cellformula) { + var edit_text = ''; + // cellformula may contain less-than and greater-than symbols, so + // we need to escape them? + edit_text +='<html><head><title>Cell Edit Window</title></head><body>'; + edit_text += '<form name="editwinform">'; + edit_text += '<center><h3>Cell '+cellname+'</h3>'; + edit_text += '<textarea name="newformula" cols="40" rows="6"'; + edit_text += ' wrap="off" >'+cellformula+'</textarea>'; + edit_text += '</br>'; + edit_text += '<input type="button" name="accept" value="Accept"'; + edit_text += ' onClick=\\\'javascript:'; + edit_text += 'opener.document.sheet.unewfield.value='; + edit_text += '"'+cellname+'";'; + edit_text += 'opener.document.sheet.unewformula.value='; + edit_text += 'document.editwinform.newformula.value;'; + edit_text += 'opener.document.sheet.submit();'; + edit_text += 'self.close()\\\' />'; + edit_text += ' '; + edit_text += '<input type="button" name="abort" '; + edit_text += 'value="Discard Changes"'; + edit_text += ' onClick="javascript:self.close()" />'; + edit_text += '</center></body></html>'; + + if (editwin != null && !(editwin.closed) ) { + editwin.close(); + } + + editwin = window.open($nothing,'CellEditWin','height=200,width=350,scrollbars=no,resizeable=yes,alwaysRaised=yes,dependent=yes',true); + editwin.document.write(edit_text); + } + + function changesheet(cn) { + document.sheet.unewfield.value=cn; + document.sheet.unewformula.value='changesheet'; + document.sheet.submit(); + } + + function insertrow(cn) { + document.sheet.unewfield.value='insertrow'; + document.sheet.unewformula.value=cn; + document.sheet.submit(); + } + +</script> +ENDSCRIPT + } + $r->print('</head>'.&Apache::loncommon::bodytag('Grades Spreadsheet'). + '<form action="'.$r->uri.'" name="sheet" method="post">'); + $r->print(&hiddenfield('uname',$ENV{'form.uname'}). + &hiddenfield('udom',$ENV{'form.udom'}). + &hiddenfield('usymb',$ENV{'form.usymb'}). + &hiddenfield('unewfield',''). + &hiddenfield('unewformula','')); + $r->rflush(); + # + # Full recalc? + # + # Read new sheet or modified worksheet + my $sheet=Apache::lonspreadsheet::Spreadsheet->new($aname,$adom,$sheettype,$ENV{'form.usymb'}); + if ($ENV{'form.forcerecalc'}) { + $r->print('<h4>Completely Recalculating Sheet ...</h4>'); + $sheet->complete_recalc(); + } + # + # Global directory configs + # + $sheet->includedir($r->dir_config('lonIncludes')); + # + # Check user permissions + if (($sheet->{'type'} eq 'classcalc' ) || + ($sheet->{'uname'} ne $ENV{'user.name'} ) || + ($sheet->{'udom'} ne $ENV{'user.domain'})) { + unless (&Apache::lonnet::allowed('vgr',$sheet->{'cid'})) { + $r->print('<h1>Access Permission Denied</h1>'. + '</form></body></html>'); + return OK; + } + } + # Print out user information + $r->print('<h2>'.$sheet->{'coursedesc'}.'</h2>'); + if ($sheet->{'type'} ne 'classcalc') { + $r->print('<h2>'.$sheet->gettitle().'</h2><p>'); + } + if ($sheet->{'type'} eq 'assesscalc') { + $r->print('<b>User:</b> '.$sheet->{'uname'}. + '<br /><b>Domain:</b> '.$sheet->{'udom'}.'<br />'); + } + if ($sheet->{'type'} eq 'studentcalc' || + $sheet->{'type'} eq 'assesscalc') { + $r->print('<b>Section/Group:</b>'.$sheet->{'csec'}.'</p>'); + } + # + # If a new formula had been entered, go from work copy + if ($ENV{'form.unewfield'}) { + $r->print('<h2>Modified Workcopy</h2>'); + #$ENV{'form.unewformula'}=~s/\'/\"/g; + $r->print('<p>Cell '.$ENV{'form.unewfield'}.' = <pre>'); + $r->print(&HTML::Entities::encode($ENV{'form.unewformula'}). + '</pre></p>'); + $sheet->{'filename'} = $ENV{'form.ufn'}; + $sheet->tmpread($ENV{'form.unewfield'},$ENV{'form.unewformula'}); + } elsif ($ENV{'form.saveas'}) { + $sheet->{'filename'} = $ENV{'form.ufn'}; + $sheet->tmpread(); + } else { + $sheet->readsheet($ENV{'form.ufn'}); + } + # Additional options + if ($sheet->{'type'} eq 'assesscalc') { + $r->print('<p><font size=+2>'. + '<a href="/adm/studentcalc?'. + 'uname='.$sheet->{'uname'}. + '&udom='.$sheet->{'udom'}.'">'. + 'Level up: Student Sheet</a></font></p>'); + } + if (($sheet->{'type'} eq 'studentcalc') && + (&Apache::lonnet::allowed('vgr',$sheet->{'cid'}))) { + $r->print ('<p><font size=+2><a href="/adm/classcalc">'. + 'Level up: Course Sheet</a></font></p>'); + } + # Recalc button + $r->print('<br />'. + '<input type="submit" name="forcerecalc" '. + 'value="Completely Recalculate Sheet"></p>'); + # Save dialog + if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { + my $fname=$ENV{'form.ufn'}; + $fname=~s/\_[^\_]+$//; + if ($fname eq 'default') { $fname='course_default'; } + $r->print('<input type=submit name=saveas value="Save as ...">'. + '<input type=text size=20 name=newfn value="'.$fname.'">'. + 'make default: <input type=checkbox name="makedefufn"><p>'); + } + $r->print(&hiddenfield('ufn',$sheet->{'filename'})); + # Load dialog + if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { + $r->print('<p><input type=submit name=load value="Load ...">'. + '<select name="loadthissheet">'. + '<option name="default">Default</option>'); + foreach ($sheet->othersheets()) { + $r->print('<option name="'.$_.'"'); + if ($ENV{'form.ufn'} eq $_) { + $r->print(' selected'); + } + $r->print('>'.$_.'</option>'); + } + $r->print('</select><p>'); + if ($sheet->{'type'} eq 'studentcalc') { + $sheet->setothersheets($sheet->othersheets('assesscalc')); + } + } + # + # Set up caching mechanisms + # + &Apache::lonspreadsheet::Spreadsheet::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->{'type'} eq 'classcalc') { + $r->print("Loading previously calculated student sheets ...\n"); + $r->rflush(); + &Apache::lonspreadsheet::Spreadsheet::cachedcsheets(); + } elsif ($sheet->{'type'} eq 'studentcalc') { + $r->print("Loading previously calculated assessment sheets ...\n"); + $r->rflush(); + $sheet->cachedssheets(); + } + # Update sheet, load rows + $r->print("Loaded sheet(s), updating rows ...<br>\n"); + $r->rflush(); + # + $sheet->updatesheet(); + $r->print("Updated rows, loading row data ...\n"); + $r->rflush(); + # + $sheet->loadrows($r); + $r->print("Loaded row data, calculating sheet ...<br>\n"); + $r->rflush(); + # + my $calcoutput=$sheet->calcsheet(); + $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>'); + # See if something to save + if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { + my $fname=''; + if ($ENV{'form.saveas'} && ($fname=$ENV{'form.newfn'})) { + $fname=~s/\W/\_/g; + if ($fname eq 'default') { $fname='course_default'; } + $fname.='_'.$sheet->{'type'}; + $sheet->{'filename'} = $fname; + $ENV{'form.ufn'}=$fname; + $r->print('<p>Saving spreadsheet: '. + $sheet->writesheet($ENV{'form.makedefufn'}). + '<p>'); + } + } + # + # Write the modified worksheet + $r->print('<b>Current sheet:</b> '.$sheet->{'filename'}.'</p>'); + $sheet->tmpwrite(); + if ($sheet->{'type'} eq 'assesscalc') { + $r->print('<p>Show rows with empty A column: '); + } else { + $r->print('<p>Show empty rows: '); + } + # + $r->print(&hiddenfield('userselhidden','true'). + '<input type="checkbox" name="showall" onClick="submit()"'); + # + if ($ENV{'form.showall'}) { + $r->print(' checked'); + } else { + unless ($ENV{'form.userselhidden'}) { + unless + ($ENV{'course.'.$sheet->{'cid'}.'.hideemptyrows'} eq 'yes') { + $r->print(' checked'); + $ENV{'form.showall'}=1; + } + } + } + $r->print('>'); + # + # output format select box + $r->print(' Output as <select name="output" size="1" onChange="submit()">'. + "\n"); + foreach my $mode (qw/HTML CSV Excel/) { + $r->print('<option value="'.$mode.'"'); + if ($ENV{'form.output'} eq $mode) { + $r->print(' selected '); + } + $r->print('>'.$mode.'</option>'."\n"); + } +# +# Mulit-sheet excel takes too long and does not work at all for large +# classes. Future inclusion of this option may be possible with the +# Spreadsheet::WriteExcel::Big and speed improvements. +# +# if ($sheet->{'type'} eq 'classcalc') { +# $r->print('<option value="recursive excel"'); +# if ($ENV{'form.output'} eq 'recursive excel') { +# $r->print(' selected '); +# } +# $r->print(">Multi-Sheet Excel</option>\n"); +# } + $r->print("</select>\n"); + # + if ($sheet->{'type'} eq 'classcalc') { + $r->print(' Student Status: '. + &Apache::lonhtmlcommon::StatusOptions + ($ENV{'form.Status'},'sheet')); + } + # + # Buttons to insert rows +# $r->print(<<ENDINSERTBUTTONS); +#<br> +#<input type='button' onClick='insertrow("top");' +#value='Insert Row Top'> +#<input type='button' onClick='insertrow("bottom");' +#value='Insert Row Bottom'><br> +#ENDINSERTBUTTONS + # Print out sheet + $sheet->outsheet($r); + $r->print('</form></body></html>'); + # Done + return OK; +} + +1; + +############################################################# +############################################################# +############################################################# + +package Apache::lonspreadsheet::Spreadsheet; + +use strict; +use Apache::Constants qw(:common :http); +use Apache::lonnet; use Apache::loncoursedata; use Apache::File(); use Safe; use Safe::Hole; use Opcode; use GDBM_File; +use HTML::Entities(); use HTML::TokeParser; use Spreadsheet::WriteExcel; +use Time::HiRes; + +# +# These global hashes are dependent on user, course and resource, +# and need to be initialized every time when a sheet is calculated +# +my %courseopt; +my %useropt; +my %parmhash; # # Caches for coursewide information @@ -73,58 +504,293 @@ my %Section; # # Caches for previously calculated spreadsheets # - -my %oldsheets; -my %loadedcaches; my %expiredates; # # Cache for stores of an individual user # - my $cachedassess; my %cachedstores; # -# These cache hashes need to be independent of user, resource and course -# (user and course can/should be in the keys) +# Some hashes for stats on timing and performance # +my %starttimes; +my %usedtimes; +my %numbertimes; + +# +# Directories +# +my $includedir; + +sub includedir { + my $self = shift; + $includedir = shift; +} my %spreadsheets; +#my %loadedcaches; my %courserdatas; my %userrdatas; my %defaultsheets; my %rowlabel_cache; +#my %oldsheets; + +sub complete_recalc { + my $self = shift; + undef %spreadsheets; + undef %courserdatas; + undef %userrdatas; + undef %defaultsheets; + undef %rowlabel_cache; +} + +sub get_sheet { + my $self = shift; + my $sheet_id = shift; + my $formulas; + # if we already have the file loaded and parsed, return the formulas + if (exists($self->{'sheets'}->{$sheet_id})) { + $formulas = $self->{'sheets'}->{$sheet_id}; + $self->debug('retrieved '.$sheet_id); + } else { + # load the file + # set $error and return undef if there is an error loading + # parse it + # set $error and return undef if there is an error parsing + } + return $formulas; +} # -# These global hashes are dependent on user, course and resource, -# and need to be initialized every time when a sheet is calculated +# Load previously cached student spreadsheets for this course # -my %courseopt; -my %useropt; -my %parmhash; +sub load_spreadsheet_expirationdates { + undef %expiredates; + my $cid=$ENV{'request.course.id'}; + my @tmp = &Apache::lonnet::dump('nohist_expirationdates', + $ENV{'course.'.$cid.'.domain'}, + $ENV{'course.'.$cid.'.num'}); + if (lc($tmp[0]) !~ /^error/){ + %expiredates = @tmp; + } +} +# ===================================================== Calculated sheets cache # -# Some hashes for stats on timing and performance +# Load previously cached student spreadsheets for this course # +sub cachedcsheets { + my $cid=$ENV{'request.course.id'}; + 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) { + $Apache::lonspreadsheet::oldsheets{$key} = $value; + } + } +} -my %starttimes; -my %usedtimes; -my %numbertimes; +# +# Load previously cached assessment spreadsheets for this student +# +sub cachedssheets { + my $self = shift; + my ($uname,$udom) = @_; + $uname = $uname || $self->{'uname'}; + $udom = $udom || $self->{'udom'}; + if (! exists($Apache::lonspreadsheet::loadedcaches{$uname.'_'.$udom})) { + my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets_'. + $ENV{'request.course.id'}, + $self->{'udom'}, + $self->{'uname'}); + if ($tmp[0] !~ /^error/) { + my %TempHash = @tmp; + my $count = 0; + while (my ($key,$value) = each %TempHash) { + $Apache::lonspreadsheet::oldsheets{$key} = $value; + $count++; + } + $Apache::lonspreadsheet::loadedcaches{$self->{'uname'}.'_'.$self->{'udom'}}=1; + } + } +} -# Stuff that only the screen handler can know +# ======================================================= Forced recalculation? +sub checkthis { + my ($keyname,$time)=@_; + if (! exists($expiredates{$keyname})) { + return 0; + } else { + return ($time<$expiredates{$keyname}); + } +} -my $includedir; -my $tmpdir; +sub forcedrecalc { + my ($uname,$udom,$stype,$usymb)=@_; + my $key=$uname.':'.$udom.':'.$stype.':'.$usymb; + my $time=$Apache::lonspreadsheet::oldsheets{$key.'.time'}; + if ($ENV{'form.forcerecalc'}) { return 1; } + unless ($time) { return 1; } + if ($stype eq 'assesscalc') { + my $map=(split(/___/,$usymb))[0]; + if (&checkthis('::assesscalc:',$time) || + &checkthis('::assesscalc:'.$map,$time) || + &checkthis('::assesscalc:'.$usymb,$time) || + &checkthis($uname.':'.$udom.':assesscalc:',$time) || + &checkthis($uname.':'.$udom.':assesscalc:'.$map,$time) || + &checkthis($uname.':'.$udom.':assesscalc:'.$usymb,$time)) { + return 1; + } + } else { + if (&checkthis('::studentcalc:',$time) || + &checkthis($uname.':'.$udom.':studentcalc:',$time)) { + return 1; + } + } + return 0; +} + + +################################################## +################################################## -# ============================================================================= -# ===================================== Implements an instance of a spreadsheet +=pod + +=item &parmval() + +Determine the value of a parameter. + +Inputs: $what, the parameter needed, $symb, $uname, $udom, $csec + +Returns: The value of a parameter, or '' if none. + +This function cascades through the possible levels searching for a value for +a parameter. The levels are checked in the following order: +user, course (at section level and course level), map, and lonnet::metadata. +This function uses %parmhash, which must be tied prior to calling it. +This function also requires %courseopt and %useropt to be initialized for +this user and course. + +=cut + +################################################## +################################################## +sub parmval { + my ($what,$symb,$uname,$udom,$csec)=@_; + return '' if (!$symb); + # + my $cid = $ENV{'request.course.id'}; + my $result=''; + # + my ($mapname,$id,$fn)=split(/\_\_\_/,$symb); + # Cascading lookup scheme + my $rwhat=$what; + $what =~ s/^parameter\_//; + $what =~ s/\_([^\_]+)$/\.$1/; + # + my $symbparm = $symb.'.'.$what; + my $mapparm = $mapname.'___(all).'.$what; + my $usercourseprefix = $uname.'_'.$udom.'_'.$cid; + # + my $seclevel = $usercourseprefix.'.['.$csec.'].'.$what; + my $seclevelr = $usercourseprefix.'.['.$csec.'].'.$symbparm; + my $seclevelm = $usercourseprefix.'.['.$csec.'].'.$mapparm; + # + my $courselevel = $usercourseprefix.'.'.$what; + my $courselevelr = $usercourseprefix.'.'.$symbparm; + my $courselevelm = $usercourseprefix.'.'.$mapparm; + # fourth, check user + if (defined($uname)) { + return $useropt{$courselevelr} if (defined($useropt{$courselevelr})); + return $useropt{$courselevelm} if (defined($useropt{$courselevelm})); + return $useropt{$courselevel} if (defined($useropt{$courselevel})); + } + # third, check course + if (defined($csec)) { + return $courseopt{$seclevelr} if (defined($courseopt{$seclevelr})); + return $courseopt{$seclevelm} if (defined($courseopt{$seclevelm})); + return $courseopt{$seclevel} if (defined($courseopt{$seclevel})); + } + # + return $courseopt{$courselevelr} if (defined($courseopt{$courselevelr})); + return $courseopt{$courselevelm} if (defined($courseopt{$courselevelm})); + return $courseopt{$courselevel} if (defined($courseopt{$courselevel})); + # second, check map parms + my $thisparm = $parmhash{$symbparm}; + return $thisparm if (defined($thisparm)); + # first, check default + return &Apache::lonnet::metadata($fn,$rwhat.'.default'); +} + +# +# new: Make a new spreadsheet +# +sub new { + my $this = shift; + my $class = ref($this) || $this; + # + my ($uname,$udom,$stype,$usymb)=@_; + # + my $self = { + uname => $uname, + udom => $udom, + type => $stype, + usymb => $usymb, + errorlog => '', + maxrow => '', + mapid => $ENV{'form.mapid'}, + resid => $ENV{'form.resid'}, + cid => $ENV{'request.course.id'}, + csec => $Section{$uname.':'.$udom}, + cnum => $ENV{'course.'.$ENV{'request.course.id'}.'.num'}, + cdom => $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, + chome => $ENV{'course.'.$ENV{'request.course.id'}.'.home'}, + coursefilename => $ENV{'request.course.fn'}, + coursedesc => $ENV{'course.'.$ENV{'request.course.id'}.'.description'}, + rows => [], + template_cells => [], + }; + $self->{'uhome'} = &Apache::lonnet::homeserver($uname,$udom); + # + # + $self->{'formulas'} = {}; + $self->{'constants'} = {}; + $self->{'othersheets'} = []; + $self->{'rowlabel'} = {}; + # + # + $self->{'safe'} = &initsheet($self->{'type'}); + $self->{'root'} = $self->{'safe'}->root(); + # + # Place some of the %$self items into the safe space except the safe space + # itself + my $initstring = ''; + foreach (qw/uname udom type usymb cid csec coursefilename + cnum cdom chome uhome/) { + $initstring.= qq{\$$_="$self->{$_}";}; + } + $self->{'safe'}->reval($initstring); + bless($self,$class); + return $self; +} ## ## mask - used to reside in the safe space. ## +{ + +my %memoizer; + sub mask { my ($lower,$upper)=@_; + my $key = $lower.'_'.$upper; + if (exists($memoizer{$key})) { + return $memoizer{$key}; + } $upper = $lower if (! defined($upper)); # my ($la,$ld) = ($lower=~/([A-Za-z]|\*)(\d+|\*)/); @@ -194,81 +860,16 @@ sub mask { } } } - return '^'.$alpha.$num."\$"; + my $expression ='^'.$alpha.$num."\$"; + $memoizer{$key} = $expression; + return $expression; } -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 = ''; - +} +sub add_hash_to_safe { + my $self = shift; + my $code = <<'END'; #------------------------------------------------------- =item UWCALC(hashname,modules,units,date) @@ -535,6 +1136,75 @@ sub HASH { } return $Values[-1]; } +END + $self->{'safe'}->reval($code); + return; +} + +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(\&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. +undef %c; # Holds the constants for a sheet. In the assessment + # sheets, this is the A column. Used in &MINPARM, &MAXPARM, &expandnamed, + # &sett, and &constants. There is no &getconstants. + # &constants 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; +$type = ''; + +# filename/reference of the sheet +$filename = ''; + +# user data +$uname = ''; +$uhome = ''; +$udom = ''; + +# course data + +$csec = ''; +$chome= ''; +$cnum = ''; +$cdom = ''; +$cid = ''; +$coursefilename = ''; + +# symb + +$usymb = ''; + +# error messages +$errormsg = ''; #------------------------------------------------------- @@ -546,14 +1216,14 @@ returns the number of items in the range #------------------------------------------------------- sub NUM { - my $mask=mask(@_); + my $mask=&mask(@_); my $num= $#{@{grep(/$mask/,keys(%sheet_values))}}+1; return $num; } sub BIN { my ($low,$high,$lower,$upper)=@_; - my $mask=mask($lower,$upper); + my $mask=&mask($lower,$upper); my $num=0; foreach (grep /$mask/,keys(%sheet_values)) { if (($sheet_values{$_}>=$low) && ($sheet_values{$_}<=$high)) { @@ -574,7 +1244,7 @@ returns the sum of items in the range. #------------------------------------------------------- sub SUM { - my $mask=mask(@_); + my $mask=&mask(@_); my $sum=0; foreach (grep /$mask/,keys(%sheet_values)) { $sum+=$sheet_values{$_}; @@ -592,7 +1262,8 @@ compute the average of the items in the #------------------------------------------------------- sub MEAN { - my $mask=mask(@_); + my $mask=&mask(@_); +# $errorlog.='(mask = '.$mask.' )'; my $sum=0; my $num=0; foreach (grep /$mask/,keys(%sheet_values)) { @@ -616,7 +1287,8 @@ compute the standard deviation of the it #------------------------------------------------------- sub STDDEV { - my $mask=mask(@_); + my $mask=&mask(@_); +# $errorlog.='(mask = '.$mask.' )'; my $sum=0; my $num=0; foreach (grep /$mask/,keys(%sheet_values)) { $sum+=$sheet_values{$_}; @@ -641,7 +1313,7 @@ compute the product of the items in the #------------------------------------------------------- sub PROD { - my $mask=mask(@_); + my $mask=&mask(@_); my $prod=1; foreach (grep /$mask/,keys(%sheet_values)) { $prod*=$sheet_values{$_}; @@ -659,7 +1331,7 @@ compute the maximum of the items in the #------------------------------------------------------- sub MAX { - my $mask=mask(@_); + my $mask=&mask(@_); my $max='-'; foreach (grep /$mask/,keys(%sheet_values)) { unless ($max) { $max=$sheet_values{$_}; } @@ -678,7 +1350,7 @@ compute the minimum of the items in the #------------------------------------------------------- sub MIN { - my $mask=mask(@_); + my $mask=&mask(@_); my $min='-'; foreach (grep /$mask/,keys(%sheet_values)) { unless ($max) { $max=$sheet_values{$_}; } @@ -701,7 +1373,7 @@ compute the sum of the largest 'num' ite #------------------------------------------------------- sub SUMMAX { my ($num,$lower,$upper)=@_; - my $mask=mask($lower,$upper); + my $mask=&mask($lower,$upper); my @inside=(); foreach (grep /$mask/,keys(%sheet_values)) { push (@inside,$sheet_values{$_}); @@ -726,7 +1398,7 @@ compute the sum of the smallest 'num' it #------------------------------------------------------- sub SUMMIN { my ($num,$lower,$upper)=@_; - my $mask=mask($lower,$upper); + my $mask=&mask($lower,$upper); my @inside=(); foreach (grep /$mask/,keys(%sheet_values)) { $inside[$#inside+1]=$sheet_values{$_}; @@ -785,20 +1457,58 @@ sub MAXPARM { return $max; } -#-------------------------------------------------------- +sub calc { +# $errorlog .= "\%t has ".(keys(%t))." keys\n"; + %sheet_values = %t; # copy %t into %sheet_values. +# $errorlog .= "\%sheet_values has ".(keys(%sheet_values))." keys\n"; + my $notfinished=1; + my $lastcalc=''; + my $depth=0; + while ($notfinished) { + $notfinished=0; + while (my ($cell,$value) = each(%t)) { + my $old=$sheet_values{$cell}; + $sheet_values{$cell}=eval $value; + if ($@) { + undef %sheet_values; + return $cell.': '.$@; + } + if ($sheet_values{$cell} ne $old) { + $notfinished=1; + $lastcalc=$cell; + } + } + $depth++; + if ($depth>100) { + undef %sheet_values; + return $lastcalc.': Maximum calculation depth exceeded'; + } + } + return ''; +} + +# ------------------------------------------- End of "Inside of the safe space" +ENDDEFS + $safeeval->reval($code); + return $safeeval; +} + + +# +# expandnamed used to reside in the safe space +# sub expandnamed { + my $self = shift; my $expression=shift; if ($expression=~/^\&/) { my ($func,$var,$formula)=($expression=~/^\&(\w+)\(([^\;]+)\;(.*)\)/); my @vars=split(/\W+/,$formula); my %values=(); - undef %values; - foreach ( @vars ) { - my $varname=$_; + foreach my $varname ( @vars ) { if ($varname=~/\D/) { $formula=~s/$varname/'$c{\''.$varname.'\'}'/ge; $varname=~s/$var/\(\\w\+\)/g; - foreach (keys(%c)) { + foreach (keys(%{$self->{'constants'}})) { if ($_=~/$varname/) { $values{$1}=1; } @@ -828,18 +1538,21 @@ sub expandnamed { my @matches = (); $#matches = -1; study $expression; - foreach $parameter (keys(%c)) { + my $parameter; + foreach $parameter (keys(%{$self->{'constants'}})) { push @matches,$parameter if ($parameter =~ /$expression/); } if (scalar(@matches) == 0) { $returnvalue = 'unmatched parameter: '.$parameter; } elsif (scalar(@matches) == 1) { + # why do we not do this lookup here, instead of delaying it? $returnvalue = '$c{\''.$matches[0].'\'}'; } elsif (scalar(@matches) > 0) { # more than one match. Look for a concise one $returnvalue = "'non-unique parameter name : $expression'"; foreach (@matches) { if (/^$expression$/) { + # why do we not do this lookup here? $returnvalue = '$c{\''.$_.'\'}'; } } @@ -852,27 +1565,26 @@ sub expandnamed { } } +# +# sett used to reside in the safe space +# sub sett { - %t=(); + my $self = shift; + my %t=(); my $pattern=''; - if ($sheettype eq 'assesscalc') { + if ($self->{'type'} eq 'assesscalc') { $pattern='A'; } else { $pattern='[A-Z]'; } # Deal with the template row - foreach (keys(%f)) { - next if ($_!~/template\_(\w)/); - my $col=$1; + foreach my $col ($self->template_cells()) { next if ($col=~/^$pattern/); - foreach (keys(%f)) { - next if ($_!~/A(\d+)/); - my $trow=$1; - next if (! $trow); + foreach my $trow ($self->rows()) { # Get the name of this cell my $lb=$col.$trow; # Grab the template declaration - $t{$lb}=$f{'template_'.$col}; + $t{$lb}=$self->formula('template_'.$col); # Replace '#' with the row number $t{$lb}=~s/\#/$trow/g; # Replace '....' with ',' @@ -880,236 +1592,405 @@ sub sett { # Replace 'A0' with the value from 'A0' $t{$lb}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g; # Replace parameters - $t{$lb}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge; + $t{$lb}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge; } } # Deal with the normal cells - foreach (keys(%f)) { - if (exists($f{$_}) && ($_!~/template\_/)) { - my $matches=($_=~/^$pattern(\d+)/); - if (($matches) && ($1)) { - unless ($f{$_}=~/^\!/) { - $t{$_}=$c{$_}; - } - } else { - $t{$_}=$f{$_}; - $t{$_}=~s/\.\.+/\,/g; - $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g; - $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge; + foreach ($self->formulas_keys()) { + next if ($_=~/template\_/); + if (($_=~/^$pattern(\d+)/) && ($1)) { + if ($self->formula($_) !~ /^\!/) { + $t{$_}=$self->{'constants'}->{$_}; } + } else { + $t{$_}=$self->formula($_); + $t{$_}=~s/\.\.+/\,/g; + $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g; + $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge; } } # For inserted lines, [B-Z] is also valid - unless ($sheettype eq 'assesscalc') { - foreach (keys(%f)) { - if ($_=~/[B-Z](\d+)/) { - if ($f{'A'.$1}=~/^[\~\-]/) { - $t{$_}=$f{$_}; - $t{$_}=~s/\.\.+/\,/g; - $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g; - $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge; - } - } - } + if ($self->{'type'} ne 'assesscalc') { + foreach ($self->formulas_keys()) { + next if ($_ !~ /[B-Z](\d+)/); + next if ($self->formula('A'.$1) !~ /^[\~\-]/); + $t{$_}=$self->formula($_); + $t{$_}=~s/\.\.+/\,/g; + $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g; + $t{$_}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge; + } } # For some reason 'A0' gets special treatment... This seems superfluous # but I imagine it is here for a reason. - $t{'A0'}=$f{'A0'}; + $t{'A0'}=$self->formula('A0'); $t{'A0'}=~s/\.\.+/\,/g; $t{'A0'}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$sheet_values\{\'$2\'\}/g; - $t{'A0'}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.&expandnamed($2)/ge; + $t{'A0'}=~s/(^|[^\"\'])\[([^\]]+)\]/$1.$self->expandnamed($2)/ge; + # Put %t into the safe space + %{$self->{'safe'}->varglob('t')}=%t; } -sub calc { - undef %sheet_values; - &sett(); - my $notfinished=1; - my $lastcalc=''; - my $depth=0; - while ($notfinished) { - $notfinished=0; - foreach (keys(%t)) { - #$errorlog .= "$_:".$t{$_}; - my $old=$sheet_values{$_}; - $sheet_values{$_}=eval $t{$_}; - if ($@) { - undef %sheet_values; - return $_.': '.$@; - } - if ($sheet_values{$_} ne $old) { $notfinished=1; $lastcalc=$_; } - #$errorlog .= ":".$sheet_values{$_}."\n"; - } - $depth++; - if ($depth>100) { - undef %sheet_values; - return $lastcalc.': Maximum calculation depth exceeded'; - } - } - return ''; -} - -# ------------------------------------------- End of "Inside of the safe space" -ENDDEFS - $safeeval->reval($code); - return $safeeval; -} +########################################### +### Row output routines ### +########################################### # -# +# get_row: Produce output row n from sheet by calling the appropriate routine # +sub get_row { + my $self = shift; + my ($n) = @_; + my ($rowlabel,@rowdata); + if ($n eq '-') { + ($rowlabel,@rowdata) = $self->templaterow(); + } elsif ($self->{'type'} eq 'studentcalc') { + ($rowlabel,@rowdata) = $self->outrowassess($n); + } else { + ($rowlabel,@rowdata) = $self->outrow($n); + } + return ($rowlabel,@rowdata); +} + sub templaterow { - my $sheet = shift; + my $self = shift; my @cols=(); - 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=$sheet->{'f'}->{'template_'.$_}; - $fm=~s/[\'\"]/\&\#34;/g; + my $rowlabel = 'Template</td><td> '; + foreach my $n ('A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m', + 'n','o','p','q','r','s','t','u','v','w','x','y','z') { push(@cols,{ name => 'template_'.$_, - formula => $fm, - value => $fm }); + formula => $self->formula('template_'.$n), + value => $self->value('template_'.$n) }); } return ($rowlabel,@cols); } sub outrowassess { + my $self = shift; # $n is the current row number - my ($sheet,$n) = @_; + my ($n) = @_; my @cols=(); my $rowlabel=''; if ($n) { - my ($usy,$ufn)=split(/__&&&\__/,$sheet->{'f'}->{'A'.$n}); - if (exists($sheet->{'rowlabel'}->{$usy})) { - $rowlabel = $sheet->{'rowlabel'}->{$usy}; + my ($usy,$ufn)=split(/__&&&\__/,$self->formula('A'.$n)); + if (exists($self->{'rowlabel'}->{$usy})) { + # This is dumb, but we need the information when we output + # the html version of the studentcalc spreadsheet for the + # links to the assesscalc sheets. + $rowlabel = $self->{'rowlabel'}->{$usy}.':'. + &Apache::lonnet::escape($ufn); } else { $rowlabel = ''; } + } elsif ($ENV{'request.role'} =~ /^st\./) { + $rowlabel = 'Summary</td><td>0'; } else { - $rowlabel = 'Export'; + $rowlabel = 'Export</td><td>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') { - my $fm=$sheet->{'f'}->{$_.$n}; - $fm=~s/[\'\"]/\&\#34;/g; push(@cols,{ name => $_.$n, - formula => $fm, - value => $sheet->{'values'}->{$_.$n}}); + formula => $self->formula($_.$n), + value => $self->value($_.$n)}); } return ($rowlabel,@cols); } sub outrow { - my ($sheet,$n)=@_; + my $self = shift; + my ($n)=@_; my @cols=(); my $rowlabel; if ($n) { - $rowlabel = $sheet->{'rowlabel'}->{$sheet->{'f'}->{'A'.$n}}; + $rowlabel = $self->{'rowlabel'}->{$self->formula('A'.$n)}; } else { - if ($sheet->{'sheettype'} eq 'classcalc') { - $rowlabel = 'Summary'; + if ($self->{'type'} eq 'classcalc') { + $rowlabel = 'Summary</td><td>0'; } else { - $rowlabel = 'Export'; + $rowlabel = 'Export</td><td>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') { - my $fm=$sheet->{'f'}->{$_.$n}; - $fm=~s/[\'\"]/\&\#34;/g; push(@cols,{ name => $_.$n, - formula => $fm, - value => $sheet->{'values'}->{$_.$n}}); + formula => $self->formula($_.$n), + value => $self->value($_.$n)}); } return ($rowlabel,@cols); } -# ------------------------------------------------ Add or change formula values -sub setformulas { - my ($sheet)=shift; - %{$sheet->{'safe'}->varglob('f')}=%{$sheet->{'f'}}; +######################################################## +#### Spreadsheet calculation methods ##### +######################################################## +# +# calcsheet: makes all the calls to compute the spreadsheet. +# +sub calcsheet { + my $self = shift; + $self->sync_safe_space(); + $self->clear_errorlog(); + $self->sett(); + my $result = $self->{'safe'}->reval('&calc();'); + %{$self->{'values'}} = %{$self->{'safe'}->varglob('sheet_values')}; +# $self->logthis($self->get_errorlog()); + return $result; +} + +## +## sync_safe_space: Called by calcsheet to make sure all the data we +# need to calculate is placed into the safe space +## +sub sync_safe_space { + my $self = shift; + # Inside the safe space 'formulas' has a diabolical alter-ego named 'f'. + %{$self->{'safe'}->varglob('f')}=%{$self->{'formulas'}}; + # 'constants' leads a peaceful hidden life of 'c'. + %{$self->{'safe'}->varglob('c')}=%{$self->{'constants'}}; + # 'othersheets' hides as 'os', a disguise few can penetrate. + @{$self->{'safe'}->varglob('os')}=@{$self->{'othersheets'}}; +} + +## +## Retrieve the error log from the safe space (used for debugging) +## +sub get_errorlog { + my $self = shift; + $self->{'errorlog'} = ${$self->{'safe'}->varglob('errorlog')}; + return $self->{'errorlog'}; +} + +## +## Clear the error log inside the safe space +## +sub clear_errorlog { + my $self = shift; + ${$self->{'safe'}->varglob('errorlog')} = ''; + $self->{'errorlog'} = ''; } -# ------------------------------------------------ Add or change formula values -sub setconstants { - my ($sheet)=shift; + +######################################################## +#### Spreadsheet content retrieval/setting methods ##### +######################################################## +## +## constants: either set or get the constants +## +## +sub constants { + my $self=shift; my ($constants) = @_; - if (! ref($constants)) { - my %tmp = @_; - $constants = \%tmp; + if (defined($constants)) { + if (! ref($constants)) { + my %tmp = @_; + $constants = \%tmp; + } + $self->{'constants'} = $constants; + return; + } else { + return %{$self->{'constants'}}; + } +} + +## +## formulas: either set or get the formulas +## +sub formulas { + my $self=shift; + my ($formulas) = @_; + if (defined($formulas)) { + if (! ref($formulas)) { + my %tmp = @_; + $formulas = \%tmp; + } + $self->{'formulas'} = $formulas; + $self->{'rows'} = []; + $self->{'template_cells'} = []; + return; + } else { + return %{$self->{'formulas'}}; } - $sheet->{'constants'} = $constants; - return %{$sheet->{'safe'}->varglob('c')}=%{$sheet->{'constants'}}; } -# --------------------------------------------- Set names of other spreadsheets -sub setothersheets { - my $sheet = shift; - my @othersheets = @_; - $sheet->{'othersheets'} = \@othersheets; - @{$sheet->{'safe'}->varglob('os')}=@othersheets; +## +## formulas_keys: Return the keys to the formulas hash. +## +sub formulas_keys { + my $self = shift; + my @keys = keys(%{$self->{'formulas'}}); + return keys(%{$self->{'formulas'}}); +} + +## +## formula: Return the formula for a given cell in the spreadsheet +## returns '' if the cell does not have a formula or does not exist +## +sub formula { + my $self = shift; + my $cell = shift; + if (defined($cell) && exists($self->{'formulas'}->{$cell})) { + return $self->{'formulas'}->{$cell}; + } + return ''; +} + +## +## logthis: write the input to lonnet.log +## +sub logthis { + my $self = shift; + my $message = shift; + &Apache::lonnet::logthis($self->{'type'}.':'. + $self->{'uname'}.':'.$self->{'udom'}.':'. + $message); return; } -# ------------------------------------------------ Add or change formula values -sub setrowlabels { - my $sheet=shift; - my ($rowlabel) = @_; - if (! ref($rowlabel)) { - my %tmp = @_; - $rowlabel = \%tmp; +## +## dump_formulas_to_log: makes lonnet.log huge... +## +sub dump_formulas_to_log { + my $self =shift; + $self->logthis("Spreadsheet formulas"); + $self->logthis("--------------------------------------------------------"); + while (my ($cell, $formula) = each(%{$self->{'formulas'}})) { + $self->logthis(' '.$cell.' = '.$formula); + } + $self->logthis("--------------------------------------------------------");} + +## +## value: returns the computed value of a particular cell +## +sub value { + my $self = shift; + my $cell = shift; + if (defined($cell) && exists($self->{'values'}->{$cell})) { + return $self->{'values'}->{$cell}; } - $sheet->{'rowlabel'}=$rowlabel; + return ''; } -# ------------------------------------------------------- Calculate spreadsheet -sub calcsheet { - my $sheet=shift; - my $result = $sheet->{'safe'}->reval('&calc();'); - %{$sheet->{'values'}} = %{$sheet->{'safe'}->varglob('sheet_values')}; - return $result; +## +## dump_values_to_log: makes lonnet.log huge... +## +sub dump_values_to_log { + my $self =shift; + $self->logthis("Spreadsheet Values"); + $self->logthis("--------------------------------------------------------"); + while (my ($cell, $value) = each(%{$self->{'values'}})) { + $self->logthis(' '.$cell.' = '.$value); + } + $self->logthis("--------------------------------------------------------");} + +## +## Yet another debugging function +## +sub dump_hash_to_log { + my $self= shift(); + my %tmp = @_; + if (@_<2) { + %tmp = %{$_[0]}; + } + $self->logthis('---------------------------- (entries end with ":"'); + while (my ($key,$val) = each (%tmp)) { + $self->logthis($key.' = '.$val.':'); + } + $self->logthis('---------------------------- (entries end with ":"'); +} + +################################ +## Helper functions ## +################################ +## +## rebuild_stats: rebuilds the rows and template_cells arrays +## +sub rebuild_stats { + my $self = shift; + $self->{'rows'}=[]; + $self->{'template_cells'}=[]; + foreach my $cell($self->formulas_keys()) { + push(@{$self->{'rows'}},$1) if ($cell =~ /^A(\d+)/ && $1 != 0); + push(@{$self->{'template_cells'}},$1) if ($cell =~ /^template_(\w+)/); + } + return; } -# ---------------------------------------------------------------- Get formulas -# Return a copy of the formulas -sub getformulas { - my $sheet = shift; - return %{$sheet->{'safe'}->varglob('f')}; +## +## template_cells returns a list of the cells defined in the template row +## +sub template_cells { + my $self = shift; + $self->rebuild_stats() if (!@{$self->{'template_cells'}}); + return @{$self->{'template_cells'}}; } -sub geterrorlog { - my $sheet = shift; - return ${$sheet->{'safe'}->varglob('errorlog')}; +## +## rows returns a list of the names of cells defined in the A column +## +sub rows { + my $self = shift; + $self->rebuild_stats() if (!@{$self->{'rows'}}); + return @{$self->{'rows'}}; } +## +## Sigh.... +## +sub setothersheets { + my $self = shift; + my @othersheets = @_; + $self->{'othersheets'} = \@othersheets; +} + +## +## rowlabels: get or set the rowlabels hash from the spreadsheet. +## +sub rowlabels { + my $self = shift; + my ($rowlabel) = @_; + if (defined($rowlabel)) { + if (! ref($rowlabel)) { + my %tmp = @_; + $rowlabel = \%tmp; + } + $self->{'rowlabel'}=$rowlabel; + return; + } else { + return %{$self->{'rowlabel'}} if (defined($self->{'rowlabel'})); + } +} + +## +## gettitle: returns a title for the spreadsheet. +## sub gettitle { - my $sheet = shift; - if ($sheet->{'sheettype'} eq 'classcalc') { - return $sheet->{'coursedesc'}; - } elsif ($sheet->{'sheettype'} eq 'studentcalc') { - return 'Grades for '.$sheet->{'uname'}.'@'.$sheet->{'udom'}; - } elsif ($sheet->{'sheettype'} eq 'assesscalc') { - if (($sheet->{'usymb'} eq '_feedback') || - ($sheet->{'usymb'} eq '_evaluation') || - ($sheet->{'usymb'} eq '_discussion') || - ($sheet->{'usymb'} eq '_tutoring')) { - my $title = $sheet->{'usymb'}; + my $self = shift; + if ($self->{'type'} eq 'classcalc') { + return $self->{'coursedesc'}; + } elsif ($self->{'type'} eq 'studentcalc') { + return 'Grades for '.$self->{'uname'}.'@'.$self->{'udom'}; + } elsif ($self->{'type'} eq 'assesscalc') { + if (($self->{'usymb'} eq '_feedback') || + ($self->{'usymb'} eq '_evaluation') || + ($self->{'usymb'} eq '_discussion') || + ($self->{'usymb'} eq '_tutoring')) { + my $title = $self->{'usymb'}; $title =~ s/^_//; $title = ucfirst($title); return $title; } - return if (! defined($sheet->{'mapid'}) || - $sheet->{'mapid'} !~ /^\d+$/); - my $mapid = $sheet->{'mapid'}; - return if (! defined($sheet->{'resid'}) || - $sheet->{'resid'} !~ /^\d+$/); - my $resid = $sheet->{'resid'}; + return if (! defined($self->{'mapid'}) || + $self->{'mapid'} !~ /^\d+$/); + my $mapid = $self->{'mapid'}; + return if (! defined($self->{'resid'}) || + $self->{'resid'} !~ /^\d+$/); + my $resid = $self->{'resid'}; my %course_db; - tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db', + tie(%course_db,'GDBM_File',$self->{'coursefilename'}.'.db', &GDBM_READER(),0640); return if (! tied(%course_db)); my $key = 'title_'.$mapid.'.'.$resid; @@ -1117,86 +1998,93 @@ sub gettitle { if (exists($course_db{$key})) { $title = $course_db{$key}; } else { - $title = $sheet->{'usymb'}; + $title = $self->{'usymb'}; } untie (%course_db); return $title; } } -# ----------------------------------------------------- Get value of $f{'A'.$n} -sub getfa { - my $sheet = shift; - my ($n)=@_; - return $sheet->{'safe'}->reval('$f{"A'.$n.'"}'); -} - -# ------------------------------------------------------------- Export of A-row +# +# Export of A-row +# sub exportdata { - my $sheet=shift; + my $self=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,''); - } + push(@exportarray,$self->value($_.'0')); } return @exportarray; } - - +## +## update_student_sheet: experimental function +## sub update_student_sheet{ - my ($sheet,$c) = @_; + my $self = shift; + my ($r,$c) = @_; # Load in the studentcalc sheet - &readsheet($sheet,'default_studentcalc'); + $self->readsheet('default_studentcalc'); # Determine the structure (contained assessments, etc) of the sheet - &updatesheet($sheet); + $self->updatesheet(); # Load in the cached sheets for this student - &cachedssheets($sheet); + $self->cachedssheets(); # Load in the (possibly cached) data from the assessment sheets - &loadstudent($sheet,$c); + $self->loadstudent($r,$c); # Compute the sheet - &calcsheet($sheet); + $self->calcsheet(); } -# ========================================================== End of Spreadsheet -# ============================================================================= # -# Procedures for spreadsheet output +# sort_indicies: returns an ordered list of the rows of the spreadsheet # -# --------------------------------------------- Produce output row n from sheet - -sub get_row { - my ($sheet,$n) = @_; - my ($rowlabel,@rowdata); - if ($n eq '-') { - ($rowlabel,@rowdata) = &templaterow($sheet); - } elsif ($sheet->{'sheettype'} eq 'studentcalc') { - ($rowlabel,@rowdata) = &outrowassess($sheet,$n); - } else { - ($rowlabel,@rowdata) = &outrow($sheet,$n); - } - return ($rowlabel,@rowdata); -} - -######################################################################## -######################################################################## sub sort_indicies { - my $sheet = shift; - # - # Sort the rows in some manner - # - my @sortby=(); + my $self = shift; my @sortidx=(); - # Skip row 0 - for (my $row=1;$row<=$sheet->{'maxrow'};$row++) { - push (@sortby, $sheet->{'safe'}->reval('$f{"A'.$row.'"}')); - push (@sortidx, $row); + # + if ($self->{'type'} eq 'classcalc') { + my @sortby=(undef); + # Skip row 0 + for (my $row=1;$row<=$self->{'maxrow'};$row++) { + my (undef,$sname,$sdom,$fullname,$section,$id) = + split(':',$self->{'rowlabel'}->{$self->formula('A'.$row)}); + push (@sortby, lc($fullname)); + push (@sortidx, $row); + } + @sortidx = sort { $sortby[$a] cmp $sortby[$b]; } @sortidx; + } elsif ($self->{'type'} eq 'studentcalc') { + my @sortby1=(undef); + my @sortby2=(undef); + # Skip row 0 + for (my $row=1;$row<=$self->{'maxrow'};$row++) { + my ($key,undef) = split(/__&&&\__/,$self->formula('A'.$row)); + my $rowlabel = $self->{'rowlabel'}->{$key}; + my (undef,$symb,$mapid,$resid,$title,$ufn) = + split(':',$rowlabel); + $ufn = &Apache::lonnet::unescape($ufn); + $symb = &Apache::lonnet::unescape($symb); + $title = &Apache::lonnet::unescape($title); + my ($sequence) = ($symb =~ /\/([^\/]*\.sequence)/); + if ($sequence eq '') { + $sequence = $symb; + } + push (@sortby1, $sequence); + push (@sortby2, $title); + push (@sortidx, $row); + } + @sortidx = sort { $sortby1[$a] cmp $sortby1[$b] || + $sortby2[$a] cmp $sortby2[$b] } @sortidx; + } else { + my @sortby=(undef); + # Skip row 0 + $self->sync_safe_space(); + for (my $row=1;$row<=$self->{'maxrow'};$row++) { + push (@sortby, $self->{'safe'}->reval('$f{"A'.$row.'"}')); + push (@sortidx, $row); + } + @sortidx = sort { $sortby[$a] cmp $sortby[$b]; } @sortidx; } - @sortidx=sort { lc($sortby[$a]) cmp lc($sortby[$b]); } @sortidx; return @sortidx; } @@ -1225,34 +2113,45 @@ sub html_editable_cell { if ($formula ne '') { $value = '<i>undefined value</i>'; } - } - if ($value =~ /^\s*$/ ) { + } elsif ($value =~ /^\s*$/ ) { $value = '<font color="'.$bgcolor.'">#</font>'; + } else { + $value = &HTML::Entities::encode($value) if ($value !~/ /); } + # Make the formula safe for outputting + $formula =~ s/\'/\"/g; + # The formula will be parsed by the browser *twice* before being + # displayed to the user for editing. + $formula = &HTML::Entities::encode(&HTML::Entities::encode($formula)); + # Escape newlines so they make it into the edit window $formula =~ s/\n/\\n/gs; - $result .= '<a href="javascript:celledit(\''. - $name.'\',\''.$formula.'\');">'.$value.'</a>'; + # Glue everything together + $result .= "<a href=\"javascript:celledit(\'". + $name."','".$formula."');\">".$value."</a>"; return $result; } sub html_uneditable_cell { my ($cell,$bgcolor) = @_; my $value = (defined($cell) ? $cell->{'value'} : ''); + $value = &HTML::Entities::encode($value) if ($value !~/ /); return ' '.$value.' '; } sub outsheet_html { - my ($sheet,$r) = @_; + my $self = shift; + my ($r) = @_; my ($num_uneditable,$realm,$row_type); - if ($sheet->{'sheettype'} eq 'assesscalc') { + my $requester_is_student = ($ENV{'request.role'} =~ /^st\./); + if ($self->{'type'} eq 'assesscalc') { $num_uneditable = 1; $realm = 'Assessment'; $row_type = 'Item'; - } elsif ($sheet->{'sheettype'} eq 'studentcalc') { + } elsif ($self->{'type'} eq 'studentcalc') { $num_uneditable = 26; $realm = 'User'; $row_type = 'Assessment'; - } elsif ($sheet->{'sheettype'} eq 'classcalc') { + } elsif ($self->{'type'} eq 'classcalc') { $num_uneditable = 26; $realm = 'Course'; $row_type = 'Student'; @@ -1266,7 +2165,7 @@ sub outsheet_html { my $tabledata =<<"END"; <table border="2"> <tr> - <th colspan="1" rowspan="2"><font size="+2">$realm</font></th> + <th colspan="2" rowspan="2"><font size="+2">$realm</font></th> <td bgcolor="#FFDDDD" colspan="$num_uneditable"> <b><font size="+1">Import</font></b></td> <td colspan="$num_left"> @@ -1288,34 +2187,39 @@ END #################################### # Print out template row #################################### - my ($rowlabel,@rowdata) = &get_row($sheet,'-'); - my $row_html = '<tr><td>'.&format_html_rowlabel($rowlabel).'</td>'; - my $num_cols_output = 0; - foreach my $cell (@rowdata) { - if ($num_cols_output++ < $num_uneditable) { - $row_html .= '<td bgcolor="#FFDDDD">'; - $row_html .= &html_uneditable_cell($cell,'#FFDDDD'); - } else { - $row_html .= '<td bgcolor="#EOFFDD">'; - $row_html .= &html_editable_cell($cell,'#E0FFDD'); + my ($num_cols_output,$row_html,$rowlabel,@rowdata); + + if (! $requester_is_student) { + ($rowlabel,@rowdata) = $self->get_row('-'); + $row_html = '<tr><td>'.$self->format_html_rowlabel($rowlabel).'</td>'; + $num_cols_output = 0; + foreach my $cell (@rowdata) { + if ($requester_is_student || + $num_cols_output++ < $num_uneditable) { + $row_html .= '<td bgcolor="#FFDDDD">'; + $row_html .= &html_uneditable_cell($cell,'#FFDDDD'); + } else { + $row_html .= '<td bgcolor="#EOFFDD">'; + $row_html .= &html_editable_cell($cell,'#E0FFDD'); + } + $row_html .= '</td>'; } - $row_html .= '</td>'; + $row_html.= "</tr>\n"; + $r->print($row_html); } - $row_html.= "</tr>\n"; - $r->print($row_html); #################################### # Print out summary/export row #################################### - my ($rowlabel,@rowdata) = &get_row($sheet,'0'); - $row_html = '<tr><td>'.&format_html_rowlabel($rowlabel).'</td>'; + ($rowlabel,@rowdata) = $self->get_row('0'); + $row_html = '<tr><td>'.$self->format_html_rowlabel($rowlabel).'</td>'; $num_cols_output = 0; foreach my $cell (@rowdata) { - if ($num_cols_output++ < 26) { + if ($num_cols_output++ < 26 && ! $requester_is_student) { $row_html .= '<td bgcolor="#CCCCFF">'; $row_html .= &html_editable_cell($cell,'#CCCCFF'); } else { $row_html .= '<td bgcolor="#DDCCFF">'; - $row_html .= &html_uneditable_cell(undef,'#CCCCFF'); + $row_html .= &html_uneditable_cell($cell,'#CCCCFF'); } $row_html .= '</td>'; } @@ -1325,17 +2229,18 @@ END #################################### # Prepare to output rows #################################### - my @Rows = &sort_indicies($sheet); + my @Rows = $self->sort_indicies(); # # 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); + my ($rowlabel,@rowdata) = $self->get_row($rownum); next if ($rowlabel =~ /^\s*$/); - next if (($sheet->{'sheettype'} eq 'assesscalc') && + next if (($self->{'type'} eq 'assesscalc') && (! $ENV{'form.showall'}) && ($rowdata[0]->{'value'} =~ /^\s*$/)); - if ($sheet->{'sheettype'} =~ /^(studentcalc|classcalc)$/) { + if (! $ENV{'form.showall'} && + $self->{'type'} =~ /^(studentcalc|classcalc)$/) { my $row_is_empty = 1; foreach my $cell (@rowdata) { if ($cell->{'value'} !~ /^\s*$/) { @@ -1343,7 +2248,7 @@ END last; } } - next if $row_is_empty; + next if ($row_is_empty); } # my $defaultbg='#E0FF'; @@ -1351,26 +2256,28 @@ END my $row_html ="\n".'<tr><td><b><font size=+1>'.$rownum. '</font></b></td>'; # - if ($sheet->{'sheettype'} eq 'classcalc') { - $row_html.='<td>'.&format_html_rowlabel($rowlabel).'</td>'; + if ($self->{'type'} eq 'classcalc') { + $row_html.='<td>'.$self->format_html_rowlabel($rowlabel).'</td>'; # 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.='<td>'.&format_html_rowlabel($rowlabel); + # Nope, that is already done for us in format_html_rowlabel + # (for now) + } elsif ($self->{'type'} eq 'studentcalc') { + my $ufn = (split(/:/,$rowlabel))[5]; + $row_html.='<td>'.$self->format_html_rowlabel($rowlabel); $row_html.= '<br>'. '<select name="sel_'.$rownum.'" '. 'onChange="changesheet('.$rownum.')">'. '<option name="default">Default</option>'; - foreach (@{$sheet->{'othersheets'}}) { + foreach (@{$self->{'othersheets'}}) { $row_html.='<option name="'.$_.'"'; - #if ($ufn eq $_) { - # $row_html.=' selected'; - #} + if ($ufn eq $_) { + $row_html.=' selected'; + } $row_html.='>'.$_.'</option>'; } $row_html.='</select></td>'; - } elsif ($sheet->{'sheettype'} eq 'assesscalc') { - $row_html.='<td>'.&format_html_rowlabel($rowlabel).'</td>'; + } elsif ($self->{'type'} eq 'assesscalc') { + $row_html.='<td>'.$self->format_html_rowlabel($rowlabel).'</td>'; } # my $shown_cells = 0; @@ -1388,7 +2295,7 @@ END $bgcolor='#FFDDDD' if ($shown_cells < $num_uneditable); # $row_html.='<td bgcolor='.$bgcolor.'>'; - if ($shown_cells < $num_uneditable) { + if ($requester_is_student || $shown_cells < $num_uneditable) { $row_html .= &html_uneditable_cell($cell,$bgcolor); } else { $row_html .= &html_editable_cell($cell,$bgcolor); @@ -1418,7 +2325,7 @@ END # Debugging code (be sure to uncomment errorlog code in safe space): # # $r->print("\n<pre>"); - # $r->print(&geterrorlog($sheet)); + # $r->print(&geterrorlog($self)); # $r->print("\n</pre>"); return 1; } @@ -1427,20 +2334,21 @@ END ## csv output routines ## ############################################ sub outsheet_csv { - my ($sheet,$r) = @_; + my $self = shift; + my ($r) = @_; my $csvdata = ''; my @Values; #################################### # Prepare to output rows #################################### - my @Rows = &sort_indicies($sheet); + my @Rows = $self->sort_indicies(); # # 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); + my ($rowlabel,@rowdata) = $self->get_row($rownum); next if ($rowlabel =~ /^\s*$/); - push (@Values,&format_csv_rowlabel($rowlabel)); + push (@Values,$self->format_csv_rowlabel($rowlabel)); foreach my $cell (@rowdata) { push (@Values,'"'.$cell->{'value'}.'"'); } @@ -1474,17 +2382,18 @@ sub outsheet_csv { ## Excel output routines ## ############################################ sub outsheet_recursive_excel { - my ($sheet,$r) = @_; + my $self = shift; + my ($r) = @_; my $c = $r->connection; - return undef if ($sheet->{'sheettype'} ne 'classcalc'); - my ($workbook,$filename) = &create_excel_spreadsheet($sheet,$r); + return undef if ($self->{'type'} ne 'classcalc'); + my ($workbook,$filename) = $self->create_excel_spreadsheet($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 %f=$self->formulas(); my $count = 0; $r->print(<<END); <p> @@ -1501,19 +2410,19 @@ A link to the spreadsheet will be availa END $r->rflush(); my $starttime = time; - foreach (keys(%f)) { - next if ($_!~/^A(\d+)/ || $1 == 0 || ($f{$_}=~/^[!~-]/)); + foreach my $rownum ($self->sort_indicies()) { $count++; - my ($sname,$sdom) = split(':',$f{$_}); + my ($sname,$sdom) = split(':',$f{'A'.$rownum}); my $student_excel_worksheet=$workbook->addworksheet($sname.'@'.$sdom); # Create a new spreadsheet - my $studentsheet = &makenewsheet($sname,$sdom,'studentcalc',undef); + my $studentsheet = &Apache::lonspreadsheet::Spreadsheet->new + ($sname,$sdom,'studentcalc',undef); # Read in the spreadsheet definition - &update_student_sheet($studentsheet,$c); + $studentsheet->update_student_sheet($r,$c); # 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)); + $studentsheet->export_sheet_as_excel($student_excel_worksheet); + my $totaltime = int((time - $starttime) / $count * $self->{'maxrow'}); + my $timeleft = int((time - $starttime) / $count * ($self->{'maxrow'} - $count)); if ($count % 5 == 0) { $r->print($count.' students completed.'. ' Time remaining: '.$timeleft.' sec. '. @@ -1530,7 +2439,7 @@ END $r->rflush(); # # &export_sheet_as_excel fills $worksheet with the data from $sheet - &export_sheet_as_excel($sheet,$main_worksheet); + $self->export_sheet_as_excel($main_worksheet); # $workbook->close(); # Okay, the spreadsheet is taken care of, so give the user a link. @@ -1544,21 +2453,22 @@ END } sub outsheet_excel { - my ($sheet,$r) = @_; - my ($workbook,$filename) = &create_excel_spreadsheet($sheet,$r); + my $self = shift; + my ($r) = @_; + my ($workbook,$filename) = $self->create_excel_spreadsheet($r); return undef if (! defined($workbook)); my $sheetname; - if ($sheet->{'sheettype'} eq 'classcalc') { + if ($self->{'type'} 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'; + } elsif ($self->{'type'} eq 'studentcalc') { + $sheetname = $self->{'uname'}.'@'.$self->{'udom'}; + } elsif ($self->{'type'} eq 'assesscalc') { + $sheetname = $self->{'uname'}.'@'.$self->{'udom'}.' assessment'; } my $worksheet = $workbook->addworksheet($sheetname); # # &export_sheet_as_excel fills $worksheet with the data from $sheet - &export_sheet_as_excel($sheet,$worksheet); + $self->export_sheet_as_excel($worksheet); # $workbook->close(); # Okay, the spreadsheet is taken care of, so give the user a link. @@ -1568,7 +2478,8 @@ sub outsheet_excel { } sub create_excel_spreadsheet { - my ($sheet,$r) = @_; + my $self = shift; + my ($r) = @_; my $filename = '/prtspool/'. $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'. time.'_'.rand(1000000000).'.xls'; @@ -1581,7 +2492,7 @@ sub create_excel_spreadsheet { return undef; } # - # The spreadsheet stores temporary data in files, then put them + # The excel 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. @@ -1592,7 +2503,7 @@ sub create_excel_spreadsheet { } sub export_sheet_as_excel { - my $sheet = shift; + my $self = shift; my $worksheet = shift; # my $rows_output = 0; @@ -1600,8 +2511,8 @@ sub export_sheet_as_excel { #################################### # Write an identifying row # #################################### - my @Headerinfo = ($sheet->{'coursedesc'}); - my $title = &gettitle($sheet); + my @Headerinfo = ($self->{'coursedesc'}); + my $title = $self->gettitle(); $cols_output = 0; if (defined($title)) { $worksheet->write($rows_output++,$cols_output++,$title); @@ -1609,8 +2520,8 @@ sub export_sheet_as_excel { #################################### # Write the summary/export row # #################################### - my ($rowlabel,@rowdata) = &get_row($sheet,'0'); - my $label = &format_excel_rowlabel($rowlabel); + my ($rowlabel,@rowdata) = &get_row($self,'0'); + my $label = &format_excel_rowlabel($self,$rowlabel); $cols_output = 0; $worksheet->write($rows_output,$cols_output++,$label); foreach my $cell (@rowdata) { @@ -1620,14 +2531,26 @@ sub export_sheet_as_excel { #################################### # Prepare to output rows #################################### - my @Rows = &sort_indicies($sheet); + my @Rows = &sort_indicies($self); # # Loop through the rows and output them one at a time foreach my $rownum (@Rows) { - my ($rowlabel,@rowdata) = &get_row($sheet,$rownum); - next if ($rowlabel =~ /^\s*$/); + my ($rowlabel,@rowdata) = &get_row($self,$rownum); + next if ($rowlabel =~ /^[\s]*$/); $cols_output = 0; - my $label = &format_excel_rowlabel($rowlabel); + my $label = &format_excel_rowlabel($self,$rowlabel); + if ( ! $ENV{'form.showall'} && + $self->{'type'} =~ /^(studentcalc|classcalc)$/) { + my $row_is_empty = 1; + foreach my $cell (@rowdata) { + if ($cell->{'value'} !~ /^\s*$/) { + $row_is_empty = 0; + last; + } + } + next if ($row_is_empty); + } + $worksheet->write($rows_output,$cols_output++,$rownum); $worksheet->write($rows_output,$cols_output++,$label); if (ref($label)) { $cols_output = (scalar(@$label)); @@ -1644,7 +2567,8 @@ sub export_sheet_as_excel { ## XML output routines ## ############################################ sub outsheet_xml { - my ($sheet,$r) = @_; + my $self = shift; + my ($r) = @_; ## Someday XML ## Will be rendered for the user ## But not on this day @@ -1654,32 +2578,35 @@ sub outsheet_xml { ## Outsheet - calls other outsheet_* functions ## sub outsheet { - my ($r,$sheet)=@_; + my $self = shift; + my ($r)=@_; if (! exists($ENV{'form.output'})) { $ENV{'form.output'} = 'HTML'; } if (lc($ENV{'form.output'}) eq 'csv') { - &outsheet_csv($sheet,$r); + $self->outsheet_csv($r); } elsif (lc($ENV{'form.output'}) eq 'excel') { - &outsheet_excel($sheet,$r); + $self->outsheet_excel($r); } elsif (lc($ENV{'form.output'}) eq 'recursive excel') { - &outsheet_recursive_excel($sheet,$r); + $self->outsheet_recursive_excel($r); # } elsif (lc($ENV{'form.output'}) eq 'xml' ) { -# &outsheet_xml($sheet,$r); +# $self->outsheet_xml($r); } else { - &outsheet_html($sheet,$r); + $self->outsheet_html($r); } } -######################################################################## -######################################################################## +# +# othersheets: Returns the list of other spreadsheets available +# sub othersheets { - my ($sheet,$stype)=@_; - $stype = $sheet->{'sheettype'} if (! defined($stype)); - # - my $cnum = $sheet->{'cnum'}; - my $cdom = $sheet->{'cdom'}; - my $chome = $sheet->{'chome'}; + my $self = shift; + my ($stype)=@_; + $stype = $self->{'type'} if (! defined($stype)); + # + my $cnum = $self->{'cnum'}; + my $cdom = $self->{'cdom'}; + my $chome = $self->{'chome'}; # my @alternatives=(); my %results=&Apache::lonnet::dump($stype.'_spreadsheets',$cdom,$cnum); @@ -1691,7 +2618,7 @@ sub othersheets { } # -# -------------------------------------- Parse a spreadsheet +# Parse a spreadsheet # sub parse_sheet { # $sheetxml is a scalar reference or a scalar @@ -1718,16 +2645,14 @@ sub parse_sheet { return \%f; } -# -# -------------------------------------- Read spreadsheet formulas for a course -# sub readsheet { - my ($sheet,$fn)=@_; + my $self = shift; + my ($fn)=@_; # - my $stype = $sheet->{'sheettype'}; - my $cnum = $sheet->{'cnum'}; - my $cdom = $sheet->{'cdom'}; - my $chome = $sheet->{'chome'}; + my $stype = $self->{'type'}; + my $cnum = $self->{'cnum'}; + my $cdom = $self->{'cdom'}; + my $chome = $self->{'chome'}; # if (! defined($fn)) { # There is no filename. Look for defaults in course and global, cache @@ -1748,13 +2673,13 @@ sub readsheet { } } # $fn now has a value - $sheet->{'filename'} = $fn; + $self->{'filename'} = $fn; # see if sheet is cached - my $fstring=''; - if ($fstring=$spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}) { - my %tmp = split(/___;___/,$fstring); - $sheet->{'f'} = \%tmp; - &setformulas($sheet); + if (exists($spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn})) { + + my %tmp = split(/___;___/, + $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}); + $self->formulas(\%tmp); } else { # Not cached, need to read my %f=(); @@ -1779,73 +2704,47 @@ sub readsheet { } %f=%{&parse_sheet(\$sheetxml)}; } else { - my $sheet=''; my %tmphash = &Apache::lonnet::dump($fn,$cdom,$cnum); my ($tmp) = keys(%tmphash); - unless ($tmp =~ /^(con_lost|error|no_such_host)/i) { + if ($tmp !~ /^(con_lost|error|no_such_host)/i) { foreach (keys(%tmphash)) { $f{$_}=$tmphash{$_}; } + } else { + # Unable to grab the specified spreadsheet, + # so we get the default ones instead. + $fn = 'default_'.$stype; + $self->{'filename'} = $fn; + my $dfn = $fn; + $dfn =~ s/\_/\./g; + my $sheetxml; + if (my $fh=Apache::File->new($includedir.'/'.$dfn)) { + $sheetxml = join('',<$fh>); + } else { + $sheetxml='<field row="0" col="A">'. + '"Unable to load spreadsheet"</field>'; + } + %f=%{&parse_sheet(\$sheetxml)}; } } # Cache and set $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f); - $sheet->{'f'}=\%f; - &setformulas($sheet); - } -} - -# -------------------------------------------------------- Make new spreadsheet -sub makenewsheet { - my ($uname,$udom,$stype,$usymb)=@_; - my $sheet={}; - $sheet->{'uname'} = $uname; - $sheet->{'udom'} = $udom; - $sheet->{'sheettype'} = $stype; - $sheet->{'usymb'} = $usymb; - $sheet->{'mapid'} = $ENV{'form.mapid'}; - $sheet->{'resid'} = $ENV{'form.resid'}; - $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 (qw/uname udom sheettype usymb cid csec coursefilename - cnum cdom chome uhome/) { - $initstring.= qq{\$$_="$sheet->{$_}";}; + $self->formulas(\%f); } - $sheet->{'safe'}->reval($initstring); - return $sheet; } # ------------------------------------------------------------ Save spreadsheet sub writesheet { - my ($sheet,$makedef)=@_; - my $cid=$sheet->{'cid'}; + my $self = shift; + my ($makedef)=@_; + my $cid=$self->{'cid'}; if (&Apache::lonnet::allowed('opa',$cid)) { - my %f=&getformulas($sheet); - my $stype= $sheet->{'sheettype'}; - my $cnum = $sheet->{'cnum'}; - my $cdom = $sheet->{'cdom'}; - my $chome= $sheet->{'chome'}; - my $fn = $sheet->{'filename'}; + my %f=$self->formulas(); + my $stype= $self->{'type'}; + my $cnum = $self->{'cnum'}; + my $cdom = $self->{'cdom'}; + my $chome= $self->{'chome'}; + my $fn = $self->{'filename'}; # Cache new sheet $spreadsheets{$cnum.'_'.$cdom.'_'.$stype.'_'.$fn}=join('___;___',%f); # Write sheet @@ -1859,9 +2758,16 @@ sub writesheet { $cdom,$cnum); if ($reply eq 'ok') { if ($makedef) { - return &Apache::lonnet::put('environment', - {'spreadsheet_default_'.$stype => $fn }, - $cdom,$cnum); + $reply = &Apache::lonnet::put('environment', + {'spreadsheet_default_'.$stype => $fn }, + $cdom,$cnum); + if ($reply eq 'ok' && + ($self->{'type'} eq 'studentcalc' || + $self->{'type'} eq 'assesscalc')) { + # Expire the spreadsheets of the other students. + &Apache::lonnet::expirespread('','','studentcalc',''); + } + return $reply; } return $reply; } @@ -1876,41 +2782,41 @@ sub writesheet { # "Modified workcopy" - interactive only # sub tmpwrite { - my ($sheet) = @_; + my $self = shift; my $fn=$ENV{'user.name'}.'_'. - $ENV{'user.domain'}.'_spreadsheet_'.$sheet->{'usymb'}.'_'. - $sheet->{'filename'}; + $ENV{'user.domain'}.'_spreadsheet_'.$self->{'usymb'}.'_'. + $self->{'filename'}; $fn=~s/\W/\_/g; - $fn=$tmpdir.$fn.'.tmp'; + $fn=$Apache::lonnet::tmpdir.$fn.'.tmp'; my $fh; if ($fh=Apache::File->new('>'.$fn)) { - print $fh join("\n",&getformulas($sheet)); + my %f = $self->formulas(); + while( my ($cell,$formula) = each(%f)) { + print $fh &Apache::lonnet::escape($cell)."=".&Apache::lonnet::escape($formula)."\n"; + } } } + # ---------------------------------------------------------- Read the temp copy sub tmpread { - my ($sheet,$nfield,$nform)=@_; + my $self = shift; + my ($nfield,$nform)=@_; my $fn=$ENV{'user.name'}.'_'. - $ENV{'user.domain'}.'_spreadsheet_'.$sheet->{'usymb'}.'_'. - $sheet->{'filename'}; + $ENV{'user.domain'}.'_spreadsheet_'.$self->{'usymb'}.'_'. + $self->{'filename'}; $fn=~s/\W/\_/g; - $fn=$tmpdir.$fn.'.tmp'; + $fn=$Apache::lonnet::tmpdir.$fn.'.tmp'; my $fh; my %fo=(); my $countrows=0; if ($fh=Apache::File->new($fn)) { - my $name; - while ($name=<$fh>) { - chomp($name); - my $value=<$fh>; - chomp($value); - $fo{$name}=$value; - if ($name=~/^A(\d+)$/) { - if ($1>$countrows) { - $countrows=$1; - } - } + while (<$fh>) { + chomp; + my ($cell,$formula) = split(/=/); + $cell = &Apache::lonnet::unescape($cell); + $formula = &Apache::lonnet::unescape($formula); + $fo{$cell} = $formula; } } if ($nform eq 'changesheet') { @@ -1918,113 +2824,36 @@ sub tmpread { unless ($ENV{'form.sel_'.$nfield} eq 'Default') { $fo{'A'.$nfield}.='__&&&__'.$ENV{'form.sel_'.$nfield}; } - } elsif ($nfield eq 'insertrow') { - $countrows++; - my $newrow=substr('000000'.$countrows,-7); - if ($nform eq 'top') { - $fo{'A'.$countrows}='--- '.$newrow; - } else { - $fo{'A'.$countrows}='~~~ '.$newrow; - } } else { if ($nfield) { $fo{$nfield}=$nform; } } - $sheet->{'f'}=\%fo; - &setformulas($sheet); -} - -################################################## -################################################## - -=pod - -=item &parmval() - -Determine the value of a parameter. - -Inputs: $what, the parameter needed, $sheet, the safe space - -Returns: The value of a parameter, or '' if none. - -This function cascades through the possible levels searching for a value for -a parameter. The levels are checked in the following order: -user, course (at section level and course level), map, and lonnet::metadata. -This function uses %parmhash, which must be tied prior to calling it. -This function also requires %courseopt and %useropt to be initialized for -this user and course. - -=cut - -################################################## -################################################## -sub parmval { - my ($what,$sheet)=@_; - my $symb = $sheet->{'usymb'}; - unless ($symb) { return ''; } - # - my $cid = $sheet->{'cid'}; - my $csec = $sheet->{'csec'}; - my $uname = $sheet->{'uname'}; - my $udom = $sheet->{'udom'}; - my $result=''; - # - my ($mapname,$id,$fn)=split(/\_\_\_/,$symb); - # Cascading lookup scheme - my $rwhat=$what; - $what =~ s/^parameter\_//; - $what =~ s/\_([^\_]+)$/\.$1/; - # - my $symbparm = $symb.'.'.$what; - my $mapparm = $mapname.'___(all).'.$what; - my $usercourseprefix = $uname.'_'.$udom.'_'.$cid; - # - my $seclevel = $usercourseprefix.'.['.$csec.'].'.$what; - my $seclevelr = $usercourseprefix.'.['.$csec.'].'.$symbparm; - my $seclevelm = $usercourseprefix.'.['.$csec.'].'.$mapparm; - # - my $courselevel = $usercourseprefix.'.'.$what; - my $courselevelr = $usercourseprefix.'.'.$symbparm; - my $courselevelm = $usercourseprefix.'.'.$mapparm; - # fourth, check user - if (defined($uname)) { - return $useropt{$courselevelr} if (defined($useropt{$courselevelr})); - return $useropt{$courselevelm} if (defined($useropt{$courselevelm})); - return $useropt{$courselevel} if (defined($useropt{$courselevel})); - } - # third, check course - if (defined($csec)) { - return $courseopt{$seclevelr} if (defined($courseopt{$seclevelr})); - return $courseopt{$seclevelm} if (defined($courseopt{$seclevelm})); - return $courseopt{$seclevel} if (defined($courseopt{$seclevel})); - } - # - return $courseopt{$courselevelr} if (defined($courseopt{$courselevelr})); - return $courseopt{$courselevelm} if (defined($courseopt{$courselevelm})); - return $courseopt{$courselevel} if (defined($courseopt{$courselevel})); - # second, check map parms - my $thisparm = $parmhash{$symbparm}; - return $thisparm if (defined($thisparm)); - # first, check default - return &Apache::lonnet::metadata($fn,$rwhat.'.default'); + $self->formulas(\%fo); } - ################################################################## ## Row label formatting routines ## ################################################################## sub format_html_rowlabel { + my $self = shift; my $rowlabel = shift; return '' if ($rowlabel eq ''); my ($type,$labeldata) = split(':',$rowlabel,2); my $result = ''; if ($type eq 'symb') { - my ($symb,$uname,$udom,$mapid,$resid,$title) = split(':',$labeldata); - $symb = &Apache::lonnet::unescape($symb); + my ($symb,$mapid,$resid,$title,$ufn) = split(':',$labeldata); + $ufn = 'default' if (!defined($ufn) || $ufn eq ''); + $ufn = &Apache::lonnet::unescape($ufn); + $symb = &Apache::lonnet::unescape($symb); + $title = &Apache::lonnet::unescape($title); $result = '<a href="/adm/assesscalc?usymb='.$symb. - '&uname='.$uname.'&udom='.$udom. - '&mapid='.$mapid.'&resid='.$resid.'">'.$title.'</a>'; + '&uname='.$self->{'uname'}.'&udom='.$self->{'udom'}. + '&ufn='.$ufn. + '&mapid='.$mapid.'&resid='.$resid.'">'.$title.'</a>'; } elsif ($type eq 'student') { my ($sname,$sdom,$fullname,$section,$id) = split(':',$labeldata); + if ($fullname =~ /^\s*$/) { + $fullname = $sname.'@'.$sdom; + } $result ='<a href="/adm/studentcalc?uname='.$sname. '&udom='.$sdom.'">'; $result.=$section.' '.$id." ".$fullname.'</a>'; @@ -2037,13 +2866,16 @@ sub format_html_rowlabel { } sub format_csv_rowlabel { + my $self = shift; my $rowlabel = shift; return '' if ($rowlabel eq ''); my ($type,$labeldata) = split(':',$rowlabel,2); my $result = ''; if ($type eq 'symb') { - my ($symb,$uname,$udom,$mapid,$resid,$title) = split(':',$labeldata); - $symb = &Apache::lonnet::unescape($symb); + my ($symb,$mapid,$resid,$title,$ufn) = split(':',$labeldata); + $ufn = &Apache::lonnet::unescape($ufn); + $symb = &Apache::lonnet::unescape($symb); + $title = &Apache::lonnet::unescape($title); $result = $title; } elsif ($type eq 'student') { my ($sname,$sdom,$fullname,$section,$id) = split(':',$labeldata); @@ -2058,13 +2890,16 @@ sub format_csv_rowlabel { } sub format_excel_rowlabel { + my $self = shift; my $rowlabel = shift; return '' if ($rowlabel eq ''); my ($type,$labeldata) = split(':',$rowlabel,2); my $result = ''; if ($type eq 'symb') { - my ($symb,$uname,$udom,$mapid,$resid,$title) = split(':',$labeldata); - $symb = &Apache::lonnet::unescape($symb); + my ($symb,$mapid,$resid,$title,$ufn) = split(':',$labeldata); + $ufn = &Apache::lonnet::unescape($ufn); + $symb = &Apache::lonnet::unescape($symb); + $title = &Apache::lonnet::unescape($title); $result = $title; } elsif ($type eq 'student') { my ($sname,$sdom,$fullname,$section,$id) = split(':',$labeldata); @@ -2083,11 +2918,11 @@ sub format_excel_rowlabel { # ---------------------------------------------- Update rows for course listing sub updateclasssheet { - my ($sheet) = @_; - my $cnum =$sheet->{'cnum'}; - my $cdom =$sheet->{'cdom'}; - my $cid =$sheet->{'cid'}; - my $chome =$sheet->{'chome'}; + my $self = shift; + my $cnum =$self->{'cnum'}; + my $cdom =$self->{'cdom'}; + my $cid =$self->{'cid'}; + my $chome =$self->{'chome'}; # %Section = (); # @@ -2111,71 +2946,67 @@ sub updateclasssheet { # # Find discrepancies between the course row table and this # - my %f=&getformulas($sheet); + my %f=$self->formulas(); my $changed=0; # - $sheet->{'maxrow'}=0; + $self->{'maxrow'}=0; my %existing=(); # # Now obsolete rows - foreach (keys(%f)) { - if ($_=~/^A(\d+)/) { - if ($1 > $sheet->{'maxrow'}) { - $sheet->{'maxrow'}= $1; - } - $existing{$f{$_}}=1; - unless ((defined($currentlist{$f{$_}})) || (!$1) || - ($f{$_}=~/^(~~~|---)/)) { - $f{$_}='!!! Obsolete'; - $changed=1; - } + foreach my $rownum ($self->rows()) { + my $cell = 'A'.$rownum; + if ($rownum > $self->{'maxrow'}) { + $self->{'maxrow'}= $rownum; + } + $existing{$f{$cell}}=1; + if (! defined($currentlist{$f{$cell}}) && ($f{$cell}=~/^(~~~|---)/)) { + $f{$cell}='!!! Obsolete'; + $changed=1; } } # # New and unknown keys foreach my $student (sort keys(%currentlist)) { - unless ($existing{$student}) { - $changed=1; - $sheet->{'maxrow'}++; - $f{'A'.$sheet->{'maxrow'}}=$student; - } - } - if ($changed) { - $sheet->{'f'} = \%f; - &setformulas($sheet,%f); + next if ($existing{$student}); + $changed=1; + $self->{'maxrow'}++; + $f{'A'.$self->{'maxrow'}}=$student; } + $self->formulas(\%f) if ($changed); # - &setrowlabels($sheet,\%currentlist); + $self->rowlabels(\%currentlist); } # ----------------------------------- Update rows for student and assess sheets sub get_student_rowlabels { - my ($sheet) = @_; + my $self = shift; # 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}); + my $stype = $self->{'type'}; + my $uname = $self->{'uname'}; + my $udom = $self->{'udom'}; + # + $self->{'rowlabel'} = {}; + # + my $identifier =$self->{'coursefilename'}.'_'.$stype; + if (exists($rowlabel_cache{$identifier})) { + my %tmp = split(/___;___/,$rowlabel_cache{$identifier}); + $self->rowlabels(\%tmp); } else { # Get the data and store it in the cache # Tie hash - tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db', + tie(%course_db,'GDBM_File',$self->{'coursefilename'}.'.db', &GDBM_READER(),0640); if (! tied(%course_db)) { return 'Could not access course data'; } # - my %assesslist; + my %assesslist = (); foreach ('Feedback','Evaluation','Tutoring','Discussion') { my $symb = '_'.lc($_); - $assesslist{$symb} = join(':',('symb',$symb,$uname,$udom,0,0,$_)); + $assesslist{$symb} = join(':',('symb',$symb,0,0, + &Apache::lonnet::escape($_))); } # while (my ($key,$srcf) = each(%course_db)) { @@ -2187,38 +3018,39 @@ sub get_student_rowlabels { my $symb= &Apache::lonnet::declutter($course_db{'map_id_'.$mapid}). '___'.$resid.'___'.&Apache::lonnet::declutter($srcf); - $assesslist{$symb}='symb:'.&Apache::lonnet::escape($symb).':' - .$uname.':'.$udom.':'.$mapid.':'.$resid.':'. - $course_db{'title_'.$id}; + $assesslist{$symb} ='symb:'.&Apache::lonnet::escape($symb).':' + .$mapid.':'.$resid.':'. + &Apache::lonnet::escape($course_db{'title_'.$id}); } } untie(%course_db); # Store away the data - $sheet->{'rowlabel'} = \%assesslist; - $rowlabel_cache{$identifier}=join('___;___',%{$sheet->{'rowlabel'}}); + $self->{'rowlabel'} = \%assesslist; + $rowlabel_cache{$identifier}=join('___;___',%{$self->{'rowlabel'}}); } - + } sub get_assess_rowlabels { - my ($sheet) = @_; + my $self = shift; # 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}); + my $stype = $self->{'type'}; + my $uname = $self->{'uname'}; + my $udom = $self->{'udom'}; + my $usymb = $self->{'usymb'}; + # + $self->rowlabels({}); + my $identifier =$self->{'coursefilename'}.'_'.$stype.'_'.$usymb; + # + if (exists($rowlabel_cache{$identifier})) { + my %tmp = split('___;___',$rowlabel_cache{$identifier}); + $self->rowlabels(\%tmp); } else { # Get the data and store it in the cache # Tie hash - tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db', + tie(%course_db,'GDBM_File',$self->{'coursefilename'}.'.db', &GDBM_READER(),0640); if (! tied(%course_db)) { return 'Could not access course data'; @@ -2257,122 +3089,114 @@ sub get_assess_rowlabels { } untie(%course_db); # Store away the results - $sheet->{'rowlabel'} = \%parameter_labels; - $rowlabel_cache{$identifier}=join('___;___',%{$sheet->{'rowlabel'}}); + $self->rowlabels(\%parameter_labels); + $rowlabel_cache{$identifier}=join('___;___',%parameter_labels); } - } sub updatestudentassesssheet { - my $sheet = shift; - if ($sheet->{'sheettype'} eq 'studentcalc') { - &get_student_rowlabels($sheet); + my $self = shift; + if ($self->{'type'} eq 'studentcalc') { + $self->get_student_rowlabels(); } else { - &get_assess_rowlabels($sheet); + $self->get_assess_rowlabels(); } # Determine if any of the information has changed - my %f=&getformulas($sheet); + my %f=$self->formulas(); my $changed=0; - - $sheet->{'maxrow'} = 0; + $self->{'maxrow'} = 0; my %existing=(); # Now obsolete rows - while (my ($cell, $formula) = each (%f)) { - next if ($cell !~ /^A(\d+)/); - $sheet->{'maxrow'} = $1 if ($1 > $sheet->{'maxrow'}); + foreach my $rownum ($self->rows()) { + my $cell = 'A'.$rownum; + my $formula = $f{$cell}; + $self->{'maxrow'} = $rownum if ($rownum > $self->{'maxrow'}); my ($usy,$ufn)=split(/__&&&\__/,$formula); $existing{$usy}=1; - unless ((exists($sheet->{'rowlabel'}->{$usy}) && - (defined($sheet->{'rowlabel'}->{$usy})) || (!$1) || - ($formula =~ /^(~~~|---)/) )) { - $f{$_}='!!! Obsolete'; + if ( ! exists($self->{'rowlabel'}->{$usy}) || + ! defined($self->{'rowlabel'}->{$usy}) || + ($formula =~ /^(~~~|---)/) || + ($formula =~ /^\s*$/)) { + $f{$cell}='!!! Obsolete'; $changed=1; - } elsif ($ufn) { - # I do not think this works any more - $sheet->{'rowlabel'}->{$usy} - =~s/assesscalc\?usymb\=/assesscalc\?ufn\=$ufn&\usymb\=/; } } # New and unknown keys - foreach (keys(%{$sheet->{'rowlabel'}})) { + my %keys_hates_me = $self->rowlabels(); + foreach (keys(%keys_hates_me)) { unless ($existing{$_}) { $changed=1; - $sheet->{'maxrow'}++; - $f{'A'.$sheet->{'maxrow'}}=$_; + $self->{'maxrow'}++; + $f{'A'.$self->{'maxrow'}}=$_; } } - if ($changed) { - $sheet->{'f'} = \%f; - &setformulas($sheet); - } + $self->formulas(\%f) if ($changed); +# $self->dump_formulas_to_log(); } # ------------------------------------------------ Load data for one assessment - sub loadstudent{ - my ($sheet,$r,$c)=@_; - my %constants=(); - my %formulas=&getformulas($sheet); - $cachedassess=$sheet->{'uname'}.':'.$sheet->{'udom'}; + my $self = shift; + my ($r,$c)=@_; + my %constants = (); + my %formulas = $self->formulas(); + $cachedassess = $self->{'uname'}.':'.$self->{'udom'}; # Get ALL the student preformance data - my @tmp = &Apache::lonnet::dump($sheet->{'cid'}, - $sheet->{'udom'}, - $sheet->{'uname'}, - undef); - if ($tmp[0] !~ /^error:/) { + my @tmp = &Apache::loncoursedata::get_current_state($self->{'uname'}, + $self->{'udom'}, + undef, + $self->{'cid'}); + if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) { %cachedstores = @tmp; } undef @tmp; - # + # debugging code + # $self->dump_hash_to_log(\%cachedstores); + # my @assessdata=(); - while (my ($cell,$value) = each (%formulas)) { + foreach my $row ($self->rows()) { + my $cell = 'A'.$row; + my $value = $formulas{$cell}; if(defined($c) && ($c->aborted())) { last; } - next if ($cell !~ /^A(\d+)/); - my $row=$1; - next if (($value =~ /^[!~-]/) || ($row==0)); + next if ($value =~ /^[!~-]/); my ($usy,$ufn)=split(/__&&&\__/,$value); - @assessdata=&exportsheet($sheet,$sheet->{'uname'}, - $sheet->{'udom'}, - 'assesscalc',$usy,$ufn,$r); + @assessdata=$self->exportsheet($self->{'uname'}, + $self->{'udom'}, + 'assesscalc',$usy,$ufn,$r); 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') { + foreach my $col ('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 (defined($assessdata[$index])) { - my $col=$_; if ($assessdata[$index]=~/\D/) { $constants{$col.$row}="'".$assessdata[$index]."'"; } else { $constants{$col.$row}=$assessdata[$index]; } - unless ($col eq 'A') { - $formulas{$col.$row}='import'; - } + $formulas{$col.$row}='import' if ($col ne 'A'); } $index++; } } $cachedassess=''; undef %cachedstores; - $sheet->{'f'} = \%formulas; - &setformulas($sheet); - &setconstants($sheet,\%constants); + $self->formulas(\%formulas); + $self->constants(\%constants); } -# --------------------------------------------------- Load data for one student +# --------------------------------------------------- Load Course Sheet # sub loadcourse { - my ($sheet,$r,$c)=@_; + my $self = shift; + my ($r,$c)=@_; # my %constants=(); - my %formulas=&getformulas($sheet); + my %formulas=$self->formulas(); # my $total=0; - foreach (keys(%formulas)) { - if ($_=~/^A(\d+)/) { - unless ($formulas{$_}=~/^[\!\~\-]/) { $total++; } - } + foreach ($self->rows()) { + $total++ if ($formulas{'A'.$_} !~ /^[!~-]/); } my $now=0; my $since=time; @@ -2382,27 +3206,30 @@ sub loadcourse { popwin.document.writeln('<html><body bgcolor="#FFFFFF">'+ '<h3>Spreadsheet Calculation Progress</h3>'+ '<form name=popremain>'+ - '<input type=text size=35 name=remaining value=Starting></form>'+ + '<input type=text size=45 name=remaining value="Processing Course Assessment Data"></form>'+ '</body></html>'); popwin.document.close(); </script> ENDPOP $r->rflush(); - foreach (keys(%formulas)) { + # It would be nice to load in the classlist and assessment info at this + # point, before attacking the student spreadsheets. + foreach my $row ($self->rows()) { if(defined($c) && ($c->aborted())) { last; } - next if ($_!~/^A(\d+)/); - my $row=$1; - next if (($formulas{$_}=~/^[\!\~\-]/) || ($row==0)); - my ($sname,$sdom) = split(':',$formulas{$_}); - my @studentdata=&exportsheet($sheet,$sname,$sdom,'studentcalc', + my $cell = 'A'.$row; + next if ($formulas{$cell}=~/^[\!\~\-]/); + my ($sname,$sdom) = split(':',$formulas{$cell}); + my $started = time; + my @studentdata=$self->exportsheet($sname,$sdom,'studentcalc', undef,undef,$r); undef %userrdatas; $now++; $r->print('<script>popwin.document.popremain.remaining.value="'. $now.'/'.$total.': '.int((time-$since)/$now*($total-$now)). - ' secs remaining";</script>'); + ' secs remaining '.(time-$started).' last student";'. + '</script>'); $r->rflush(); # my $index=0; @@ -2422,9 +3249,8 @@ ENDPOP $index++; } } - $sheet->{'f'}=\%formulas; - &setformulas($sheet); - &setconstants($sheet,\%constants); + $self->formulas(\%formulas); + $self->constants(\%constants); $r->print('<script>popwin.close()</script>'); $r->rflush(); } @@ -2432,16 +3258,18 @@ ENDPOP # ------------------------------------------------ Load data for one assessment # sub loadassessment { - my ($sheet,$r,$c)=@_; + my $self = shift; + my ($r,$c)=@_; - 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 $uhome = $self->{'uhome'}; + my $uname = $self->{'uname'}; + my $udom = $self->{'udom'}; + my $symb = $self->{'usymb'}; + my $cid = $self->{'cid'}; + my $cnum = $self->{'cnum'}; + my $cdom = $self->{'cdom'}; + my $chome = $self->{'chome'}; + my $csec = $self->{'csec'}; my $namespace; unless ($namespace=$cid) { return ''; } @@ -2451,23 +3279,16 @@ sub loadassessment { # # get data out of the dumped stores # - my $version=$cachedstores{'version:'.$symb}; - my $scope; - for ($scope=1;$scope<=$version;$scope++) { - foreach (split(/\:/,$cachedstores{$scope.':keys:'.$symb})) { - $returnhash{$_}=$cachedstores{$scope.':'.$symb.':'.$_}; - } + if (exists($cachedstores{$symb})) { + %returnhash = %{$cachedstores{$symb}}; + } else { + %returnhash = (); } } else { # # restore individual # %returnhash = &Apache::lonnet::restore($symb,$namespace,$udom,$uname); - for (my $version=1;$version<=$returnhash{'version'};$version++) { - foreach (split(/\:/,$returnhash{$version.':keys'})) { - $returnhash{$_}=$returnhash{$version.':'.$_}; - } - } } # # returnhash now has all stores for this resource @@ -2526,66 +3347,42 @@ sub loadassessment { # my %c=(); if (tie(%parmhash,'GDBM_File', - $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; + $self->{'coursefilename'}.'_parms.db',&GDBM_READER(),0640)) { + my %f=$self->formulas(); + foreach my $row ($self->rows()) { + my $cell = 'A'.$row; + my $formula = $self->formula($cell); + next if ($formula =~/^[\!\~\-]/); + if ($formula =~ /^parameter/) { + if (defined($thisassess{$formula})) { + my $val = &parmval($formula,$symb,$uname,$udom,$csec); + $c{$cell} = $val; + $c{$formula} = $val; } } else { - my $key=$f{$cell}; - my $ckey=$key; - $key=~s/^stores\_/resource\./; - $key=~s/\_/\./g; - $c{$cell}=$returnhash{$key}; - $c{$ckey}=$returnhash{$key}; + my $ckey=$formula; + $formula=~s/^stores\_/resource\./; + $formula=~s/\_/\./g; + $c{$cell}=$returnhash{$formula}; + $c{$ckey}=$returnhash{$formula}; } } untie(%parmhash); } - &setconstants($sheet,\%c); + $self->constants(\%c); } -# --------------------------------------------------------- Various form fields - -sub textfield { - my ($title,$name,$value)=@_; - return "\n<p><b>$title:</b><br>". - '<input type=text name="'.$name.'" size=80 value="'.$value.'">'; -} - -sub hiddenfield { - my ($name,$value)=@_; - return "\n".'<input type=hidden name="'.$name.'" value="'.$value.'">'; -} - -sub selectbox { - my ($title,$name,$value,%options)=@_; - my $selout="\n<p><b>$title:</b><br>".'<select name="'.$name.'">'; - foreach (sort keys(%options)) { - $selout.='<option value="'.$_.'"'; - if ($_ eq $value) { $selout.=' selected'; } - $selout.='>'.$options{$_}.'</option>'; - } - return $selout.'</select>'; -} # =============================================== Update information in a sheet # # Add new users or assessments, etc. # - sub updatesheet { - my ($sheet)=@_; - if ($sheet->{'sheettype'} eq 'classcalc') { - return &updateclasssheet($sheet); + my $self = shift; + if ($self->{'type'} eq 'classcalc') { + return $self->updateclasssheet(); } else { - return &updatestudentassesssheet($sheet); + return $self->updatestudentassesssheet(); } } @@ -2593,62 +3390,32 @@ sub updatesheet { # # Import the data for rows # - sub loadrows { - my ($sheet,$r)=@_; + my $self = shift; + my ($r)=@_; my $c = $r->connection; - my $stype=$sheet->{'sheettype'}; - if ($stype eq 'classcalc') { - &loadcourse($sheet,$r,$c); - } elsif ($stype eq 'studentcalc') { - &loadstudent($sheet,$r,$c); + if ($self->{'type'} eq 'classcalc') { + $self->loadcourse($r,$c); + } elsif ($self->{'type'} eq 'studentcalc') { + $self->loadstudent($r,$c); } else { - &loadassessment($sheet,$r,$c); + $self->loadassessment($r,$c); } } -# ======================================================= Forced recalculation? - -sub checkthis { - my ($keyname,$time)=@_; - return ($time<$expiredates{$keyname}); -} - -sub forcedrecalc { - my ($uname,$udom,$stype,$usymb)=@_; - my $key=$uname.':'.$udom.':'.$stype.':'.$usymb; - my $time=$oldsheets{$key.'.time'}; - if ($ENV{'form.forcerecalc'}) { return 1; } - unless ($time) { return 1; } - if ($stype eq 'assesscalc') { - my $map=(split(/___/,$usymb))[0]; - if (&checkthis('::assesscalc:',$time) || - &checkthis('::assesscalc:'.$map,$time) || - &checkthis('::assesscalc:'.$usymb,$time) || - &checkthis($uname.':'.$udom.':assesscalc:',$time) || - &checkthis($uname.':'.$udom.':assesscalc:'.$map,$time) || - &checkthis($uname.':'.$udom.':assesscalc:'.$usymb,$time)) { - return 1; - } - } else { - if (&checkthis('::studentcalc:',$time) || - &checkthis($uname.':'.$udom.':studentcalc:',$time)) { - return 1; - } - } - return 0; -} - # ============================================================== Export handler # exportsheet # returns the export row for a spreadsheet. # sub exportsheet { - my ($sheet,$uname,$udom,$stype,$usymb,$fn,$r)=@_; - $uname = $uname || $sheet->{'uname'}; - $udom = $udom || $sheet->{'udom'}; - $stype = $stype || $sheet->{'sheettype'}; + my $self = shift; + my ($uname,$udom,$stype,$usymb,$fn,$r)=@_; + my $flag = 0; + $uname = $uname || $self->{'uname'}; + $udom = $udom || $self->{'udom'}; + $stype = $stype || $self->{'type'}; my @exportarr=(); + # This handles the assessment sheets for '_feedback', etc if (defined($usymb) && ($usymb=~/^\_(\w+)/) && (!defined($fn) || $fn eq '')) { $fn='default_'.$1; @@ -2658,8 +3425,8 @@ sub exportsheet { # my $key=$uname.':'.$udom.':'.$stype.':'.$usymb; my $found=''; - if ($oldsheets{$key}) { - foreach (split(/___&\___/,$oldsheets{$key})) { + if ($Apache::lonspreadsheet::oldsheets{$key}) { + foreach (split(/___&\___/,$Apache::lonspreadsheet::oldsheets{$key})) { my ($name,$value)=split(/___=___/,$_); if ($name eq $fn) { $found=$value; @@ -2667,9 +3434,9 @@ sub exportsheet { } } unless ($found) { - &cachedssheets($sheet,$uname,$udom); - if ($oldsheets{$key}) { - foreach (split(/___&\___/,$oldsheets{$key})) { + &cachedssheets($self,$uname,$udom); + if ($Apache::lonspreadsheet::oldsheets{$key}) { + foreach (split(/___&\___/,$Apache::lonspreadsheet::oldsheets{$key})) { my ($name,$value)=split(/___=___/,$_); if ($name eq $fn) { $found=$value; @@ -2695,12 +3462,13 @@ sub exportsheet { # # Not cached # - my ($newsheet)=&makenewsheet($uname,$udom,$stype,$usymb); - &readsheet($newsheet,$fn); - &updatesheet($newsheet); - &loadrows($newsheet,$r); - &calcsheet($newsheet); - @exportarr=&exportdata($newsheet); + my $newsheet = Apache::lonspreadsheet::Spreadsheet->new($uname,$udom, + $stype,$usymb); + $newsheet->readsheet($fn); + $newsheet->updatesheet(); + $newsheet->loadrows($r); + $newsheet->calcsheet(); + @exportarr=$newsheet->exportdata(); ## ## Store now ## @@ -2711,16 +3479,26 @@ sub exportsheet { if ($stype eq 'studentcalc') { my @tmp = &Apache::lonnet::get('nohist_calculatedsheets', [$key], - $sheet->{'cdom'},$sheet->{'cnum'}); + $self->{'cdom'},$self->{'cnum'}); if ($tmp[0]!~/^error/) { - %currentlystored = @tmp; + # We only got one key, so we will access it directly. + foreach (split('___&___',$tmp[1])) { + my ($key,$value) = split('___=___',$_); + $key = '' if (! defined($key)); + $currentlystored{$key} = $value; + } } } else { my @tmp = &Apache::lonnet::get('nohist_calculatedsheets_'. - $sheet->{'cid'},[$key], - $sheet->{'udom'},$sheet->{'uname'}); + $self->{'cid'},[$key], + $self->{'udom'},$self->{'uname'}); if ($tmp[0]!~/^error/) { - %currentlystored = @tmp; + # We only got one key, so we will access it directly. + foreach (split('___&___',$tmp[1])) { + my ($key,$value) = split('___=___',$_); + $key = '' if (! defined($key)); + $currentlystored{$key} = $value; + } } } # @@ -2739,457 +3517,25 @@ sub exportsheet { # # Store away the new value # + my $timekey = $key.'.time'; 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'}) + my $result = &Apache::lonnet::put('nohist_calculatedsheets', + { $key => $newstore, + $timekey => $now }, + $self->{'cdom'}, + $self->{'cnum'}); + } else { + my $result = &Apache::lonnet::put('nohist_calculatedsheets_'.$self->{'cid'}, + { $key => $newstore, + $timekey => $now }, + $self->{'udom'}, + $self->{'uname'}); } return @exportarr; } -# ============================================================ Expiration Dates -# -# Load previously cached student spreadsheets for this course -# -sub load_spreadsheet_expirationdates { - undef %expiredates; - my $cid=$ENV{'request.course.id'}; - my @tmp = &Apache::lonnet::dump('nohist_expirationdates', - $ENV{'course.'.$cid.'.domain'}, - $ENV{'course.'.$cid.'.num'}); - if (lc($tmp[0]) !~ /^error/){ - %expiredates = @tmp; - } -} - -# ===================================================== Calculated sheets cache -# -# Load previously cached student spreadsheets for this course -# - -sub cachedcsheets { - my $cid=$ENV{'request.course.id'}; - 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; - } - } -} - -# ===================================================== Calculated sheets cache -# -# Load previously cached assessment spreadsheets for this student -# - -sub cachedssheets { - 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; - } - } - -} - -# ===================================================== Calculated sheets cache -# -# Load previously cached assessment spreadsheets for this student -# - -# ================================================================ Main handler -# -# Interactive call to screen -# -# -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 - $ENV{'user.error.msg'}= - $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','mapid','resid']); - # - # 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'}; - } - # - # Determine the user name and domain for the sheet. - my $aname; - my $adom; - unless ($ENV{'form.uname'}) { - $aname=$ENV{'user.name'}; - $adom=$ENV{'user.domain'}; - } else { - $aname=$ENV{'form.uname'}; - $adom=$ENV{'form.udom'}; - } - # - # 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; - # - # Header.... - # - $r->print('<html><head><title>LON-CAPA Spreadsheet</title>'); - my $nothing = "''"; - if ($ENV{'browser.type'} eq 'explorer') { - $nothing = "'javascript:void(0);'"; - } - - if ($ENV{'request.role'} !~ /^st\./) { - $r->print(<<ENDSCRIPT); -<script language="JavaScript"> - - var editwin; - - function celledit(cellname,cellformula) { - var edit_text = ''; - edit_text +='<html><head><title>Cell Edit Window</title></head><body>'; - edit_text += '<form name="editwinform">'; - edit_text += '<center><h3>Cell '+cellname+'</h3>'; - edit_text += '<textarea name="newformula" cols="40" rows="6"'; - edit_text += ' wrap="off" >'+cellformula+'</textarea>'; - edit_text += '</br>'; - edit_text += '<input type="button" name="accept" value="Accept"'; - edit_text += ' onClick=\\\'javascript:'; - edit_text += 'opener.document.sheet.unewfield.value='; - edit_text += '"'+cellname+'";'; - edit_text += 'opener.document.sheet.unewformula.value='; - edit_text += 'document.editwinform.newformula.value;'; - edit_text += 'opener.document.sheet.submit();'; - edit_text += 'self.close()\\\' />'; - edit_text += ' '; - edit_text += '<input type="button" name="abort" '; - edit_text += 'value="Discard Changes"'; - edit_text += ' onClick="javascript:self.close()" />'; - edit_text += '</center></body></html>'; - - if (editwin != null && !(editwin.closed) ) { - editwin.close(); - } - - editwin = window.open($nothing,'CellEditWin','height=200,width=350,scrollbars=no,resizeable=yes,alwaysRaised=yes,dependent=yes',true); - editwin.document.write(edit_text); - } +1; - function changesheet(cn) { - document.sheet.unewfield.value=cn; - document.sheet.unewformula.value='changesheet'; - document.sheet.submit(); - } +__END__ - function insertrow(cn) { - document.sheet.unewfield.value='insertrow'; - document.sheet.unewformula.value=cn; - document.sheet.submit(); - } -</script> -ENDSCRIPT - } - $r->print('</head>'.&Apache::loncommon::bodytag('Grades Spreadsheet'). - '<form action="'.$r->uri.'" name="sheet" method="post">'); - $r->print(&hiddenfield('uname',$ENV{'form.uname'}). - &hiddenfield('udom',$ENV{'form.udom'}). - &hiddenfield('usymb',$ENV{'form.usymb'}). - &hiddenfield('unewfield',''). - &hiddenfield('unewformula','')); - $r->rflush(); - # - # Full recalc? - # - if ($ENV{'form.forcerecalc'}) { - $r->print('<h4>Completely Recalculating Sheet ...</h4>'); - undef %spreadsheets; - undef %courserdatas; - undef %userrdatas; - undef %defaultsheets; - undef %rowlabel_cache; - } - # Read new sheet or modified worksheet - my ($sheet)=&makenewsheet($aname,$adom,$sheettype,$ENV{'form.usymb'}); - # - # Check user permissions - 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('<h1>Access Permission Denied</h1>'. - '</form></body></html>'); - return OK; - } - } - # Print out user information - $r->print('<h2>'.$sheet->{'coursedesc'}.'</h2>'); - if ($sheet->{'sheettype'} ne 'classcalc') { - $r->print('<h2>'.&gettitle($sheet).'</h2><p>'); - } - if ($sheet->{'sheettype'} eq 'assesscalc') { - $r->print('<b>User:</b> '.$sheet->{'uname'}. - '<br /><b>Domain:</b> '.$sheet->{'udom'}.'<br />'); - } - if ($sheet->{'sheettype'} eq 'studentcalc' || - $sheet->{'sheettype'} eq 'assesscalc') { - $r->print('<b>Section/Group:</b>'.$sheet->{'csec'}.'</p>'); - } - # - # If a new formula had been entered, go from work copy - if ($ENV{'form.unewfield'}) { - $r->print('<h2>Modified Workcopy</h2>'); - $ENV{'form.unewformula'}=~s/\'/\"/g; - $r->print('<p>New formula: '.$ENV{'form.unewfield'}.'='. - $ENV{'form.unewformula'}.'<p>'); - $sheet->{'filename'} = $ENV{'form.ufn'}; - &tmpread($sheet,$ENV{'form.unewfield'},$ENV{'form.unewformula'}); - } elsif ($ENV{'form.saveas'}) { - $sheet->{'filename'} = $ENV{'form.ufn'}; - &tmpread($sheet); - } else { - &readsheet($sheet,$ENV{'form.ufn'}); - } - # Additional options - if ($sheet->{'sheettype'} eq 'assesscalc') { - $r->print('<p><font size=+2>'. - '<a href="/adm/studentcalc?'. - 'uname='.$sheet->{'uname'}. - '&udom='.$sheet->{'udom'}.'">'. - 'Level up: Student Sheet</a></font></p>'); - } - if (($sheet->{'sheettype'} eq 'studentcalc') && - (&Apache::lonnet::allowed('vgr',$sheet->{'cid'}))) { - $r->print ('<p><font size=+2><a href="/adm/classcalc">'. - 'Level up: Course Sheet</a></font></p>'); - } - # Recalc button - $r->print('<br />'. - '<input type="submit" name="forcerecalc" '. - 'value="Completely Recalculate Sheet"></p>'); - # Save dialog - if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { - my $fname=$ENV{'form.ufn'}; - $fname=~s/\_[^\_]+$//; - if ($fname eq 'default') { $fname='course_default'; } - $r->print('<input type=submit name=saveas value="Save as ...">'. - '<input type=text size=20 name=newfn value="'.$fname.'">'. - 'make default: <input type=checkbox name="makedefufn"><p>'); - } - $r->print(&hiddenfield('ufn',$sheet->{'filename'})); - # Load dialog - if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { - $r->print('<p><input type=submit name=load value="Load ...">'. - '<select name="loadthissheet">'. - '<option name="default">Default</option>'); - foreach (&othersheets($sheet)) { - $r->print('<option name="'.$_.'"'); - if ($ENV{'form.ufn'} eq $_) { - $r->print(' selected'); - } - $r->print('>'.$_.'</option>'); - } - $r->print('</select><p>'); - if ($sheet->{'sheettype'} eq 'studentcalc') { - &setothersheets($sheet, - &othersheets($sheet,'assesscalc')); - } - } - # - # 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 ($sheet->{'sheettype'} eq 'studentcalc') { - $r->print("Loading previously calculated assessment sheets ...\n"); - $r->rflush(); - &cachedssheets($sheet); - } - # Update sheet, load rows - $r->print("Loaded sheet(s), updating rows ...<br>\n"); - $r->rflush(); - # - &updatesheet($sheet); - $r->print("Updated rows, loading row data ...\n"); - $r->rflush(); - # - &loadrows($sheet,$r); - $r->print("Loaded row data, calculating sheet ...<br>\n"); - $r->rflush(); - # - my $calcoutput=&calcsheet($sheet); - $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>'); - # See if something to save - if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) { - my $fname=''; - if ($ENV{'form.saveas'} && ($fname=$ENV{'form.newfn'})) { - $fname=~s/\W/\_/g; - if ($fname eq 'default') { $fname='course_default'; } - $fname.='_'.$sheet->{'sheettype'}; - $sheet->{'filename'} = $fname; - $ENV{'form.ufn'}=$fname; - $r->print('<p>Saving spreadsheet: '. - &writesheet($sheet,$ENV{'form.makedefufn'}). - '<p>'); - } - } - # - # Write the modified worksheet - $r->print('<b>Current sheet:</b> '.$sheet->{'filename'}.'</p>'); - &tmpwrite($sheet); - if ($sheet->{'sheettype'} eq 'assesscalc') { - $r->print('<p>Show rows with empty A column: '); - } else { - $r->print('<p>Show empty rows: '); - } - # - $r->print(&hiddenfield('userselhidden','true'). - '<input type="checkbox" name="showall" onClick="submit()"'); - # - if ($ENV{'form.showall'}) { - $r->print(' checked'); - } else { - unless ($ENV{'form.userselhidden'}) { - unless - ($ENV{'course.'.$sheet->{'cid'}.'.hideemptyrows'} eq 'yes') { - $r->print(' checked'); - $ENV{'form.showall'}=1; - } - } - } - $r->print('>'); - # - # CSV format checkbox (classcalc sheets only) - $r->print(' Output as <select name="output" size="1" onClick="submit()">'. - "\n"); - foreach my $mode (qw/HTML CSV Excel/) { - $r->print('<option value="'.$mode.'"'); - if ($ENV{'form.output'} eq $mode) { - $r->print(' selected '); - } - $r->print('>'.$mode.'</option>'."\n"); - } - if ($sheet->{'sheettype'} eq 'classcalc') { - $r->print('<option value="recursive excel"'); - if ($ENV{'form.output'} eq 'recursive excel') { - $r->print(' selected '); - } - $r->print(">Multi-Sheet Excel</option>\n"); - } - $r->print("</select>\n"); - # - if ($sheet->{'sheettype'} eq 'classcalc') { - $r->print(' Student Status: '. - &Apache::lonhtmlcommon::StatusOptions - ($ENV{'form.Status'},'sheet')); - } - # - # Buttons to insert rows -# $r->print(<<ENDINSERTBUTTONS); -#<br> -#<input type='button' onClick='insertrow("top");' -#value='Insert Row Top'> -#<input type='button' onClick='insertrow("bottom");' -#value='Insert Row Bottom'><br> -#ENDINSERTBUTTONS - # Print out sheet - &outsheet($r,$sheet); - $r->print('</form></body></html>'); - # Done - return OK; -} - -1; -__END__