--- loncom/interface/Attic/lonspreadsheet.pm 2002/11/06 20:00:13 1.134 +++ loncom/interface/Attic/lonspreadsheet.pm 2002/12/13 19:11:00 1.159 @@ -1,5 +1,5 @@ # -# $Id: lonspreadsheet.pm,v 1.134 2002/11/06 20:00:13 matthew Exp $ +# $Id: lonspreadsheet.pm,v 1.159 2002/12/13 19:11:00 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -53,17 +53,19 @@ built-in functions. package Apache::lonspreadsheet; use strict; +use Apache::Constants qw(:common :http); +use Apache::lonnet; +use Apache::lonhtmlcommon; +use Apache::loncoursedata; +use Apache::File(); use Safe; use Safe::Hole; use Opcode; -use Apache::lonnet; -use Apache::Constants qw(:common :http); use GDBM_File; +use HTML::Entities(); use HTML::TokeParser; -use Apache::lonhtmlcommon; -use Apache::loncoursedata; -use Apache::File(); use Spreadsheet::WriteExcel; + # # Caches for coursewide information # @@ -93,7 +95,7 @@ my %spreadsheets; my %courserdatas; my %userrdatas; my %defaultsheets; -my %updatedata; +my %rowlabel_cache; # # These global hashes are dependent on user, course and resource, @@ -959,16 +961,14 @@ ENDDEFS sub templaterow { my $sheet = shift; my @cols=(); - my $rowlabel = 'Template'; + my $rowlabel = 'Template</td><td> '; 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; push(@cols,{ name => 'template_'.$_, - formula => $fm, - value => $fm }); + formula => $sheet->{'f'}->{'template_'.$_}, + value => $sheet->{'f'}->{'template_'.$_} }); } return ($rowlabel,@cols); } @@ -981,21 +981,25 @@ sub outrowassess { if ($n) { my ($usy,$ufn)=split(/__&&&\__/,$sheet->{'f'}->{'A'.$n}); if (exists($sheet->{'rowlabel'}->{$usy})) { - $rowlabel = $sheet->{'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 = $sheet->{'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, + formula => $sheet->{'f'}->{$_.$n}, value => $sheet->{'values'}->{$_.$n}}); } return ($rowlabel,@cols); @@ -1009,19 +1013,17 @@ sub outrow { $rowlabel = $sheet->{'rowlabel'}->{$sheet->{'f'}->{'A'.$n}}; } else { if ($sheet->{'sheettype'} eq 'classcalc') { - $rowlabel = 'Summary'; + $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, + formula => $sheet->{'f'}->{$_.$n}, value => $sheet->{'values'}->{$_.$n}}); } return ($rowlabel,@cols); @@ -1085,6 +1087,44 @@ sub geterrorlog { return ${$sheet->{'safe'}->varglob('errorlog')}; } +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'}; + $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'}; + my %course_db; + tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db', + &GDBM_READER(),0640); + return if (! tied(%course_db)); + my $key = 'title_'.$mapid.'.'.$resid; + my $title = ''; + if (exists($course_db{$key})) { + $title = $course_db{$key}; + } else { + $title = $sheet->{'usymb'}; + } + untie (%course_db); + return $title; + } +} + # ----------------------------------------------------- Get value of $f{'A'.$n} sub getfa { my $sheet = shift; @@ -1107,11 +1147,26 @@ sub exportdata { return @exportarray; } + + +sub update_student_sheet{ + my ($sheet,$r,$c) = @_; + # Load in the studentcalc sheet + &readsheet($sheet,'default_studentcalc'); + # Determine the structure (contained assessments, etc) of the sheet + &updatesheet($sheet); + # Load in the cached sheets for this student + &cachedssheets($sheet); + # Load in the (possibly cached) data from the assessment sheets + &loadstudent($sheet,$r,$c); + # Compute the sheet + &calcsheet($sheet); +} + # ========================================================== End of Spreadsheet # ============================================================================= - # -# Procedures for screen output +# Procedures for spreadsheet output # # --------------------------------------------- Produce output row n from sheet @@ -1132,22 +1187,61 @@ sub get_row { ######################################################################## sub sort_indicies { my $sheet = shift; - # - # Sort the rows in some manner - # - my @sortby=(); my @sortidx=(); - for (my $row=1;$row<=$sheet->{'maxrow'};$row++) { - push (@sortby, $sheet->{'safe'}->reval('$f{"A'.$row.'"}')); - push (@sortidx, $row); + # + if ($sheet->{'sheettype'} eq 'classcalc') { + my @sortby=(undef); + # Skip row 0 + for (my $row=1;$row<=$sheet->{'maxrow'};$row++) { + my (undef,$sname,$sdom,$fullname,$section,$id) = + split(':',$sheet->{'rowlabel'}->{$sheet->{'f'}->{'A'.$row}}); + push (@sortby, lc($fullname)); + push (@sortidx, $row); + } + @sortidx = sort { $sortby[$a] cmp $sortby[$b]; } @sortidx; + } elsif ($sheet->{'sheettype'} eq 'studentcalc') { + my @sortby1=(undef); + my @sortby2=(undef); + # Skip row 0 + for (my $row=1;$row<=$sheet->{'maxrow'};$row++) { + my ($key,undef) = split(/__&&&\__/,$sheet->{'f'}->{'A'.$row}); + my $rowlabel = $sheet->{'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 + for (my $row=1;$row<=$sheet->{'maxrow'};$row++) { + push (@sortby, $sheet->{'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; } -######################################################################## -######################################################################## - +############################################################# +### ### +### Spreadsheet Output Routines ### +### ### +############################################################# + +############################################ +## HTML output routines ## +############################################ sub html_editable_cell { my ($cell,$bgcolor) = @_; my $result; @@ -1164,27 +1258,35 @@ 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 !~/ /); } - $result .= '<a href="javascript:celledit(\''. - $name.'\',\''.$formula.'\');">'.$value.'</a>'; + # 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; + # 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 ($num_uneditable,$realm,$row_type); + my $requester_is_student = ($ENV{'request.role'} =~ /^st\./); if ($sheet->{'sheettype'} eq 'assesscalc') { $num_uneditable = 1; $realm = 'Assessment'; @@ -1207,7 +1309,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"> @@ -1229,35 +1331,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) = &get_row($sheet,'-'); + $row_html = '<tr><td>'.&format_html_rowlabel($sheet,$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'); - my $rowcount = 0; - $row_html = '<tr><td>'.&format_html_rowlabel($rowlabel).'</td>'; + ($rowlabel,@rowdata) = &get_row($sheet,'0'); + $row_html = '<tr><td>'.&format_html_rowlabel($sheet,$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>'; } @@ -1273,6 +1379,21 @@ END my $rows_output=0; foreach my $rownum (@Rows) { my ($rowlabel,@rowdata) = &get_row($sheet,$rownum); + next if ($rowlabel =~ /^\s*$/); + next if (($sheet->{'sheettype'} eq 'assesscalc') && + (! $ENV{'form.showall'}) && + ($rowdata[0]->{'value'} =~ /^\s*$/)); + if (! $ENV{'form.showall'} && + $sheet->{'sheettype'} =~ /^(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); + } # my $defaultbg='#E0FF'; # @@ -1280,25 +1401,28 @@ END '</font></b></td>'; # if ($sheet->{'sheettype'} eq 'classcalc') { - $row_html.='<td>'.&format_html_rowlabel($rowlabel).'</td>'; + $row_html.='<td>'.&format_html_rowlabel($sheet,$rowlabel).'</td>'; # Output links for each student? - # Nope, that is already done for us in format_html_rowlabel (for now) + # 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); + my $ufn = (split(/:/,$rowlabel))[5]; + $row_html.='<td>'.&format_html_rowlabel($sheet,$rowlabel); $row_html.= '<br>'. '<select name="sel_'.$rownum.'" '. 'onChange="changesheet('.$rownum.')">'. '<option name="default">Default</option>'; + foreach (@{$sheet->{'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>'; + $row_html.='<td>'.&format_html_rowlabel($sheet,$rowlabel).'</td>'; } # my $shown_cells = 0; @@ -1316,7 +1440,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); @@ -1351,6 +1475,9 @@ END return 1; } +############################################ +## csv output routines ## +############################################ sub outsheet_csv { my ($sheet,$r) = @_; my $csvdata = ''; @@ -1364,7 +1491,8 @@ sub outsheet_csv { my $rows_output=0; foreach my $rownum (@Rows) { my ($rowlabel,@rowdata) = &get_row($sheet,$rownum); - push (@Values,&format_csv_rowlabel($rowlabel)); + next if ($rowlabel =~ /^\s*$/); + push (@Values,&format_csv_rowlabel($sheet,$rowlabel)); foreach my $cell (@rowdata) { push (@Values,'"'.$cell->{'value'}.'"'); } @@ -1394,19 +1522,114 @@ sub outsheet_csv { return 1; } +############################################ +## Excel output routines ## +############################################ +sub outsheet_recursive_excel { + my ($sheet,$r) = @_; + my $c = $r->connection; + return undef if ($sheet->{'sheettype'} ne 'classcalc'); + my ($workbook,$filename) = &create_excel_spreadsheet($sheet,$r); + return undef if (! defined($workbook)); + # + # Create main worksheet + my $main_worksheet = $workbook->addworksheet('main'); + # + # Figure out who the students are + my %f=&getformulas($sheet); + my $count = 0; + $r->print(<<END); +<p> +Compiling Excel Workbook with a worksheet for each student. +</p><p> +This operation may take longer than a complete recalculation of the +spreadsheet. +</p><p> +To abort this operation, hit the stop button on your browser. +</p><p> +A link to the spreadsheet will be available at the end of this process. +</p> +<p> +END + $r->rflush(); + my $starttime = time; + foreach my $rownum (&sort_indicies($sheet)) { + $count++; + 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); + # Read in the spreadsheet definition + &update_student_sheet($studentsheet,$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)); + if ($count % 5 == 0) { + $r->print($count.' students completed.'. + ' Time remaining: '.$timeleft.' sec. '. + ' Estimated total time: '.$totaltime." sec <br />\n"); + $r->rflush(); + } + if(defined($c) && ($c->aborted())) { + last; + } + } + # + if(! $c->aborted() ) { + $r->print('All students spreadsheets completed!<br />'); + $r->rflush(); + # + # &export_sheet_as_excel fills $worksheet with the data from $sheet + &export_sheet_as_excel($sheet,$main_worksheet); + # + $workbook->close(); + # Okay, the spreadsheet is taken care of, so give the user a link. + $r->print('<br /><br />'. + '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n"); + } else { + $workbook->close(); # Not sure how necessary this is. + #unlink('/home/httpd'.$filename); # No need to keep this around? + } + return 1; +} + sub outsheet_excel { my ($sheet,$r) = @_; + my ($workbook,$filename) = &create_excel_spreadsheet($sheet,$r); + return undef if (! defined($workbook)); + my $sheetname; + if ($sheet->{'sheettype'} eq 'classcalc') { + $sheetname = 'Main'; + } elsif ($sheet->{'sheettype'} eq 'studentcalc') { + $sheetname = $sheet->{'uname'}.'@'.$sheet->{'udom'}; + } elsif ($sheet->{'sheettype'} eq 'assesscalc') { + $sheetname = $sheet->{'uname'}.'@'.$sheet->{'udom'}.' assessment'; + } + my $worksheet = $workbook->addworksheet($sheetname); + # + # &export_sheet_as_excel fills $worksheet with the data from $sheet + &export_sheet_as_excel($sheet,$worksheet); + # + $workbook->close(); + # Okay, the spreadsheet is taken care of, so give the user a link. + $r->print('<br /><br />'. + '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n"); + return 1; +} + +sub create_excel_spreadsheet { + my ($sheet,$r) = @_; my $filename = '/prtspool/'. $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'. time.'_'.rand(1000000000).'.xls'; - &Apache::lonnet::logthis("spreadsheet:filename = ".$filename); my $workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename); if (! defined($workbook)) { $r->log_error("Error creating excel spreadsheet $filename: $!"); $r->print("Problems creating new Excel file. ". "This error has been logged. ". "Please alert your LON-CAPA administrator"); - return 0; + return undef; } # # The spreadsheet stores temporary data in files, then put them @@ -1416,50 +1639,84 @@ sub outsheet_excel { $workbook->set_tempdir('/home/httpd/perl/tmp'); # # Determine the name to give the worksheet - my $sheetname; - if ($sheet->{'sheettype'} eq 'classcalc') { - $sheetname = 'Main'; - } elsif ($sheet->{'sheettype'} eq 'studentcalc') { - $sheetname = $sheet->{'uname'}.'@'.$sheet->{'udom'}; - } elsif ($sheet->{'sheettype'} eq 'assesscalc') { - $sheetname = $sheet->{'uname'}.'@'.$sheet->{'udom'}.' assessment'; + return ($workbook,$filename); +} + +sub export_sheet_as_excel { + my $sheet = shift; + my $worksheet = shift; + # + my $rows_output = 0; + my $cols_output = 0; + #################################### + # Write an identifying row # + #################################### + my @Headerinfo = ($sheet->{'coursedesc'}); + my $title = &gettitle($sheet); + $cols_output = 0; + if (defined($title)) { + $worksheet->write($rows_output++,$cols_output++,$title); } - my $worksheet = $workbook->addworksheet($sheetname); + #################################### + # Write the summary/export row # + #################################### + my ($rowlabel,@rowdata) = &get_row($sheet,'0'); + my $label = &format_excel_rowlabel($sheet,$rowlabel); + $cols_output = 0; + $worksheet->write($rows_output,$cols_output++,$label); + foreach my $cell (@rowdata) { + $worksheet->write($rows_output,$cols_output++,$cell->{'value'}); + } + $rows_output+= 2; # Skip a row, just for fun #################################### # Prepare to output rows #################################### my @Rows = &sort_indicies($sheet); # # Loop through the rows and output them one at a time - my $rows_output=0; foreach my $rownum (@Rows) { my ($rowlabel,@rowdata) = &get_row($sheet,$rownum); - my $cols_output = 0; - my $label = &format_excel_rowlabel($rowlabel); + next if ($rowlabel =~ /^[\s]*$/); + $cols_output = 0; + my $label = &format_excel_rowlabel($sheet,$rowlabel); + if ( ! $ENV{'form.showall'} && + $sheet->{'sheettype'} =~ /^(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++,$label); if (ref($label)) { $cols_output = (scalar(@$label)); } foreach my $cell (@rowdata) { - $worksheet->write($rows_output,$cols_output++, - $cell->{'value'}); + $worksheet->write($rows_output,$cols_output++,$cell->{'value'}); } $rows_output++; } - # - $workbook->close(); - # Okay, the spreadsheet is taken care of, so give the user a link. - $r->print('<br /><br />'. - '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n"); - return 1; + return; } +############################################ +## XML output routines ## +############################################ sub outsheet_xml { my ($sheet,$r) = @_; + ## Someday XML + ## Will be rendered for the user + ## But not on this day } +## +## Outsheet - calls other outsheet_* functions +## sub outsheet { - my ($r,$sheet)=@_; + my ($sheet,$r)=@_; if (! exists($ENV{'form.output'})) { $ENV{'form.output'} = 'HTML'; } @@ -1467,6 +1724,8 @@ sub outsheet { &outsheet_csv($sheet,$r); } elsif (lc($ENV{'form.output'}) eq 'excel') { &outsheet_excel($sheet,$r); + } elsif (lc($ENV{'form.output'}) eq 'recursive excel') { + &outsheet_recursive_excel($sheet,$r); # } elsif (lc($ENV{'form.output'}) eq 'xml' ) { # &outsheet_xml($sheet,$r); } else { @@ -1569,7 +1828,8 @@ sub readsheet { if ($fh=Apache::File->new($includedir.'/'.$dfn)) { $sheetxml=join('',<$fh>); } else { - $sheetxml='<field row="0" col="A">"Error"</field>'; + # $sheetxml='<field row="0" col="A">"Error"</field>'; + $sheetxml='<field row="0" col="A"></field>'; } %f=%{&parse_sheet(\$sheetxml)}; } elsif($fn=~/\/*\.spreadsheet$/) { @@ -1581,13 +1841,27 @@ 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; + $sheet->{'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 @@ -1605,6 +1879,8 @@ sub makenewsheet { $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'}; @@ -1612,7 +1888,7 @@ sub makenewsheet { $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'}; + '.description'}; $sheet->{'uhome'} = &Apache::lonnet::homeserver($uname,$udom); # # @@ -1659,9 +1935,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' && + ($sheet->{'sheettype'} eq 'studentcalc' || + $sheet->{'sheettype'} eq 'assesscalc')) { + # Expire the spreadsheets of the other students. + &Apache::lonnet::expirespread('','','studentcalc',''); + } + return $reply; } return $reply; } @@ -1684,7 +1967,10 @@ sub tmpwrite { $fn=$tmpdir.$fn.'.tmp'; my $fh; if ($fh=Apache::File->new('>'.$fn)) { - print $fh join("\n",&getformulas($sheet)); + my %f = &getformulas($sheet); + while( my ($cell,$formula) = each(%f)) { + print $fh &Apache::lonnet::escape($cell)."=".&Apache::lonnet::escape($formula)."\n"; + } } } @@ -1700,32 +1986,36 @@ sub tmpread { 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; + } + } +# chomp($value); +# $fo{$name}=$value; +# if ($name=~/^A(\d+)$/) { +# if ($1>$countrows) { +# $countrows=$1; +# } +# } +# } +# } if ($nform eq 'changesheet') { $fo{'A'.$nfield}=(split(/__&&&\__/,$fo{'A'.$nfield}))[0]; 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; - } +# } 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; } } @@ -1813,17 +2103,26 @@ sub parmval { ## Row label formatting routines ## ################################################################## sub format_html_rowlabel { + my $sheet = shift; my $rowlabel = shift; return '' if ($rowlabel eq ''); my ($type,$labeldata) = split(':',$rowlabel,2); my $result = ''; if ($type eq 'symb') { - my ($symb,$uname,$udom,$title) = split(':',$labeldata); - $symb = &Apache::lonnet::unescape($symb); + 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.'">'.$title.'</a>'; + '&uname='.$sheet->{'uname'}.'&udom='.$sheet->{'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>'; @@ -1836,13 +2135,16 @@ sub format_html_rowlabel { } sub format_csv_rowlabel { + my $sheet = shift; my $rowlabel = shift; return '' if ($rowlabel eq ''); my ($type,$labeldata) = split(':',$rowlabel,2); my $result = ''; if ($type eq 'symb') { - my ($symb,$uname,$udom,$title) = split(':',$labeldata); - $symb = &Apache::lonnet::unescape($symb); + 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); @@ -1857,13 +2159,16 @@ sub format_csv_rowlabel { } sub format_excel_rowlabel { + my $sheet = shift; my $rowlabel = shift; return '' if ($rowlabel eq ''); my ($type,$labeldata) = split(':',$rowlabel,2); my $result = ''; if ($type eq 'symb') { - my ($symb,$uname,$udom,$title) = split(':',$labeldata); - $symb = &Apache::lonnet::unescape($symb); + 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); @@ -1889,7 +2194,6 @@ sub updateclasssheet { my $chome =$sheet->{'chome'}; # %Section = (); - # # Read class list and row labels my $classlist = &Apache::loncoursedata::get_classlist(); @@ -1901,6 +2205,7 @@ sub updateclasssheet { foreach my $student (keys(%$classlist)) { my ($studentDomain,$studentName,$end,$start,$id,$studentSection, $fullname,$status) = @{$classlist->{$student}}; + $Section{$studentName.':'.$studentDomain} = $studentSection; if ($ENV{'form.Status'} eq $status || $ENV{'form.Status'} eq 'Any') { $currentlist{$student}=join(':',('student',$studentName, $studentDomain,$fullname, @@ -1948,29 +2253,82 @@ sub updateclasssheet { } # ----------------------------------- Update rows for student and assess sheets -sub updatestudentassesssheet { +sub get_student_rowlabels { my ($sheet) = @_; # - my %bighash; + my %course_db; # my $stype = $sheet->{'sheettype'}; my $uname = $sheet->{'uname'}; my $udom = $sheet->{'udom'}; + # $sheet->{'rowlabel'} = {}; - my $identifier =$sheet->{'coursefilename'}.'_'.$stype.'_'.$uname.'_'.$udom; - if ($updatedata{$identifier}) { - %{$sheet->{'rowlabel'}}=split(/___;___/,$updatedata{$identifier}); + # + my $identifier =$sheet->{'coursefilename'}.'_'.$stype; + if ($rowlabel_cache{$identifier}) { + %{$sheet->{'rowlabel'}}=split(/___;___/,$rowlabel_cache{$identifier}); } else { + # Get the data and store it in the cache # Tie hash - tie(%bighash,'GDBM_File',$sheet->{'coursefilename'}.'.db', + tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db', &GDBM_READER(),0640); - if (! tied(%bighash)) { + if (! tied(%course_db)) { + return 'Could not access course data'; + } + # + my %assesslist = (); + foreach ('Feedback','Evaluation','Tutoring','Discussion') { + my $symb = '_'.lc($_); + $assesslist{$symb} = join(':',('symb',$symb,0,0, + &Apache::lonnet::escape($_))); + } + # + while (my ($key,$srcf) = each(%course_db)) { + next if ($key !~ /^src_(\d+)\.(\d+)$/); + my $mapid = $1; + my $resid = $2; + my $id = $mapid.'.'.$resid; + if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) { + my $symb= + &Apache::lonnet::declutter($course_db{'map_id_'.$mapid}). + '___'.$resid.'___'.&Apache::lonnet::declutter($srcf); + $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'}}); + } + +} + +sub get_assess_rowlabels { + my ($sheet) = @_; + # + my %course_db; + # + my $stype = $sheet->{'sheettype'}; + my $uname = $sheet->{'uname'}; + my $udom = $sheet->{'udom'}; + my $usymb = $sheet->{'usymb'}; + # + $sheet->{'rowlabel'} = {}; + my $identifier =$sheet->{'coursefilename'}.'_'.$stype.'_'.$usymb; + # + if ($rowlabel_cache{$identifier}) { + %{$sheet->{'rowlabel'}}=split(/___;___/,$rowlabel_cache{$identifier}); + } else { + # Get the data and store it in the cache + # Tie hash + tie(%course_db,'GDBM_File',$sheet->{'coursefilename'}.'.db', + &GDBM_READER(),0640); + if (! tied(%course_db)) { return 'Could not access course data'; } - # Get all assessments # - # parameter_labels is used in the assessment sheets to provide labels - # for the parameters. my %parameter_labels= ('timestamp' => 'parameter:Timestamp of Last Transaction<br>timestamp', @@ -1980,29 +2338,16 @@ sub updatestudentassesssheet { 'parameter:Number of Tutor Responses<br>tutornumber', 'totalpoints' => 'parameter:Total Points Granted<br>totalpoints'); - # - # assesslist holds the descriptions of all assessments - my %assesslist; - foreach ('Feedback','Evaluation','Tutoring','Discussion') { - my $symb = '_'.lc($_); - $assesslist{$symb} = join(':',('symb',$symb,$uname,$udom,$_)); - } - while (($_,undef) = each(%bighash)) { - next if ($_!~/^src\_(\d+)\.(\d+)$/); - my $mapid=$1; - my $resid=$2; - my $id=$mapid.'.'.$resid; - my $srcf=$bighash{$_}; + while (my ($key,$srcf) = each(%course_db)) { + next if ($key !~ /^src_(\d+)\.(\d+)$/); + my $mapid = $1; + my $resid = $2; + my $id = $mapid.'.'.$resid; if ($srcf=~/\.(problem|exam|quiz|assess|survey|form)$/) { - my $symb= - &Apache::lonnet::declutter($bighash{'map_id_'.$mapid}). - '___'.$resid.'___'.&Apache::lonnet::declutter($srcf); - $assesslist{$symb}='symb:'.&Apache::lonnet::escape($symb).':' - .$uname.':'.$udom.':'.$bighash{'title_'.$id}; - next if ($stype ne 'assesscalc'); - foreach my $key (split(/\,/, - &Apache::lonnet::metadata($srcf,'keys') - )) { + # Loop through the metadata for this key + my @Metadata = split(/,/, + &Apache::lonnet::metadata($srcf,'keys')); + foreach my $key (@Metadata) { next if ($key !~ /^(stores|parameter)_/); my $display= &Apache::lonnet::metadata($srcf,$key.'.display'); @@ -2014,46 +2359,40 @@ sub updatestudentassesssheet { $parameter_labels{$key}='parameter:'.$display; } # end of foreach } - } # end of foreach (keys(%bighash)) - untie(%bighash); - # - # %parameter_labels has a list of storage and parameter displays by - # unikey - # %assesslist has a list of all resource, by symb - # - if ($stype eq 'assesscalc') { - $sheet->{'rowlabel'} = \%parameter_labels; - } elsif ($stype eq 'studentcalc') { - $sheet->{'rowlabel'} = \%assesslist; - } - $updatedata{$sheet->{'coursefilename'}.'_'.$stype.'_' - .$uname.'_'.$udom}= - join('___;___',%{$sheet->{'rowlabel'}}); - # Get current from cache + } + untie(%course_db); + # Store away the results + $sheet->{'rowlabel'} = \%parameter_labels; + $rowlabel_cache{$identifier}=join('___;___',%{$sheet->{'rowlabel'}}); } - # Find discrepancies between the course row table and this - # + +} + +sub updatestudentassesssheet { + my $sheet = shift; + if ($sheet->{'sheettype'} eq 'studentcalc') { + &get_student_rowlabels($sheet); + } else { + &get_assess_rowlabels($sheet); + } + # Determine if any of the information has changed my %f=&getformulas($sheet); my $changed=0; $sheet->{'maxrow'} = 0; my %existing=(); # Now obsolete rows - foreach (keys(%f)) { - next if ($_!~/^A(\d+)/); - if ($1 > $sheet->{'maxrow'}) { - $sheet->{'maxrow'} = $1; - } - my ($usy,$ufn)=split(/__&&&\__/,$f{$_}); + foreach my $cell (keys(%f)) { + my $formula = $f{$cell}; + next if ($cell !~ /^A(\d+)/); + $sheet->{'maxrow'} = $1 if ($1 > $sheet->{'maxrow'}); + my ($usy,$ufn)=split(/__&&&\__/,$formula); $existing{$usy}=1; unless ((exists($sheet->{'rowlabel'}->{$usy}) && (defined($sheet->{'rowlabel'}->{$usy})) || (!$1) || - ($f{$_}=~/^(~~~|---)/))){ + ($formula =~ /^(~~~|---)/) )) { $f{$_}='!!! Obsolete'; $changed=1; - } elsif ($ufn) { - $sheet->{'rowlabel'}->{$usy} - =~s/assesscalc\?usymb\=/assesscalc\?ufn\=$ufn\&usymb\=/; } } # New and unknown keys @@ -2068,16 +2407,14 @@ sub updatestudentassesssheet { $sheet->{'f'} = \%f; &setformulas($sheet); } - # - undef %existing; } # ------------------------------------------------ Load data for one assessment -sub loadstudent { - my ($sheet)=@_; - my %c=(); - my %f=&getformulas($sheet); +sub loadstudent{ + my ($sheet,$r,$c)=@_; + my %constants=(); + my %formulas=&getformulas($sheet); $cachedassess=$sheet->{'uname'}.':'.$sheet->{'udom'}; # Get ALL the student preformance data my @tmp = &Apache::lonnet::dump($sheet->{'cid'}, @@ -2090,48 +2427,51 @@ sub loadstudent { undef @tmp; # my @assessdata=(); - foreach (keys(%f)) { - next if ($_!~/^A(\d+)/); + foreach my $cell (keys(%formulas)) { + my $value = $formulas{$cell}; + if(defined($c) && ($c->aborted())) { + last; + } + next if ($cell !~ /^A(\d+)/); my $row=$1; - next if (($f{$_}=~/^[\!\~\-]/) || ($row==0)); - my ($usy,$ufn)=split(/__&&&\__/,$f{$_}); + next if (($value =~ /^[!~-]/) || ($row==0)); + my ($usy,$ufn)=split(/__&&&\__/,$value); @assessdata=&exportsheet($sheet,$sheet->{'uname'}, $sheet->{'udom'}, - 'assesscalc',$usy,$ufn); + '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') { - if ($assessdata[$index]) { - my $col=$_; + 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])) { if ($assessdata[$index]=~/\D/) { - $c{$col.$row}="'".$assessdata[$index]."'"; + $constants{$col.$row}="'".$assessdata[$index]."'"; } else { - $c{$col.$row}=$assessdata[$index]; - } - unless ($col eq 'A') { - $f{$col.$row}='import'; + $constants{$col.$row}=$assessdata[$index]; } + $formulas{$col.$row}='import' if ($col ne 'A'); } $index++; } } $cachedassess=''; undef %cachedstores; - $sheet->{'f'} = \%f; + $sheet->{'f'} = \%formulas; &setformulas($sheet); - &setconstants($sheet,\%c); + &setconstants($sheet,\%constants); } # --------------------------------------------------- Load data for one student # sub loadcourse { - my ($sheet,$r)=@_; - my %c=(); - my %f=&getformulas($sheet); + my ($sheet,$r,$c)=@_; + # + my %constants=(); + my %formulas=&getformulas($sheet); + # my $total=0; - foreach (keys(%f)) { + foreach (keys(%formulas)) { if ($_=~/^A(\d+)/) { - unless ($f{$_}=~/^[\!\~\-]/) { $total++; } + unless ($formulas{$_}=~/^[\!\~\-]/) { $total++; } } } my $now=0; @@ -2148,12 +2488,16 @@ sub loadcourse { </script> ENDPOP $r->rflush(); - foreach (keys(%f)) { + foreach (keys(%formulas)) { + if(defined($c) && ($c->aborted())) { + last; + } next if ($_!~/^A(\d+)/); my $row=$1; - next if (($f{$_}=~/^[\!\~\-]/) || ($row==0)); - my ($sname,$sdom) = split(':',$f{$_}); - my @studentdata=&exportsheet($sheet,$sname,$sdom,'studentcalc'); + next if (($formulas{$_}=~/^[\!\~\-]/) || ($row==0)); + my ($sname,$sdom) = split(':',$formulas{$_}); + my @studentdata=&exportsheet($sheet,$sname,$sdom,'studentcalc', + undef,undef,$r); undef %userrdatas; $now++; $r->print('<script>popwin.document.popremain.remaining.value="'. @@ -2167,20 +2511,20 @@ ENDPOP if (defined($studentdata[$index])) { my $col=$_; if ($studentdata[$index]=~/\D/) { - $c{$col.$row}="'".$studentdata[$index]."'"; + $constants{$col.$row}="'".$studentdata[$index]."'"; } else { - $c{$col.$row}=$studentdata[$index]; + $constants{$col.$row}=$studentdata[$index]; } unless ($col eq 'A') { - $f{$col.$row}='import'; + $formulas{$col.$row}='import'; } } $index++; } } - $sheet->{'f'}=\%f; + $sheet->{'f'}=\%formulas; &setformulas($sheet); - &setconstants($sheet,\%c); + &setconstants($sheet,\%constants); $r->print('<script>popwin.close()</script>'); $r->rflush(); } @@ -2188,7 +2532,7 @@ ENDPOP # ------------------------------------------------ Load data for one assessment # sub loadassessment { - my ($sheet)=@_; + my ($sheet,$r,$c)=@_; my $uhome = $sheet->{'uhome'}; my $uname = $sheet->{'uname'}; @@ -2338,8 +2682,7 @@ sub selectbox { sub updatesheet { my ($sheet)=@_; - my $stype=$sheet->{'sheettype'}; - if ($stype eq 'classcalc') { + if ($sheet->{'sheettype'} eq 'classcalc') { return &updateclasssheet($sheet); } else { return &updatestudentassesssheet($sheet); @@ -2353,13 +2696,14 @@ sub updatesheet { sub loadrows { my ($sheet,$r)=@_; + my $c = $r->connection; my $stype=$sheet->{'sheettype'}; if ($stype eq 'classcalc') { - &loadcourse($sheet,$r); + &loadcourse($sheet,$r,$c); } elsif ($stype eq 'studentcalc') { - &loadstudent($sheet); + &loadstudent($sheet,$r,$c); } else { - &loadassessment($sheet); + &loadassessment($sheet,$r,$c); } } @@ -2367,7 +2711,11 @@ sub loadrows { sub checkthis { my ($keyname,$time)=@_; - return ($time<$expiredates{$keyname}); + if (! exists($expiredates{$keyname})) { + return 0; + } else { + return ($time<$expiredates{$keyname}); + } } sub forcedrecalc { @@ -2385,7 +2733,7 @@ sub forcedrecalc { &checkthis($uname.':'.$udom.':assesscalc:'.$map,$time) || &checkthis($uname.':'.$udom.':assesscalc:'.$usymb,$time)) { return 1; - } + } } else { if (&checkthis('::studentcalc:',$time) || &checkthis($uname.':'.$udom.':studentcalc:',$time)) { @@ -2400,11 +2748,13 @@ sub forcedrecalc { # returns the export row for a spreadsheet. # sub exportsheet { - my ($sheet,$uname,$udom,$stype,$usymb,$fn)=@_; + my ($sheet,$uname,$udom,$stype,$usymb,$fn,$r)=@_; + my $flag = 0; $uname = $uname || $sheet->{'uname'}; $udom = $udom || $sheet->{'udom'}; $stype = $stype || $sheet->{'sheettype'}; my @exportarr=(); + # This handles the assessment sheets for '_feedback', etc if (defined($usymb) && ($usymb=~/^\_(\w+)/) && (!defined($fn) || $fn eq '')) { $fn='default_'.$1; @@ -2450,11 +2800,11 @@ sub exportsheet { } # # Not cached - # + # my ($newsheet)=&makenewsheet($uname,$udom,$stype,$usymb); &readsheet($newsheet,$fn); &updatesheet($newsheet); - &loadrows($newsheet); + &loadrows($newsheet,$r); &calcsheet($newsheet); @exportarr=&exportdata($newsheet); ## @@ -2469,14 +2819,24 @@ sub exportsheet { [$key], $sheet->{'cdom'},$sheet->{'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'}); 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; + } } } # @@ -2495,17 +2855,19 @@ 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 }, + $sheet->{'cdom'}, + $sheet->{'cnum'}); + } else { + my $result = &Apache::lonnet::put('nohist_calculatedsheets_'.$sheet->{'cid'}, + { $key => $newstore, + $timekey => $now }, + $sheet->{'udom'}, + $sheet->{'uname'}); } return @exportarr; } @@ -2514,13 +2876,13 @@ sub exportsheet { # # Load previously cached student spreadsheets for this course # -sub expirationdates { +sub load_spreadsheet_expirationdates { undef %expiredates; my $cid=$ENV{'request.course.id'}; my @tmp = &Apache::lonnet::dump('nohist_expirationdates', $ENV{'course.'.$cid.'.domain'}, $ENV{'course.'.$cid.'.num'}); - if (lc($tmp[0])!~/^error/){ + if (lc($tmp[0]) !~ /^error/){ %expiredates = @tmp; } } @@ -2552,18 +2914,22 @@ sub cachedssheets { my ($sheet,$uname,$udom) = @_; $uname = $uname || $sheet->{'uname'}; $udom = $udom || $sheet->{'udom'}; - if (! $loadedcaches{$sheet->{'uname'}.'_'.$sheet->{'udom'}}) { - my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets', + if (! $loadedcaches{$uname.'_'.$udom}) { + my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets_'. + $ENV{'request.course.id'}, $sheet->{'udom'}, $sheet->{'uname'}); if ($tmp[0] !~ /^error/) { - my %StupidTempHash = @tmp; - while (my ($key,$value) = each %StupidTempHash) { + 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 @@ -2579,12 +2945,19 @@ sub cachedssheets { sub handler { my $r=shift; + my ($sheettype) = ($r->uri=~/\/(\w+)$/); + if (! exists($ENV{'form.Status'})) { $ENV{'form.Status'} = 'Active'; } - if (! exists($ENV{'form.output'})) { + 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; } @@ -2592,15 +2965,22 @@ sub handler { $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 @@ -2608,17 +2988,32 @@ sub handler { $r->uri.":opa:0:0:Cannot modify spreadsheet"; return HTTP_NOT_ACCEPTABLE; } + # # Get query string for limited number of parameters - &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, - ['uname','udom','usymb','ufn']); + # + &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'})); } - if (($ENV{'form.usymb'}=~/^\_(\w+)/) && (!$ENV{'form.ufn'})) { + # + # 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'}; } @@ -2634,24 +3029,57 @@ sub handler { $adom=$ENV{'form.udom'}; } # - # Open page + # Open page, try to prevent browser cache. + # $r->content_type('text/html'); $r->header_out('Cache-control','no-cache'); $r->header_out('Pragma','no-cache'); $r->send_http_header; - # Screen output + # + # Header.... + # $r->print('<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"> - function celledit(cn,cf) { - var cnf=prompt(cn,cf); - if (cnf!=null) { - document.sheet.unewfield.value=cn; - document.sheet.unewformula.value=cnf; - document.sheet.submit(); + 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) { @@ -2670,7 +3098,7 @@ sub handler { ENDSCRIPT } $r->print('</head>'.&Apache::loncommon::bodytag('Grades Spreadsheet'). - '<form action="'.$r->uri.'" name=sheet method=post>'); + '<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'}). @@ -2679,24 +3107,49 @@ ENDSCRIPT $r->rflush(); # # Full recalc? + # if ($ENV{'form.forcerecalc'}) { $r->print('<h4>Completely Recalculating Sheet ...</h4>'); undef %spreadsheets; undef %courserdatas; undef %userrdatas; undef %defaultsheets; - undef %updatedata; + undef %rowlabel_cache; } # Read new sheet or modified worksheet - $r->uri=~/\/(\w+)$/; - my ($sheet)=&makenewsheet($aname,$adom,$1,$ENV{'form.usymb'}); + 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>'); + #$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'}; &tmpread($sheet,$ENV{'form.unewfield'},$ENV{'form.unewformula'}); } elsif ($ENV{'form.saveas'}) { @@ -2705,43 +3158,23 @@ ENDSCRIPT } else { &readsheet($sheet,$ENV{'form.ufn'}); } - # Print out user information - if ($sheet->{'sheettype'} ne 'classcalc') { - $r->print('<p><b>User:</b> '.$sheet->{'uname'}. - '<br><b>Domain:</b> '.$sheet->{'udom'}); - $r->print('<br><b>Section/Group:</b> '.$sheet->{'csec'}); - if ($ENV{'form.usymb'}) { - $r->print('<br><b>Assessment:</b> <tt>'. - $ENV{'form.usymb'}.'</tt>'); - } - } - # - # 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; - } - } # Additional options - $r->print('<br />'. - '<input type="submit" name="forcerecalc" '. - 'value="Completely Recalculate Sheet"><p>'); 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>'); + '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>'); + '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'}; @@ -2770,10 +3203,18 @@ ENDSCRIPT &othersheets($sheet,'assesscalc')); } } - # Cached sheets - &expirationdates(); - undef %oldsheets; - undef %loadedcaches; + # + # 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(); @@ -2813,12 +3254,12 @@ ENDSCRIPT } # # Write the modified worksheet - $r->print('<b>Current sheet:</b> '.$sheet->{'filename'}.'<p>'); + $r->print('<b>Current sheet:</b> '.$sheet->{'filename'}.'</p>'); &tmpwrite($sheet); - if ($sheet->{'sheettype'} eq 'studentcalc') { - $r->print('<br>Show rows with empty A column: '); + if ($sheet->{'sheettype'} eq 'assesscalc') { + $r->print('<p>Show rows with empty A column: '); } else { - $r->print('<br>Show empty rows: '); + $r->print('<p>Show empty rows: '); } # $r->print(&hiddenfield('userselhidden','true'). @@ -2837,16 +3278,28 @@ ENDSCRIPT } $r->print('>'); # - # CSV format checkbox (classcalc sheets only) - $r->print(' Output as <select name="output" size="1" onClick="submit()">'. + # output format select box + $r->print(' Output as <select name="output" size="1" onChange="submit()">'. "\n"); - foreach my $mode (qw/HTML CSV EXCEL/) { + 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->{'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') { @@ -2864,7 +3317,7 @@ ENDSCRIPT #value='Insert Row Bottom'><br> #ENDINSERTBUTTONS # Print out sheet - &outsheet($r,$sheet); + &outsheet($sheet,$r); $r->print('</form></body></html>'); # Done return OK; @@ -2872,3 +3325,5 @@ ENDSCRIPT 1; __END__ + +