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