Annotation of loncom/interface/spreadsheet/studentcalc.pm, revision 1.2
1.1 matthew 1: #
1.2 ! matthew 2: # $Id: studentcalc.pm,v 1.1 2003/05/16 20:55:11 matthew Exp $
1.1 matthew 3: #
4: # Copyright Michigan State University Board of Trustees
5: #
6: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
7: #
8: # LON-CAPA is free software; you can redistribute it and/or modify
9: # it under the terms of the GNU General Public License as published by
10: # the Free Software Foundation; either version 2 of the License, or
11: # (at your option) any later version.
12: #
13: # LON-CAPA is distributed in the hope that it will be useful,
14: # but WITHOUT ANY WARRANTY; without even the implied warranty of
15: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16: # GNU General Public License for more details.
17: #
18: # You should have received a copy of the GNU General Public License
19: # along with LON-CAPA; if not, write to the Free Software
20: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21: #
22: # /home/httpd/html/adm/gpl.txt
23: #
24: # http://www.lon-capa.org/
25: #
26: # The LearningOnline Network with CAPA
27: # Spreadsheet/Grades Display Handler
28: #
29: # POD required stuff:
30:
31: =head1 NAME
32:
33: studentcalc
34:
35: =head1 SYNOPSIS
36:
37: =head1 DESCRIPTION
38:
39: =over 4
40:
41: =cut
42:
43: ###################################################
44: ### StudentSheet ###
45: ###################################################
46: package Apache::studentcalc;
47:
48: use strict;
49: use Apache::Constants qw(:common :http);
50: use Apache::lonnet;
51: use Apache::lonnavmaps;
52: use Apache::Spreadsheet;
53: use Apache::assesscalc;
54: use HTML::Entities();
55: use Spreadsheet::WriteExcel;
56: use Time::HiRes;
57:
58: @Apache::studentcalc::ISA = ('Apache::Spreadsheet');
59:
60: my @Sequences = ();
61: my %Exportrows = ();
62:
63: my $current_course;
64:
65: sub initialize_package {
66: $current_course = $ENV{'request.course.id'};
67: &initialize_sequence_cache();
68: &load_cached_export_rows();
69: }
70:
71: sub initialize_sequence_cache {
72: #
73: # Set up the sequences and assessments
74: @Sequences = ();
75: my ($top,$sequences,$assessments) =
76: &Apache::loncoursedata::get_sequence_assessment_data();
77: if (! defined($top) || ! ref($top)) {
78: # There has been an error, better report it
1.2 ! matthew 79: &Apache::lonnet::logthis('top is undefined (studentcalc.pm)');
1.1 matthew 80: return;
81: }
82: @Sequences = @{$sequences} if (ref($sequences) eq 'ARRAY');
83: }
84:
85: sub clear_package {
86: @Sequences = undef;
87: %Exportrows = undef;
88: }
89:
90: sub get_title {
91: my $self = shift;
92: my $title = '';
93: $title .= '<h2>Spreadsheet for '.
94: $self->{'name'}.'@'.$self->{'domain'}.'</h2>';
95: return $title;
96: }
97:
98: sub parent_link {
99: my $self = shift;
100: my $link .= '<p><a href="/adm/classcalc?'.
101: 'sname='.$self->{'name'}.
102: '&sdomain='.$self->{'domain'}.'">'.
103: 'Course level sheet</a></p>'."\n";
104: return $link;
105: }
106:
107: sub outsheet_html {
108: my $self = shift;
109: my ($r) = @_;
110: ####################################
111: # Get the list of assessment files #
112: ####################################
113: my @AssessFileNames = $self->othersheets('assesscalc');
1.2 ! matthew 114: my $editing_is_allowed = &Apache::lonnet::allowed('mgr',
! 115: $ENV{'request.course.id'});
1.1 matthew 116: ####################################
117: # Determine table structure #
118: ####################################
119: my $num_uneditable = 26;
120: my $num_left = 52-$num_uneditable;
121: my $tableheader =<<"END";
1.2 ! matthew 122: <p>
1.1 matthew 123: <table border="2">
124: <tr>
125: <th colspan="2" rowspan="2"><font size="+2">Student</font></th>
126: <td bgcolor="#FFDDDD" colspan="$num_uneditable">
127: <b><font size="+1">Import</font></b></td>
128: <td colspan="$num_left">
129: <b><font size="+1">Calculations</font></b></td>
130: </tr><tr>
131: END
132: my $label_num = 0;
133: foreach (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')){
134: if ($label_num<$num_uneditable) {
135: $tableheader .='<td bgcolor="#FFDDDD">';
136: } else {
137: $tableheader .='<td>';
138: }
139: $tableheader .="<b><font size=+1>$_</font></b></td>";
140: $label_num++;
141: }
142: $tableheader .="</tr>\n";
143: #
144: $r->print($tableheader);
145: #
146: # Print out template row
147: $r->print('<tr><td>Template</td><td> </td>'.
148: $self->html_template_row($num_uneditable)."</tr>\n");
149: #
150: # Print out summary/export row
151: $r->print('<tr><td>Export</td><td>0</td>'.
152: $self->html_export_row()."</tr>\n");
153: $r->print("</table>\n");
154: #
155: # Prepare to output rows
156: $tableheader =<<"END";
1.2 ! matthew 157: </p><p>
1.1 matthew 158: <table border="2">
159: <tr><th>Row</th><th>Assessment</th>
160: END
161: foreach (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')){
162: if ($label_num<$num_uneditable) {
163: $tableheader.='<td bgcolor="#FFDDDD">';
164: } else {
165: $tableheader.='<td>';
166: }
167: $tableheader.="<b><font size=+1>$_</font></b></td>";
168: }
169: $tableheader.="\n";
170: #
171: my $num_output = 1;
172: if (scalar(@Sequences)< 1) {
173: &initialize_sequence_cache();
174: }
175: foreach my $Sequence (@Sequences) {
176: next if ($Sequence->{'num_assess'} < 1);
177: $r->print("<h3>".$Sequence->{'title'}."</h3>\n");
178: $r->print($tableheader);
179: foreach my $resource (@{$Sequence->{'contents'}}) {
180: next if ($resource->{'type'} ne 'assessment');
181: my $rownum = $self->get_row_number_from_key($resource->{'symb'});
182: my $assess_filename = $self->{'row_source'}->{$rownum};
1.2 ! matthew 183: my $row_output = '<tr>';
! 184: if ($editing_is_allowed) {
! 185: $row_output .= '<td>'.$rownum.'</td>';
! 186: $row_output .= '<td>'.
! 187: '<a href="/adm/assesscalc?sname='.$self->{'name'}.
! 188: '&sdomain='.$self->{'domain'}.
! 189: '&filename='.$assess_filename.
! 190: '&usymb='.&Apache::lonnet::escape($resource->{'symb'}).'">'.
! 191: $resource->{'title'}.'</a><br />';
! 192: $row_output .= &assess_file_selector($rownum,
! 193: $assess_filename,
! 194: \@AssessFileNames).
! 195: '</td>';
! 196: } else {
! 197: $row_output .= '<td><a href="'.$resource->{'src'}.'?symb='.
! 198: &Apache::lonnet::escape($resource->{'symb'}).'>Go To</a>';
! 199: $row_output .= '</td><td>'.$resource->{'title'}.'</td>';
! 200: }
! 201: $row_output .= $self->html_row($num_uneditable,$rownum).
1.1 matthew 202: "</tr>\n";
203: $r->print($row_output);
204: }
205: $r->print("</table>\n");
206: }
1.2 ! matthew 207: $r->print("</p>\n");
1.1 matthew 208: return;
209: }
210:
211: ########################################################
212: ########################################################
213:
214: =pod
215:
216: =item &assess_file_selector()
217:
218: =cut
219:
220: ########################################################
221: ########################################################
222: sub assess_file_selector {
223: my ($row,$default,$AssessFiles)=@_;
1.2 ! matthew 224: if (!defined($AssessFiles) || ! @$AssessFiles) {
! 225: return '';
! 226: }
1.1 matthew 227: return '' if (! &Apache::lonnet::allowed('mgr',$ENV{'request.course.id'}));
228: my $element_name = 'FileSelect_'.$row;
229: my $load_dialog = '<select size="1" name="'.$element_name.'" '.
230: 'onchange="'.
231: "document.sheet.cell.value='source_$row';".
232: "document.sheet.newformula.value=document.sheet.$element_name\.value;".
233: 'document.sheet.submit()" '.'>'."\n";
234: foreach my $file (@{$AssessFiles}) {
235: $load_dialog .= ' <option name="'.$file.'"';
236: $load_dialog .= ' selected' if ($default eq $file);
237: $load_dialog .= '>'.$file."</option>\n";
238: }
239: $load_dialog .= "</select>\n";
240: return $load_dialog;
241: }
242:
243: sub modify_cell {
244: my $self = shift;
245: my ($cell,$formula) = @_;
246: if ($cell =~ /^source_(\d+)$/) {
247: # Need to make sure $formula is a valid filename....
248: my $row = $1;
249: $cell = 'A'.$row;
250: $self->{'row_source'}->{$row} = $formula;
251: my $original_source = $self->formula($cell);
252: if ($original_source =~ /__&&&__/) {
253: ($original_source,undef) = split('__&&&__',$original_source);
254: }
255: $formula = $original_source.'__&&&__'.$formula;
256: } elsif ($cell =~ /([A-z])\-/) {
257: $cell = 'template_'.$1;
258: } elsif ($cell !~ /^([A-z](\d+)|template_[A-z])$/) {
259: return;
260: }
261: $self->set_formula($cell,$formula);
262: $self->rebuild_stats();
263: return;
264: }
265:
266: sub outsheet_csv {
267: my $self = shift;
268: my ($r) = @_;
269: }
270: sub outsheet_excel {
271: my $self = shift;
272: my ($r) = @_;
273: }
274: sub outsheet_recursive_excel {
275: my $self = shift;
276: my ($r) = @_;
277: }
278:
279: sub display {
280: my $self = shift;
281: my ($r) = @_;
282: $self->compute();
283: $self->outsheet_html($r);
284: return;
285: }
286:
287: sub set_row_sources {
288: my $self = shift;
289: while (my ($cell,$value) = each(%{$self->{'formulas'}})) {
290: next if ($cell !~ /^A(\d+)/ && $1 > 0);
291: my $row = $1;
292: (undef,$value) = split('__&&&__',$value);
293: $value = 'Default' if (! defined($value));
294: $self->{'row_source'}->{$row} = $value;
295: }
296: return;
297: }
298:
299: sub compute {
300: my $self = shift;
301: $self->logthis('computing');
302: if (! defined($current_course) ||
303: $current_course ne $ENV{'request.course.id'}) {
304: $current_course = $ENV{'request.course.id'};
305: &clear_package();
306: &initialize_sequence_cache();
307: }
308: $self->initialize_safe_space();
309: my @sequences = @Sequences;
310: if (@sequences < 1) {
311: my ($top,$sequences,$assessments) =
312: &Apache::loncoursedata::get_sequence_assessment_data();
313: if (! defined($top) || ! ref($top)) {
314: &Apache::lonnet::logthis('top is undefined');
315: return;
316: }
317: @sequences = @{$sequences} if (ref($sequences) eq 'ARRAY');
318: }
319: &Apache::assesscalc::initialize_package($self->{'name'},$self->{'domain'});
320: my %f = $self->formulas();
321: #
322: # Process the formulas list -
323: # the formula for the A column of a row is symb__&&__filename
324: my %c = $self->constants();
325: foreach my $seq (@sequences) {
326: next if ($seq->{'num_assess'}<1);
327: foreach my $resource (@{$seq->{'contents'}}) {
328: next if ($resource->{'type'} ne 'assessment');
329: my $rownum = $self->get_row_number_from_key($resource->{'symb'});
330: my $cell = 'A'.$rownum;
331: my $assess_filename = 'Default';
332: if (exists($self->{'row_source'}->{$rownum})) {
333: $assess_filename = $self->{'row_source'}->{$rownum};
334: } else {
335: $self->{'row_source'}->{$rownum} = $assess_filename;
336: }
337: $f{$cell} = $resource->{'symb'}.'__&&&__'.$assess_filename;
338: my $assessSheet = Apache::assesscalc->new($self->{'name'},
339: $self->{'domain'},
340: $assess_filename,
341: $resource->{'symb'});
342: my @exportdata = $assessSheet->export_data();
343: #
344: # Be sure not to disturb the formulas in the 'A' column
345: my $data = shift(@exportdata);
346: $c{$cell} = $data if (defined($data));
347: #
348: # Deal with the remaining columns
349: my $i=0;
350: foreach (split(//,'BCDEFGHIJKLMNOPQRSTUVWXYZ')) {
351: my $cell = $_.$rownum;
352: my $data = shift(@exportdata);
353: if (defined($data)) {
354: $f{$cell} = 'import';
355: $c{$cell} = $data;
356: }
357: $i++;
358: }
359: }
360: }
361: $self->constants(\%c);
362: $self->formulas(\%f);
363: $self->calcsheet();
364: #
365: # Store export row in cache
366: my @exportarray=$self->exportrow();
367: my $student = $self->{'name'}.':'.$self->{'domain'};
368: $Exportrows{$student}->{'time'} = time;
369: $Exportrows{$student}->{'data'} = \@exportarray;
370: # save export row
371: $self->save_export_data();
372: return;
373: }
374:
375: sub set_row_numbers {
376: my $self = shift;
377: while (my ($cell,$formula) = each(%{$self->{'formulas'}})) {
378: next if ($cell !~ /^A(\d+)/);
379: my $row = $1;
380: next if ($row == 0);
381: my ($symb,undef) = split('__&&&__',$formula);
382: $self->{'row_numbers'}->{$symb} = $row;
383: }
384: }
385:
386: sub get_row_number_from_symb {
387: my $self = shift;
388: my ($key) = @_;
389: ($key,undef) = split('__&&&__',$key) if ($key =~ /__&&&__/);
390: return $self->get_row_number_from_key($key);
391: }
392:
393: #############################################
394: #############################################
395:
396: =pod
397:
398: =item &load_cached_export_rows
399:
400: Retrieves and parsers the export rows of the student spreadsheets.
401: These rows are saved in the courses directory in the format:
402:
403: sname:sdom:studentcalc:.time => time
404:
405: sname:sdom:studentcalc => ___=___Adata___;___Bdata___;___Cdata___;___ .....
406:
407: =cut
408:
409: #############################################
410: #############################################
411: sub load_cached_export_rows {
412: %Exportrows = undef;
413: my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets',
414: $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
415: $ENV{'course.'.$ENV{'request.course.id'}.'.num'},undef);
416: my %Selected_Assess_Sheet;
417: if ($tmp[0] =~ /^error/) {
418: &Apache::lonnet::logthis('unable to read cached student export rows '.
419: 'for course '.$ENV{'request.course.id'});
420: return;
421: }
422: my %tmp = @tmp;
423: while (my ($key,$sheetdata) = each(%tmp)) {
424: my ($sname,$sdom,$sheettype,$remainder) = split(':',$key);
425: my $student = $sname.':'.$sdom;
426: if ($remainder =~ /\.time/) {
427: $Exportrows{$student}->{'time'} = $sheetdata;
428: } else {
429: $sheetdata =~ s/^___=___//;
430: my @Data = split('___;___',$sheetdata);
431: $Exportrows{$student}->{'data'} = \@Data;
432: }
433: }
434: }
435:
436: #############################################
437: #############################################
438:
439: =pod
440:
441: =item &save_export_data()
442:
443: Writes the export data for this student to the course cache.
444:
445: =cut
446:
447: #############################################
448: #############################################
449: sub save_export_data {
450: my $self = shift;
451: my $student = $self->{'name'}.':'.$self->{'domain'};
452: return if (! exists($Exportrows{$student}));
453: return if (! $self->is_default());
454: my $key = join(':',($self->{'name'},$self->{'domain'},'studentcalc')).':';
455: my $timekey = $key.'.time';
456: my $newstore = join('___;___',
457: @{$Exportrows{$student}->{'data'}});
458: $newstore = '___=___'.$newstore;
459: &Apache::lonnet::put('nohist_calculatedsheets',
460: { $key => $newstore,
461: $timekey => $Exportrows{$student}->{'time'} },
462: $self->{'cdom'},
463: $self->{'cnum'});
464: return;
465: }
466:
467: #############################################
468: #############################################
469:
470: =pod
471:
472: =item &export_data()
473:
474: Returns the export data associated with the spreadsheet. Computes the
475: spreadsheet only if necessary.
476:
477: =cut
478:
479: #############################################
480: #############################################
481: sub export_data {
482: my $self = shift;
483: my $student = $self->{'name'}.':'.$self->{'domain'};
484: if (! exists($Exportrows{$student}) ||
485: ! $self->check_expiration_time($Exportrows{$student}->{'time'})) {
486: $self->compute();
487: }
488: my @Data = @{$Exportrows{$student}->{'data'}};
489: for (my $i=0; $i<=$#Data;$i++) {
490: $Data[$i]="'".$Data[$i]."'" if ($Data[$i]=~/\D/ && defined($Data[$i]));
491: }
492: return @Data;
493: }
494:
495: 1;
496:
497: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>