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