Annotation of loncom/interface/spreadsheet/studentcalc.pm, revision 1.5
1.1 matthew 1: #
1.5 ! matthew 2: # $Id: studentcalc.pm,v 1.4 2003/05/22 21:16:35 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;
1.3 matthew 51: use Apache::loncommon();
52: use Apache::loncoursedata();
1.1 matthew 53: use Apache::lonnavmaps;
1.3 matthew 54: use Apache::Spreadsheet();
55: use Apache::assesscalc();
1.1 matthew 56: use HTML::Entities();
57: use Spreadsheet::WriteExcel;
58: use Time::HiRes;
59:
60: @Apache::studentcalc::ISA = ('Apache::Spreadsheet');
61:
62: my @Sequences = ();
63: my %Exportrows = ();
64:
65: my $current_course;
66:
67: sub initialize_package {
68: $current_course = $ENV{'request.course.id'};
69: &initialize_sequence_cache();
70: &load_cached_export_rows();
71: }
72:
73: sub initialize_sequence_cache {
74: #
75: # Set up the sequences and assessments
76: @Sequences = ();
77: my ($top,$sequences,$assessments) =
78: &Apache::loncoursedata::get_sequence_assessment_data();
79: if (! defined($top) || ! ref($top)) {
80: # There has been an error, better report it
1.2 matthew 81: &Apache::lonnet::logthis('top is undefined (studentcalc.pm)');
1.1 matthew 82: return;
83: }
84: @Sequences = @{$sequences} if (ref($sequences) eq 'ARRAY');
85: }
86:
87: sub clear_package {
88: @Sequences = undef;
89: %Exportrows = undef;
90: }
91:
92: sub get_title {
93: my $self = shift;
94: my $title = '';
1.3 matthew 95: my %userenv = &Apache::loncoursedata::GetUserName($self->{'name'},
96: $self->{'domain'});
97: &Apache::lonnet::logthis('userenv = '.join(' ',%userenv));
98: my $name =
99: join(' ',@userenv{'firstname','middlename','lastname','generation'});
100: $name =~ s/\s+$//;
101: $title .= '<h1>'.$name;
102: if ($ENV{'user.name'} ne $self->{'name'} &&
103: $ENV{'user.domain'} ne $self->{'domain'}) {
104: $title .= &Apache::loncommon::aboutmewrapper
105: ($self->{'name'}.'@'.$self->{'domain'},
106: $self->{'name'},$self->{'domain'});
107: }
108: $title .= "</h1>\n";
109: $title .= '<h2>'.$self->{'coursedesc'}."</h2>\n";
110: $title .= '<h3>'.localtime(time).'</h3>';
1.1 matthew 111: return $title;
112: }
113:
114: sub parent_link {
115: my $self = shift;
116: my $link .= '<p><a href="/adm/classcalc?'.
117: 'sname='.$self->{'name'}.
118: '&sdomain='.$self->{'domain'}.'">'.
119: 'Course level sheet</a></p>'."\n";
120: return $link;
121: }
122:
123: sub outsheet_html {
124: my $self = shift;
125: my ($r) = @_;
126: ####################################
127: # Get the list of assessment files #
128: ####################################
129: my @AssessFileNames = $self->othersheets('assesscalc');
1.2 matthew 130: my $editing_is_allowed = &Apache::lonnet::allowed('mgr',
131: $ENV{'request.course.id'});
1.1 matthew 132: ####################################
133: # Determine table structure #
134: ####################################
135: my $num_uneditable = 26;
136: my $num_left = 52-$num_uneditable;
137: my $tableheader =<<"END";
1.2 matthew 138: <p>
1.1 matthew 139: <table border="2">
140: <tr>
141: <th colspan="2" rowspan="2"><font size="+2">Student</font></th>
142: <td bgcolor="#FFDDDD" colspan="$num_uneditable">
143: <b><font size="+1">Import</font></b></td>
144: <td colspan="$num_left">
145: <b><font size="+1">Calculations</font></b></td>
146: </tr><tr>
147: END
148: my $label_num = 0;
149: foreach (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')){
150: if ($label_num<$num_uneditable) {
151: $tableheader .='<td bgcolor="#FFDDDD">';
152: } else {
153: $tableheader .='<td>';
154: }
155: $tableheader .="<b><font size=+1>$_</font></b></td>";
156: $label_num++;
157: }
158: $tableheader .="</tr>\n";
1.4 matthew 159: if ($self->blackout()) {
160: $r->print('<font color="red" size="+2"><p>'.
161: 'Some computations are not available at this time.<br />'.
162: 'There are problems whose status you are allowed to view.'.
163: '</font></p>'."\n");
164: } else {
165: $r->print($tableheader);
166: #
167: # Print out template row
168: if (exists($ENV{'request.role.adv'}) && $ENV{'request.role.adv'}) {
169: $r->print('<tr><td>Template</td><td> </td>'.
170: $self->html_template_row($num_uneditable)."</tr>\n");
171: }
172: #
173: # Print out summary/export row
174: $r->print('<tr><td>Summary</td><td>0</td>'.
175: $self->html_export_row()."</tr>\n");
176: }
1.1 matthew 177: $r->print("</table>\n");
178: #
179: # Prepare to output rows
1.4 matthew 180: if (exists($ENV{'request.role.adv'}) && $ENV{'request.role.adv'}) {
181: $tableheader =<<"END";
1.2 matthew 182: </p><p>
1.1 matthew 183: <table border="2">
184: <tr><th>Row</th><th>Assessment</th>
185: END
1.4 matthew 186: } else {
187: $tableheader =<<"END";
188: </p><p>
189: <table border="2">
190: <tr><th> </th><th>Assessment</th>
191: END
192: }
1.1 matthew 193: foreach (split(//,'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')){
194: if ($label_num<$num_uneditable) {
195: $tableheader.='<td bgcolor="#FFDDDD">';
196: } else {
197: $tableheader.='<td>';
198: }
199: $tableheader.="<b><font size=+1>$_</font></b></td>";
200: }
201: $tableheader.="\n";
202: #
203: my $num_output = 1;
204: if (scalar(@Sequences)< 1) {
205: &initialize_sequence_cache();
206: }
207: foreach my $Sequence (@Sequences) {
208: next if ($Sequence->{'num_assess'} < 1);
209: $r->print("<h3>".$Sequence->{'title'}."</h3>\n");
210: $r->print($tableheader);
211: foreach my $resource (@{$Sequence->{'contents'}}) {
212: next if ($resource->{'type'} ne 'assessment');
213: my $rownum = $self->get_row_number_from_key($resource->{'symb'});
214: my $assess_filename = $self->{'row_source'}->{$rownum};
1.2 matthew 215: my $row_output = '<tr>';
216: if ($editing_is_allowed) {
217: $row_output .= '<td>'.$rownum.'</td>';
218: $row_output .= '<td>'.
219: '<a href="/adm/assesscalc?sname='.$self->{'name'}.
220: '&sdomain='.$self->{'domain'}.
221: '&filename='.$assess_filename.
1.4 matthew 222: '&usymb='.&Apache::lonnet::escape($resource->{'symb'}).
223: '">'.$resource->{'title'}.'</a><br />';
1.2 matthew 224: $row_output .= &assess_file_selector($rownum,
225: $assess_filename,
226: \@AssessFileNames).
227: '</td>';
228: } else {
229: $row_output .= '<td><a href="'.$resource->{'src'}.'?symb='.
1.4 matthew 230: &Apache::lonnet::escape($resource->{'symb'}).
231: '">Go To</a>';
1.2 matthew 232: $row_output .= '</td><td>'.$resource->{'title'}.'</td>';
233: }
1.4 matthew 234: if ($self->blackout() && $self->{'blackout_rows'}->{$rownum}>0) {
235: $row_output .=
236: '<td colspan="52">Unavailable at this time</td></tr>'."\n";
237: } else {
238: $row_output .= $self->html_row($num_uneditable,$rownum).
239: "</tr>\n";
240: }
1.1 matthew 241: $r->print($row_output);
242: }
243: $r->print("</table>\n");
244: }
1.2 matthew 245: $r->print("</p>\n");
1.1 matthew 246: return;
247: }
248:
249: ########################################################
250: ########################################################
251:
252: =pod
253:
254: =item &assess_file_selector()
255:
256: =cut
257:
258: ########################################################
259: ########################################################
260: sub assess_file_selector {
261: my ($row,$default,$AssessFiles)=@_;
1.2 matthew 262: if (!defined($AssessFiles) || ! @$AssessFiles) {
263: return '';
264: }
1.1 matthew 265: return '' if (! &Apache::lonnet::allowed('mgr',$ENV{'request.course.id'}));
266: my $element_name = 'FileSelect_'.$row;
267: my $load_dialog = '<select size="1" name="'.$element_name.'" '.
268: 'onchange="'.
269: "document.sheet.cell.value='source_$row';".
270: "document.sheet.newformula.value=document.sheet.$element_name\.value;".
271: 'document.sheet.submit()" '.'>'."\n";
272: foreach my $file (@{$AssessFiles}) {
273: $load_dialog .= ' <option name="'.$file.'"';
274: $load_dialog .= ' selected' if ($default eq $file);
275: $load_dialog .= '>'.$file."</option>\n";
276: }
277: $load_dialog .= "</select>\n";
278: return $load_dialog;
279: }
280:
281: sub modify_cell {
282: my $self = shift;
283: my ($cell,$formula) = @_;
284: if ($cell =~ /^source_(\d+)$/) {
285: # Need to make sure $formula is a valid filename....
286: my $row = $1;
287: $cell = 'A'.$row;
288: $self->{'row_source'}->{$row} = $formula;
289: my $original_source = $self->formula($cell);
290: if ($original_source =~ /__&&&__/) {
291: ($original_source,undef) = split('__&&&__',$original_source);
292: }
293: $formula = $original_source.'__&&&__'.$formula;
294: } elsif ($cell =~ /([A-z])\-/) {
295: $cell = 'template_'.$1;
296: } elsif ($cell !~ /^([A-z](\d+)|template_[A-z])$/) {
297: return;
298: }
299: $self->set_formula($cell,$formula);
300: $self->rebuild_stats();
301: return;
302: }
303:
304: sub outsheet_csv {
305: my $self = shift;
306: my ($r) = @_;
307: }
308: sub outsheet_excel {
309: my $self = shift;
310: my ($r) = @_;
311: }
312: sub outsheet_recursive_excel {
313: my $self = shift;
314: my ($r) = @_;
315: }
316:
317: sub display {
318: my $self = shift;
319: my ($r) = @_;
320: $self->compute();
321: $self->outsheet_html($r);
322: return;
323: }
324:
325: sub set_row_sources {
326: my $self = shift;
327: while (my ($cell,$value) = each(%{$self->{'formulas'}})) {
328: next if ($cell !~ /^A(\d+)/ && $1 > 0);
329: my $row = $1;
330: (undef,$value) = split('__&&&__',$value);
331: $value = 'Default' if (! defined($value));
332: $self->{'row_source'}->{$row} = $value;
333: }
334: return;
335: }
336:
337: sub compute {
338: my $self = shift;
339: $self->logthis('computing');
340: if (! defined($current_course) ||
341: $current_course ne $ENV{'request.course.id'}) {
342: $current_course = $ENV{'request.course.id'};
343: &clear_package();
344: &initialize_sequence_cache();
345: }
346: $self->initialize_safe_space();
347: my @sequences = @Sequences;
348: if (@sequences < 1) {
349: my ($top,$sequences,$assessments) =
350: &Apache::loncoursedata::get_sequence_assessment_data();
351: if (! defined($top) || ! ref($top)) {
352: &Apache::lonnet::logthis('top is undefined');
353: return;
354: }
355: @sequences = @{$sequences} if (ref($sequences) eq 'ARRAY');
356: }
357: &Apache::assesscalc::initialize_package($self->{'name'},$self->{'domain'});
358: my %f = $self->formulas();
359: #
360: # Process the formulas list -
361: # the formula for the A column of a row is symb__&&__filename
362: my %c = $self->constants();
363: foreach my $seq (@sequences) {
364: next if ($seq->{'num_assess'}<1);
365: foreach my $resource (@{$seq->{'contents'}}) {
366: next if ($resource->{'type'} ne 'assessment');
367: my $rownum = $self->get_row_number_from_key($resource->{'symb'});
368: my $cell = 'A'.$rownum;
369: my $assess_filename = 'Default';
370: if (exists($self->{'row_source'}->{$rownum})) {
371: $assess_filename = $self->{'row_source'}->{$rownum};
372: } else {
373: $self->{'row_source'}->{$rownum} = $assess_filename;
374: }
375: $f{$cell} = $resource->{'symb'}.'__&&&__'.$assess_filename;
376: my $assessSheet = Apache::assesscalc->new($self->{'name'},
377: $self->{'domain'},
378: $assess_filename,
379: $resource->{'symb'});
380: my @exportdata = $assessSheet->export_data();
1.4 matthew 381: if ($assessSheet->blackout()) {
382: $self->blackout(1);
383: $self->{'blackout_rows'}->{$rownum} = 1;
384: }
1.1 matthew 385: #
386: # Be sure not to disturb the formulas in the 'A' column
387: my $data = shift(@exportdata);
388: $c{$cell} = $data if (defined($data));
389: #
390: # Deal with the remaining columns
391: my $i=0;
392: foreach (split(//,'BCDEFGHIJKLMNOPQRSTUVWXYZ')) {
393: my $cell = $_.$rownum;
394: my $data = shift(@exportdata);
395: if (defined($data)) {
396: $f{$cell} = 'import';
397: $c{$cell} = $data;
398: }
399: $i++;
400: }
401: }
402: }
403: $self->constants(\%c);
404: $self->formulas(\%f);
405: $self->calcsheet();
406: #
407: # Store export row in cache
408: my @exportarray=$self->exportrow();
409: my $student = $self->{'name'}.':'.$self->{'domain'};
410: $Exportrows{$student}->{'time'} = time;
411: $Exportrows{$student}->{'data'} = \@exportarray;
412: # save export row
413: $self->save_export_data();
414: return;
415: }
416:
417: sub set_row_numbers {
418: my $self = shift;
419: while (my ($cell,$formula) = each(%{$self->{'formulas'}})) {
420: next if ($cell !~ /^A(\d+)/);
421: my $row = $1;
422: next if ($row == 0);
423: my ($symb,undef) = split('__&&&__',$formula);
424: $self->{'row_numbers'}->{$symb} = $row;
425: }
426: }
427:
428: sub get_row_number_from_symb {
429: my $self = shift;
430: my ($key) = @_;
431: ($key,undef) = split('__&&&__',$key) if ($key =~ /__&&&__/);
432: return $self->get_row_number_from_key($key);
433: }
434:
435: #############################################
436: #############################################
437:
438: =pod
439:
440: =item &load_cached_export_rows
441:
442: Retrieves and parsers the export rows of the student spreadsheets.
443: These rows are saved in the courses directory in the format:
444:
445: sname:sdom:studentcalc:.time => time
446:
447: sname:sdom:studentcalc => ___=___Adata___;___Bdata___;___Cdata___;___ .....
448:
449: =cut
450:
451: #############################################
452: #############################################
453: sub load_cached_export_rows {
454: %Exportrows = undef;
455: my @tmp = &Apache::lonnet::dump('nohist_calculatedsheets',
456: $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
457: $ENV{'course.'.$ENV{'request.course.id'}.'.num'},undef);
458: my %Selected_Assess_Sheet;
459: if ($tmp[0] =~ /^error/) {
460: &Apache::lonnet::logthis('unable to read cached student export rows '.
461: 'for course '.$ENV{'request.course.id'});
462: return;
463: }
464: my %tmp = @tmp;
465: while (my ($key,$sheetdata) = each(%tmp)) {
466: my ($sname,$sdom,$sheettype,$remainder) = split(':',$key);
467: my $student = $sname.':'.$sdom;
468: if ($remainder =~ /\.time/) {
469: $Exportrows{$student}->{'time'} = $sheetdata;
470: } else {
471: $sheetdata =~ s/^___=___//;
472: my @Data = split('___;___',$sheetdata);
473: $Exportrows{$student}->{'data'} = \@Data;
474: }
475: }
476: }
477:
478: #############################################
479: #############################################
480:
481: =pod
482:
483: =item &save_export_data()
484:
485: Writes the export data for this student to the course cache.
486:
487: =cut
488:
489: #############################################
490: #############################################
491: sub save_export_data {
492: my $self = shift;
1.5 ! matthew 493: return if ($self->temporary());
1.1 matthew 494: my $student = $self->{'name'}.':'.$self->{'domain'};
495: return if (! exists($Exportrows{$student}));
496: return if (! $self->is_default());
497: my $key = join(':',($self->{'name'},$self->{'domain'},'studentcalc')).':';
498: my $timekey = $key.'.time';
499: my $newstore = join('___;___',
500: @{$Exportrows{$student}->{'data'}});
501: $newstore = '___=___'.$newstore;
502: &Apache::lonnet::put('nohist_calculatedsheets',
503: { $key => $newstore,
504: $timekey => $Exportrows{$student}->{'time'} },
505: $self->{'cdom'},
506: $self->{'cnum'});
507: return;
508: }
509:
510: #############################################
511: #############################################
512:
513: =pod
514:
515: =item &export_data()
516:
517: Returns the export data associated with the spreadsheet. Computes the
518: spreadsheet only if necessary.
519:
520: =cut
521:
522: #############################################
523: #############################################
524: sub export_data {
525: my $self = shift;
526: my $student = $self->{'name'}.':'.$self->{'domain'};
527: if (! exists($Exportrows{$student}) ||
528: ! $self->check_expiration_time($Exportrows{$student}->{'time'})) {
529: $self->compute();
530: }
531: my @Data = @{$Exportrows{$student}->{'data'}};
532: for (my $i=0; $i<=$#Data;$i++) {
533: $Data[$i]="'".$Data[$i]."'" if ($Data[$i]=~/\D/ && defined($Data[$i]));
534: }
535: return @Data;
536: }
537:
538: 1;
539:
540: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>