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