1: # The LearningOnline Network with CAPA
2: #
3: # $Id: lonstatistics.pm,v 1.63 2003/03/03 19:17:51 matthew Exp $
4: #
5: # Copyright Michigan State University Board of Trustees
6: #
7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
8: #
9: # LON-CAPA is free software; you can redistribute it and/or modify
10: # it under the terms of the GNU General Public License as published by
11: # the Free Software Foundation; either version 2 of the License, or
12: # (at your option) any later version.
13: #
14: # LON-CAPA is distributed in the hope that it will be useful,
15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17: # GNU General Public License for more details.
18: #
19: # You should have received a copy of the GNU General Public License
20: # along with LON-CAPA; if not, write to the Free Software
21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22: #
23: # /home/httpd/html/adm/gpl.txt
24: #
25: # http://www.lon-capa.org/
26: #
27: # (Navigate problems for statistical reports
28: #
29: ###
30:
31: =pod
32:
33: =head1 NAME
34:
35: lonstatistics
36:
37: =head1 SYNOPSIS
38:
39: Main handler for statistics and chart.
40:
41: =head1 PACKAGES USED
42:
43: use strict;
44: use Apache::Constants qw(:common :http);
45: use Apache::lonnet();
46: use Apache::lonhomework;
47: use Apache::loncommon;
48: use Apache::loncoursedata;
49: use Apache::lonhtmlcommon;
50: use Apache::lonproblemanalysis;
51: use Apache::lonproblemstatistics;
52: use Apache::lonstudentassessment;
53: use Apache::lonpercentage;
54: use GDBM_File;
55:
56: =over 4
57:
58: =cut
59:
60: package Apache::lonstatistics;
61:
62: use strict;
63: use Apache::Constants qw(:common :http);
64: use vars qw(
65: @FullClasslist
66: @Students
67: @Sections
68: @SelectedSections
69: %StudentData
70: @StudentDataOrder
71: @SelectedStudentData
72: $top_map
73: @Sequences
74: @SelectedMaps
75: @Assessments);
76:
77: use Apache::lonnet();
78: use Apache::lonhomework;
79: use Apache::loncommon;
80: use Apache::loncoursedata;
81: use Apache::lonhtmlcommon;
82: use Apache::lonproblemanalysis();
83: use Apache::lonproblemstatistics();
84: use Apache::lonstudentassessment();
85: use Apache::lonpercentage;
86: use GDBM_File;
87:
88:
89: #######################################################
90: #######################################################
91:
92: =pod
93:
94: =item Package Variables
95:
96: =item @FullClasslist The full classlist
97:
98: =item @Students The students we are concerned with for this invocation
99:
100: =item @Sections The sections available in this class
101:
102: =item $curr_student The student currently being examined
103:
104: =item $prev_student The student previous in the classlist
105:
106: =item $next_student The student next in the classlist
107:
108: =over
109:
110: =cut
111:
112: #######################################################
113: #######################################################
114: #
115: # Classlist variables
116: #
117: my $curr_student;
118: my $prev_student;
119: my $next_student;
120:
121: #######################################################
122: #######################################################
123:
124: =pod
125:
126: =item &clear_classlist_variables()
127:
128: undef the following package variables:
129:
130: =over
131:
132: =item @FullClasslist
133:
134: =item @Students
135:
136: =item @Sections
137:
138: =item @SelectedSections
139:
140: =item %StudentData
141:
142: =item @StudentDataOrder
143:
144: =item @SelectedStudentData
145:
146: =item $curr_student
147:
148: =item $prev_student
149:
150: =item $next_student
151:
152: =back
153:
154: =cut
155:
156: #######################################################
157: #######################################################
158: sub clear_classlist_variables {
159: undef(@FullClasslist);
160: undef(@Students);
161: undef(@Sections);
162: undef(@SelectedSections);
163: undef(%StudentData);
164: undef(@SelectedStudentData);
165: undef($curr_student);
166: undef($prev_student);
167: undef($next_student);
168: }
169:
170: #######################################################
171: #######################################################
172:
173: =pod
174:
175: =item &PrepareClasslist()
176:
177: Build up the classlist information. The classlist information is kept in
178: the following package variables:
179:
180: =over
181:
182: =item @FullClasslist
183:
184: =item @Students
185:
186: =item @Sections
187:
188: =item @SelectedSections
189:
190: =item %StudentData
191:
192: =item @SelectedStudentData
193:
194: =item $curr_student
195:
196: =item $prev_student
197:
198: =item $next_student
199:
200: =back
201:
202: $curr_student, $prev_student, and $next_student may not be defined, depending
203: upon the calling context.
204:
205: =cut
206:
207: #######################################################
208: #######################################################
209: sub PrepareClasslist {
210: my $r = shift;
211: my %Sections;
212: &clear_classlist_variables();
213: #
214: # Retrieve the classlist
215: my $cid = $ENV{'request.course.id'};
216: my $cdom = $ENV{'course.'.$cid.'.domain'};
217: my $cnum = $ENV{'course.'.$cid.'.num'};
218: my ($classlist,$field_names) = &Apache::loncoursedata::get_classlist($cid,
219: $cdom,$cnum);
220: if (exists($ENV{'form.Section'})) {
221: if (ref($ENV{'form.Section'})) {
222: @SelectedSections = @{$ENV{'form.Section'}};
223: } elsif ($ENV{'form.Section'} !~ /^\s*$/) {
224: @SelectedSections = ($ENV{'form.Section'});
225: }
226: }
227: @SelectedSections = ('all') if (! @SelectedSections);
228: foreach (@SelectedSections) {
229: if ($_ eq 'all') {
230: @SelectedSections = ('all');
231: }
232: }
233: #
234: # Set up %StudentData
235: @StudentDataOrder = qw/fullname username domain id section status/;
236: foreach my $field (@StudentDataOrder) {
237: $StudentData{$field}->{'title'} = $field;
238: $StudentData{$field}->{'base_width'} = length($field);
239: $StudentData{$field}->{'width'} =
240: $StudentData{$field}->{'base_width'};
241: }
242:
243: #
244: # Process the classlist
245: while (my ($student,$student_data) = each (%$classlist)) {
246: my $studenthash = ();
247: for (my $i=0; $i< scalar(@$field_names);$i++) {
248: my $field = $field_names->[$i];
249: # Store the data
250: $studenthash->{$field}=$student_data->[$i];
251: # Keep track of the width of the fields
252: next if (! exists($StudentData{$field}));
253: my $length = length($student_data->[$i]);
254: if ($StudentData{$field}->{'width'} < $length) {
255: $StudentData{$field}->{'width'} = $length;
256: }
257: }
258: push (@FullClasslist,$studenthash);
259: #
260: # Build up a list of sections
261: my $section = $studenthash->{'section'};
262: if (! defined($section) || $section =~/^\s*$/ || $section == -1) {
263: $studenthash->{'section'} = 'none';
264: $section = $studenthash->{'section'};
265: }
266: $Sections{$section}++;
267: #
268: # Only put in the list those students we are interested in
269: foreach my $sect (@SelectedSections) {
270: if (($sect eq 'all') || ($section eq $sect)) {
271: push (@Students,$studenthash);
272: last;
273: }
274: }
275: }
276: #
277: # Put the consolidated section data in the right place
278: @Sections = sort {$a cmp $b} keys(%Sections);
279: unshift(@Sections,'all'); # Put 'all' at the front of the list
280: #
281: # Sort the Students
282: my $sortby = 'fullname';
283: $sortby = $ENV{'form.sort'} if (exists($ENV{'form.sort'}));
284: my @TmpStudents = sort { $a->{$sortby} cmp $b->{$sortby} ||
285: $a->{'fullname'} cmp $b->{'fullname'} } @Students;
286: @Students = @TmpStudents;
287: #
288: # Now deal with that current student thing....
289: if (exists($ENV{'form.StudentAssessmentStudent'})) {
290: my ($current_uname,$current_dom) =
291: split(':',$ENV{'form.StudentAssessmentStudent'});
292: my $i;
293: for ($i = 0; $i<=$#Students; $i++) {
294: next if (($Students[$i]->{'username'} ne $current_uname) ||
295: ($Students[$i]->{'domain'} ne $current_dom));
296: $curr_student = $Students[$i];
297: last; # If we get here, we have our student.
298: }
299: if ($i == 0) {
300: $prev_student = 'none';
301: } else {
302: $prev_student = $Students[$i-1];
303: }
304: if ($i == $#Students) {
305: $next_student = 'none';
306: } else {
307: $next_student = $Students[$i+1];
308: }
309: }
310: #
311: if (exists($ENV{'form.StudentData'})) {
312: if (ref($ENV{'form.StudentData'}) eq 'ARRAY') {
313: @SelectedStudentData = @{$ENV{'form.StudentData'}};
314: } else {
315: @SelectedStudentData = ($ENV{'form.StudentData'});
316: }
317: } else {
318: @SelectedStudentData = ('fullname');
319: }
320: foreach (@SelectedStudentData) {
321: if ($_ eq 'all') {
322: @SelectedStudentData = ('all');
323: last;
324: }
325: }
326: #
327: return;
328: }
329:
330: #######################################################
331: #######################################################
332:
333: =pod
334:
335: =item ¤t_student()
336:
337: Returns a pointer to a hash containing data about the currently
338: selected student.
339:
340: =cut
341:
342: #######################################################
343: #######################################################
344: sub current_student {
345: if (defined($curr_student)) {
346: return $curr_student;
347: } else {
348: return 'All Students';
349: }
350: }
351:
352: #######################################################
353: #######################################################
354:
355: =pod
356:
357: =item &previous_student()
358:
359: Returns a pointer to a hash containing data about the student prior
360: in the list of students. Or something.
361:
362: =cut
363:
364: #######################################################
365: #######################################################
366: sub previous_student {
367: if (defined($prev_student)) {
368: return $prev_student;
369: } else {
370: return 'No Student Selected';
371: }
372: }
373:
374: #######################################################
375: #######################################################
376:
377: =pod
378:
379: =item &next_student()
380:
381: Returns a pointer to a hash containing data about the next student
382: to be viewed.
383:
384: =cut
385:
386: #######################################################
387: #######################################################
388: sub next_student {
389: if (defined($next_student)) {
390: return $next_student;
391: } else {
392: return 'No Student Selected';
393: }
394: }
395:
396: #######################################################
397: #######################################################
398:
399: =pod
400:
401: =item &clear_sequence_variables()
402:
403: =cut
404:
405: #######################################################
406: #######################################################
407: sub clear_sequence_variables {
408: undef($top_map);
409: undef(@Sequences);
410: undef(@Assessments);
411: }
412:
413: #######################################################
414: #######################################################
415:
416: =pod
417:
418: =item &SetSelectedMaps($elementname)
419:
420: Sets the @SelectedMaps array from $ENV{'form.'.$elementname};
421:
422: =cut
423:
424: #######################################################
425: #######################################################
426: sub SetSelectedMaps {
427: my $elementname = shift;
428: if (exists($ENV{'form.'.$elementname})) {
429: if (ref($ENV{'form.'.$elementname})) {
430: @SelectedMaps = @{$ENV{'form.'.$elementname}};
431: } else {
432: @SelectedMaps = ($ENV{'form.'.$elementname});
433: }
434: } else {
435: @SelectedMaps = ('all');
436: }
437: }
438:
439: #######################################################
440: #######################################################
441:
442: =pod
443:
444: =item &PrepareCourseData($r)
445:
446: =cut
447:
448: #######################################################
449: #######################################################
450: sub PrepareCourseData {
451: my ($r) = @_;
452: &clear_sequence_variables();
453: my ($top,$sequences,$assessments) =
454: &Apache::loncoursedata::get_sequence_assessment_data();
455: if (! defined($top) || ! ref($top)) {
456: # There has been an error, better report it
457: &Apache::lonnet::logthis('top is undefined');
458: return;
459: }
460: $top_map = $top if (ref($top));
461: @Sequences = @{$sequences} if (ref($sequences) eq 'ARRAY');
462: @Assessments = @{$assessments} if (ref($assessments) eq 'ARRAY');
463: #
464: # Compute column widths
465: foreach my $seq (@Sequences) {
466: my $name_length = length($seq->{'title'});
467: my $num_parts = $seq->{'num_assess_parts'};
468: #
469: # The number of columns needed for the summation text:
470: # " 1/5" = 1+3 columns, " 10/99" = 1+5 columns
471: my $sum_length = 1+1+2*(length($num_parts));
472: my $num_col = $num_parts+$sum_length;
473: if ($num_col < $name_length) {
474: $num_col = $name_length;
475: }
476: $seq->{'base_width'} = $name_length;
477: $seq->{'width'} = $num_col;
478: }
479: return;
480: }
481:
482: #######################################################
483: #######################################################
484:
485: =pod
486:
487: =item &log_sequence($sequence,$recursive,$padding)
488:
489: Write data about the sequence to a logfile. If $recursive is not
490: undef the data is written recursively. $padding is used for recursive
491: calls.
492:
493: =cut
494:
495: #######################################################
496: #######################################################
497: sub log_sequence {
498: my ($seq,$recursive,$padding) = @_;
499: $padding = '' if (! defined($padding));
500: if (ref($seq) ne 'HASH') {
501: &Apache::lonnet::logthis('log_sequence passed bad sequnce');
502: return;
503: }
504: &Apache::lonnet::logthis($padding.'sequence '.$seq->{'title'});
505: while (my($key,$value) = each(%$seq)) {
506: next if ($key eq 'contents');
507: if (ref($value) eq 'ARRAY') {
508: for (my $i=0;$i< scalar(@$value);$i++) {
509: &Apache::lonnet::logthis($padding.$key.'['.$i.']='.
510: $value->[$i]);
511: }
512: } else {
513: &Apache::lonnet::logthis($padding.$key.'='.$value);
514: }
515: }
516: if (defined($recursive)) {
517: &Apache::lonnet::logthis($padding.'-'x20);
518: &Apache::lonnet::logthis($padding.'contains:');
519: foreach my $item (@{$seq->{'contents'}}) {
520: if ($item->{'type'} eq 'container') {
521: &log_sequence($item,$recursive,$padding.' ');
522: } else {
523: &Apache::lonnet::logthis($padding.'title = '.$item->{'title'});
524: while (my($key,$value) = each(%$item)) {
525: next if ($key eq 'title');
526: if (ref($value) eq 'ARRAY') {
527: for (my $i=0;$i< scalar(@$value);$i++) {
528: &Apache::lonnet::logthis($padding.$key.'['.$i.']='.
529: $value->[$i]);
530: }
531: } else {
532: &Apache::lonnet::logthis($padding.$key.'='.$value);
533: }
534: }
535: }
536: }
537: &Apache::lonnet::logthis($padding.'end contents of '.$seq->{'title'});
538: &Apache::lonnet::logthis($padding.'-'x20);
539: }
540: return;
541: }
542:
543: ##############################################
544: ##############################################
545:
546: =pod
547:
548: =item &StudentDataSelect($elementname,$status,$numvisible,$selected)
549:
550: Returns html for a selection box allowing the user to choose one (or more)
551: of the fields of student data available (fullname, username, id, section, etc)
552:
553: =over 4
554:
555: =item $elementname The name of the HTML form element
556:
557: =item $status 'multiple' or 'single' selection box
558:
559: =item $numvisible The number of options to be visible
560:
561: =back
562:
563: =cut
564:
565: ##############################################
566: ##############################################
567: sub StudentDataSelect {
568: my ($elementname,$status,$numvisible)=@_;
569: if ($numvisible < 1) {
570: return;
571: }
572: #
573: # Build the form element
574: my $Str = "\n";
575: $Str .= '<select name="'.$elementname.'" ';
576: if ($status ne 'single') {
577: $Str .= 'multiple="true" ';
578: }
579: $Str .= 'size="'.$numvisible.'" >'."\n";
580: #
581: # Deal with 'all'
582: $Str .= ' <option value="all" ';
583: foreach (@SelectedStudentData) {
584: if ($_ eq 'all') {
585: $Str .= 'selected ';
586: last;
587: }
588: }
589: $Str .= ">all</option>\n";
590: #
591: # Loop through the student data fields
592: foreach my $item (@StudentDataOrder) {
593: $Str .= ' <option value="'.$item.'" ';
594: foreach (@SelectedStudentData) {
595: if ($item eq $_ ) {
596: $Str .= 'selected ';
597: last;
598: }
599: }
600: $Str .= '>'.$item."</option>\n";
601: }
602: $Str .= "</select>\n";
603: return $Str;
604: }
605:
606: ##############################################
607: ##############################################
608:
609: =pod
610:
611: =item &MapSelect($elementname,$status,$numvisible,$restriction)
612:
613: Returns html for a selection box allowing the user to choose one (or more)
614: of the sequences in the course. The values of the sequences are the symbs.
615: If the top sequence is selected, the value 'top' will result.
616:
617: =over 4
618:
619: =item $elementname The name of the HTML form element
620:
621: =item $status 'multiple' or 'single' selection box
622:
623: =item $numvisible The number of options to be visible
624:
625: =item $restriction Code reference to subroutine which returns true or
626: false. The code must expect a reference to a sequence data structure.
627:
628: =back
629:
630: =cut
631:
632: ##############################################
633: ##############################################
634: sub MapSelect {
635: my ($elementname,$status,$numvisible,$restriction)=@_;
636: if ($numvisible < 1) {
637: return;
638: }
639: #
640: # Set up array of selected items
641: &SetSelectedMaps($elementname);
642: #
643: # Set up the restriction call
644: if (! defined($restriction)) {
645: $restriction = sub { 1; };
646: }
647: #
648: # Build the form element
649: my $Str = "\n";
650: $Str .= '<select name="'.$elementname.'" ';
651: if ($status ne 'single') {
652: $Str .= 'multiple="true" ';
653: }
654: $Str .= 'size="'.$numvisible.'" >'."\n";
655: #
656: # Deal with 'all'
657: foreach (@SelectedMaps) {
658: if ($_ eq 'all') {
659: @SelectedMaps = ('all');
660: last;
661: }
662: }
663: #
664: # Put in option for 'all'
665: $Str .= ' <option value="all" ';
666: foreach (@SelectedMaps) {
667: if ($_ eq 'all') {
668: $Str .= 'selected ';
669: last;
670: }
671: }
672: $Str .= ">all</option>\n";
673: #
674: # Loop through the sequences
675: foreach my $seq (@Sequences) {
676: next if (! $restriction->($seq));
677: $Str .= ' <option value="'.$seq->{'symb'}.'" ';
678: foreach (@SelectedMaps) {
679: if ($seq->{'symb'} eq $_) {
680: $Str .= 'selected ';
681: last;
682: }
683: }
684: $Str .= '>'.$seq->{'title'}."</option>\n";
685: }
686: $Str .= "</select>\n";
687: return $Str;
688: }
689:
690: ##############################################
691: ##############################################
692:
693: =pod
694:
695: =item &SectionSelect($elementname,$status,$numvisible)
696:
697: Returns html for a selection box allowing the user to choose one (or more)
698: of the sections in the course.
699:
700: =over 4
701:
702: =item $elementname The name of the HTML form element
703:
704: =item $status 'multiple' or 'single' selection box
705:
706: =item $numvisible The number of options to be visible
707:
708: =item $selected Array ref to the names of the already selected sections.
709: If undef, $ENV{'form.'.$elementname} is used.
710: If $ENV{'form.'.$elementname} is also empty, none will be selected.
711:
712: =item $restriction Code reference to subroutine which returns true or
713: false. The code must expect a reference to a sequence data structure.
714:
715: =back
716:
717: =cut
718:
719: ##############################################
720: ##############################################
721: sub SectionSelect {
722: my ($elementname,$status,$numvisible)=@_;
723: if ($numvisible < 1) {
724: return;
725: }
726: #
727: # Build the form element
728: my $Str = "\n";
729: $Str .= '<select name="'.$elementname.'" ';
730: if ($status ne 'single') {
731: $Str .= 'multiple="true" ';
732: }
733: $Str .= 'size="'.$numvisible.'" >'."\n";
734: #
735: # Loop through the sequences
736: foreach my $s (@Sections) {
737: $Str .= ' <option value="'.$s.'" ';
738: foreach (@SelectedSections) {
739: if ($s eq $_) {
740: $Str .= 'selected ';
741: last;
742: }
743: }
744: $Str .= '>'.$s."</option>\n";
745: }
746: $Str .= "</select>\n";
747: return $Str;
748: }
749:
750: ##############################################
751: ##############################################
752:
753: sub CheckFormElement {
754: my ($cache, $ENVName, $cacheName, $default)=@_;
755:
756: if(defined($ENV{'form.'.$ENVName})) {
757: $cache->{$cacheName} = $ENV{'form.'.$ENVName};
758: } elsif(!defined($cache->{$cacheName})) {
759: $cache->{$cacheName} = $default;
760: } else {
761: $ENV{'form.'.$ENVName} = $cache->{$cacheName};
762: }
763: return;
764: }
765:
766: sub ProcessFormData{
767: my ($cache)=@_;
768:
769: $cache->{'reportKey'} = 'false';
770:
771: &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
772: ['download',
773: 'reportSelected',
774: 'StudentAssessmentStudent',
775: 'ProblemStatisticsSort']);
776: &CheckFormElement($cache, 'DownloadAll', 'DownloadAll', 'false');
777: if ($cache->{'DownloadAll'} ne 'false') {
778: # Clean the hell out of that cache!
779: # We cannot untie the hash at this scope (stupid libgd :( )
780: # So, remove every single key. What a waste of time....
781: # Of course, if you are doing this you are probably resigned
782: # to waiting a while.
783: &Apache::lonnet::logthis("Cleaning out the cache file");
784: while (my ($key,undef)=each(%$cache)) {
785: next if ($key eq 'DownloadAll');
786: delete($cache->{$key});
787: }
788: }
789: &CheckFormElement($cache, 'Status', 'Status', 'Active');
790: &CheckFormElement($cache, 'postdata', 'reportSelected', 'Class list');
791: &CheckFormElement($cache, 'reportSelected', 'reportSelected',
792: 'Class list');
793: $cache->{'reportSelected'} =
794: &Apache::lonnet::unescape($cache->{'reportSelected'});
795: &CheckFormElement($cache, 'sort', 'sort', 'fullname');
796: &CheckFormElement($cache, 'download', 'download', 'false');
797: &CheckFormElement($cache, 'StatisticsMaps',
798: 'StatisticsMaps', 'All Maps');
799: &CheckFormElement($cache, 'StatisticsProblemSelect',
800: 'StatisticsProblemSelect', 'All Problems');
801: &CheckFormElement($cache, 'StatisticsPartSelect',
802: 'StatisticsPartSelect', 'All Parts');
803: if(defined($ENV{'form.Section'})) {
804: my @sectionsSelected = (ref($ENV{'form.Section'}) ?
805: @{$ENV{'form.Section'}} :
806: ($ENV{'form.Section'}));
807: $cache->{'sectionsSelected'} = join(':', @sectionsSelected);
808: } elsif(!defined($cache->{'sectionsSelected'})) {
809: $cache->{'sectionsSelected'} = $cache->{'sectionList'};
810: }
811:
812: # student assessment
813: if(defined($ENV{'form.CreateStudentAssessment'}) ||
814: defined($ENV{'form.NextStudent'}) ||
815: defined($ENV{'form.PreviousStudent'})) {
816: $cache->{'reportSelected'} = 'Student Assessment';
817: }
818: if(defined($ENV{'form.NextStudent'})) {
819: $cache->{'StudentAssessmentMove'} = 'next';
820: } elsif(defined($ENV{'form.PreviousStudent'})) {
821: $cache->{'StudentAssessmentMove'} = 'previous';
822: } else {
823: $cache->{'StudentAssessmentMove'} = 'selected';
824: }
825: &CheckFormElement($cache, 'StudentAssessmentStudent',
826: 'StudentAssessmentStudent', 'All Students');
827: $cache->{'StudentAssessmentStudent'} =
828: &Apache::lonnet::unescape($cache->{'StudentAssessmentStudent'});
829: &CheckFormElement($cache, 'DefaultColumns', 'DefaultColumns', 'false');
830:
831: # Problem analysis
832: &CheckFormElement($cache, 'Interval', 'Interval', '1');
833:
834: # ProblemStatistcs
835: &CheckFormElement($cache, 'DisplayCSVFormat',
836: 'DisplayFormat', 'Display Table Format');
837: &CheckFormElement($cache, 'ProblemStatisticsAscend',
838: 'ProblemStatisticsAscend', 'Ascending');
839: &CheckFormElement($cache, 'ProblemStatisticsSort',
840: 'ProblemStatisticsSort', 'Homework Sets Order');
841: &CheckFormElement($cache, 'DisplayLegend', 'DisplayLegend',
842: 'Hide Legend');
843: &CheckFormElement($cache, 'SortProblems', 'SortProblems',
844: 'Sort Within Sequence');
845:
846: # Search only form elements
847: my @headingColumns=();
848: my @sequenceColumns=();
849: my $foundColumn = 0;
850: if(defined($ENV{'form.ReselectColumns'})) {
851: my @reselected = (ref($ENV{'form.ReselectColumns'}) ?
852: @{$ENV{'form.ReselectColumns'}}
853: : ($ENV{'form.ReselectColumns'}));
854: foreach (@reselected) {
855: if(/HeadingColumn/) {
856: push(@headingColumns, $_);
857: $foundColumn = 1;
858: } elsif(/SequenceColumn/) {
859: push(@sequenceColumns, $_);
860: $foundColumn = 1;
861: }
862: }
863: }
864:
865: $cache->{'reportKey'} = 'false';
866: if($cache->{'reportSelected'} eq 'Analyze') {
867: $cache->{'reportKey'} = 'Analyze';
868: } elsif($cache->{'reportSelected'} eq 'DoDiffGraph') {
869: $cache->{'reportKey'} = 'DoDiffGraph';
870: } elsif($cache->{'reportSelected'} eq 'PercentWrongGraph') {
871: $cache->{'reportKey'} = 'PercentWrongGraph';
872: }
873:
874: if(defined($ENV{'form.DoDiffGraph'})) {
875: $cache->{'reportSelected'} = 'DoDiffGraph';
876: $cache->{'reportKey'} = 'DoDiffGraph';
877: } elsif(defined($ENV{'form.PercentWrongGraph'})) {
878: $cache->{'reportSelected'} = 'PercentWrongGraph';
879: $cache->{'reportKey'} = 'PercentWrongGraph';
880: }
881:
882: foreach (keys(%ENV)) {
883: if(/form\.Analyze/) {
884: $cache->{'reportSelected'} = 'Analyze';
885: $cache->{'reportKey'} = 'Analyze';
886: my $data;
887: (undef, $data)=split(':::', $_);
888: $cache->{'AnalyzeInfo'}=$data;
889: } elsif(/form\.HeadingColumn/) {
890: my $value = $_;
891: $value =~ s/form\.//;
892: push(@headingColumns, $value);
893: $foundColumn=1;
894: } elsif(/form\.SequenceColumn/) {
895: my $value = $_;
896: $value =~ s/form\.//;
897: push(@sequenceColumns, $value);
898: $foundColumn=1;
899: }
900: }
901:
902: if($foundColumn) {
903: $cache->{'HeadingsFound'} = join(':', @headingColumns);
904: $cache->{'SequencesFound'} = join(':', @sequenceColumns);;
905: }
906: if(!defined($cache->{'HeadingsFound'}) ||
907: $cache->{'DefaultColumns'} ne 'false') {
908: $cache->{'HeadingsFound'}='HeadingColumnFull Name';
909: }
910: if(!defined($cache->{'SequencesFound'}) ||
911: $cache->{'DefaultColumns'} ne 'false') {
912: $cache->{'SequencesFound'}='All Sequences';
913: }
914: $cache->{'DefaultColumns'} = 'false';
915:
916: return;
917: }
918:
919: ##################################################
920: ##################################################
921:
922: =pod
923:
924: =item &SortStudents()
925:
926: Determines which students to display and in which order. Which are
927: displayed are determined by their status(active/expired). The order
928: is determined by the sort button pressed (default to username). The
929: type of sorting is username, lastname, or section.
930:
931: =over 4
932:
933: Input: $students, $CacheData
934:
935: $students: A array pointer to a list of students (username:domain)
936:
937: $CacheData: A pointer to the hash tied to the cached data
938:
939: Output: \@order
940:
941: @order: An ordered list of students (username:domain)
942:
943: =back
944:
945: =cut
946:
947: sub SortStudents {
948: my ($cache)=@_;
949:
950: my @students = split(':::',$cache->{'NamesOfStudents'});
951: my @sorted1Students=();
952: foreach (@students) {
953: if($cache->{'Status'} eq 'Any' ||
954: $cache->{$_.':Status'} eq $cache->{'Status'}) {
955: push(@sorted1Students, $_);
956: }
957: }
958:
959: my $sortBy = '';
960: if(defined($cache->{'sort'})) {
961: $sortBy = ':'.$cache->{'sort'};
962: } else {
963: $sortBy = ':fullname';
964: }
965: my @order = sort { lc($cache->{$a.$sortBy}) cmp lc($cache->{$b.$sortBy}) ||
966: lc($cache->{$a.':fullname'}) cmp lc($cache->{$b.':fullname'}) }
967: @sorted1Students;
968:
969: return \@order;
970: }
971:
972: =pod
973:
974: =item &SpaceColumns()
975:
976: Determines the width of all the columns in the chart. It is based on
977: the max of the data for that column and its header.
978:
979: =over 4
980:
981: Input: $students, $studentInformation, $headings, $ChartDB
982:
983: $students: An array pointer to a list of students (username:domain)
984:
985: $studentInformatin: The type of data for the student information. It is
986: used as part of the key in $CacheData.
987:
988: $headings: The name of the student information columns.
989:
990: $ChartDB: The name of the cache database which is opened for read/write.
991:
992: Output: None - All data stored in cache.
993:
994: =back
995:
996: =cut
997:
998: sub SpaceColumns {
999: my ($students,$studentInformation,$headings,$cache)=@_;
1000:
1001: # Initialize Lengths
1002: for(my $index=0; $index<(scalar @$headings); $index++) {
1003: my @titleLength=split(//,$headings->[$index]);
1004: $cache->{$studentInformation->[$index].':columnWidth'}=
1005: scalar @titleLength;
1006: }
1007:
1008: foreach my $name (@$students) {
1009: foreach (@$studentInformation) {
1010: my @dataLength=split(//,$cache->{$name.':'.$_});
1011: my $length=(scalar @dataLength);
1012: if($length > $cache->{$_.':columnWidth'}) {
1013: $cache->{$_.':columnWidth'}=$length;
1014: }
1015: }
1016: }
1017:
1018: return;
1019: }
1020:
1021: sub PrepareData {
1022: my ($c, $cacheDB, $studentInformation, $headings,$r)=@_;
1023:
1024: # Test for access to the cache data
1025: my $courseID=$ENV{'request.course.id'};
1026: my $isRecalculate=0;
1027: if(defined($ENV{'form.Recalculate'})) {
1028: $isRecalculate=1;
1029: }
1030:
1031: my $isCached = &Apache::loncoursedata::TestCacheData($cacheDB,
1032: $isRecalculate);
1033: if($isCached < 0) {
1034: return "Unable to tie hash to db file.";
1035: }
1036:
1037: # Download class list information if not using cached data
1038: my %cache;
1039: unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_WRCREAT(),0640)) {
1040: return "Unable to tie hash to db file.";
1041: }
1042:
1043: # if(!$isCached) {
1044: my $processTopResourceMapReturn=
1045: &Apache::loncoursedata::ProcessTopResourceMap(\%cache, $c);
1046: if($processTopResourceMapReturn ne 'OK') {
1047: untie(%cache);
1048: return $processTopResourceMapReturn;
1049: }
1050: # }
1051:
1052: if($c->aborted()) {
1053: untie(%cache);
1054: return 'aborted';
1055: }
1056:
1057: my $classlist=&Apache::loncoursedata::DownloadClasslist($courseID,
1058: $cache{'ClasslistTimestamp'},
1059: $c);
1060: foreach (keys(%$classlist)) {
1061: if(/^(con_lost|error|no_such_host)/i) {
1062: untie(%cache);
1063: return "Error getting student data.";
1064: }
1065: }
1066:
1067: if($c->aborted()) {
1068: untie(%cache);
1069: return 'aborted';
1070: }
1071:
1072: # Active is a temporary solution, remember to change
1073: Apache::loncoursedata::ProcessClasslist(\%cache,$classlist,$courseID,$c);
1074: if($c->aborted()) {
1075: untie(%cache);
1076: return 'aborted';
1077: }
1078:
1079: &ProcessFormData(\%cache);
1080: my $students = &SortStudents(\%cache);
1081: &SpaceColumns($students, $studentInformation, $headings, \%cache);
1082: $cache{'updateTime:columnWidth'}=24;
1083:
1084: my $download = $cache{'download'};
1085: my $downloadAll = $cache{'DownloadAll'};
1086: my @allStudents=();
1087: if($download ne 'false') {
1088: $cache{'download'} = 'false';
1089: } elsif($downloadAll ne 'false') {
1090: $cache{'DownloadAll'} = 'false';
1091: if($downloadAll eq 'sorted') {
1092: @allStudents = @$students;
1093: } else {
1094: @allStudents = split(':::', $cache{'NamesOfStudents'});
1095: }
1096: }
1097:
1098: untie(%cache);
1099:
1100: if($download ne 'false') {
1101: my @who = ($download);
1102: if(&Apache::loncoursedata::DownloadStudentCourseData(\@who, 'false',
1103: $cacheDB, 'true',
1104: 'false', $courseID,
1105: $r, $c) ne 'OK') {
1106: return 'Stop at download individual';
1107: }
1108: } elsif($downloadAll ne 'false') {
1109: if(&Apache::loncoursedata::DownloadStudentCourseData(\@allStudents,
1110: 'false',
1111: $cacheDB, 'true',
1112: 'true', $courseID,
1113: $r, $c) ne 'OK') {
1114: return 'Stop at download all';
1115: }
1116: }
1117:
1118: return ('OK', $students);
1119: }
1120:
1121: sub DisplayClasslist {
1122: my ($r)=@_;
1123: #
1124: my @Fields = ('fullname','username','domain','id','section');
1125: #
1126: my $Str='';
1127: $Str .= '<table border="0"><tr><td bgcolor="#777777">'."\n";
1128: $Str .= '<table border="0" cellpadding="3"><tr bgcolor="#e6ffff">'."\n";
1129: foreach my $field (@Fields) {
1130: $Str .= '<th><a href="/adm/statistics?sort='.$field.'">'.$field.
1131: '</a></th>';
1132: }
1133: $Str .= '</tr>'."\n";
1134: #
1135: my $alternate = 0;
1136: foreach my $student (@Students) {
1137: my $sname = $student->{'username'}.':'.$student->{'domain'};
1138: if($alternate) {
1139: $Str .= '<tr bgcolor="#ffffe6">';
1140: } else {
1141: $Str .= '<tr bgcolor="#ffffc6">';
1142: }
1143: $alternate = ($alternate + 1) % 2;
1144: #
1145: foreach my $field (@Fields) {
1146: $Str .= '<td>';
1147: if ($field eq 'fullname') {
1148: $Str .= '<a href="/adm/statistics?reportSelected=';
1149: $Str .= &Apache::lonnet::escape('Student Assessment');
1150: $Str .= '&StudentAssessmentStudent=';
1151: $Str .= &Apache::lonnet::escape($sname).'">';
1152: $Str .= $student->{$field}.' ';
1153: $Str .= '</a>';
1154: } else {
1155: $Str .= $student->{$field};
1156: }
1157: $Str .= '</td>';
1158: }
1159: $Str .= "</tr>\n";
1160: }
1161: $Str .= '</table></td></tr></table>'."\n";
1162: #
1163: $r->print($Str);
1164: $r->rflush();
1165: #
1166: return;
1167: }
1168:
1169: sub BuildClasslist {
1170: my ($cacheDB,$students,$studentInformation,$headings,$r)=@_;
1171:
1172: my %cache;
1173: unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
1174: return '<html><body>Unable to tie database.</body></html>';
1175: }
1176:
1177: # my $Ptr = '';
1178: # $Ptr .= '<table border="0"><tbody>';
1179: # $Ptr .= '<tr><td align="right"><b>Select Sections</b>';
1180: # $Ptr .= '</td>'."\n";
1181: # $Ptr .= '<td align="left">'."\n";
1182: # my @sectionsSelected = split(':',$cache{'sectionsSelected'});
1183: # my @sections = split(':',$cache{'sectionList'});
1184: # $Ptr .= &Apache::lonhtmlcommon::MultipleSectionSelect(\@sections,
1185: # \@sectionsSelected,
1186: # 'Statistics');
1187: # $Ptr .= '</td></tr></table><br>';
1188: # $r->print($Ptr);
1189: # $r->rflush();
1190: # my %mySections = ();
1191: # foreach (@sections) { $mySections{$_} = 'True'; }
1192: # $r->print("<br>$cache{'sectionsSelected'}<br>");
1193:
1194: my $Str='';
1195: $Str .= '<table border="0"><tr><td bgcolor="#777777">'."\n";
1196: $Str .= '<table border="0" cellpadding="3"><tr bgcolor="#e6ffff">'."\n";
1197:
1198: my $displayString = '<td align="left"><a href="/adm/statistics?';
1199: $displayString .= 'sort=LINKDATA">DISPLAYDATA </a></td>'."\n";
1200: $Str .= &Apache::lonhtmlcommon::CreateHeadings(\%cache,
1201: $studentInformation,
1202: $headings, $displayString);
1203: $Str .= '</tr>'."\n";
1204:
1205: my $alternate=0;
1206: foreach (@$students) {
1207: # if ($mySections{$cache{$_.':'.'section'}} ne 'True') {next;}
1208: my ($username, $domain) = split(':', $_);
1209: if($alternate) {
1210: $Str .= '<tr bgcolor="#ffffe6">';
1211: } else {
1212: $Str .= '<tr bgcolor="#ffffc6">';
1213: }
1214: $alternate = ($alternate + 1) % 2;
1215: foreach my $data (@$studentInformation) {
1216: $Str .= '<td>';
1217: if($data eq 'fullname') {
1218: $Str .= '<a href="/adm/statistics?reportSelected=';
1219: $Str .= &Apache::lonnet::escape('Student Assessment');
1220: $Str .= '&StudentAssessmentStudent=';
1221: $Str .= &Apache::lonnet::escape($cache{$_.':'.$data}).'">';
1222: $Str .= $cache{$_.':'.$data}.' ';
1223: $Str .= '</a>';
1224: } elsif($data eq 'updateTime') {
1225: $Str .= '<a href="/adm/statistics?reportSelected=';
1226: $Str .= &Apache::lonnet::escape('Class list');
1227: $Str .= '&download='.$_.'">';
1228: $Str .= $cache{$_.':'.$data}.' ';
1229: $Str .= ' </a>';
1230: } else {
1231: $Str .= $cache{$_.':'.$data}.' ';
1232: }
1233:
1234: $Str .= '</td>'."\n";
1235: }
1236: }
1237:
1238: $Str .= '</tr>'."\n";
1239: $Str .= '</table></td></tr></table>'."\n";
1240: $r->print($Str);
1241: $r->rflush();
1242:
1243: untie(%cache);
1244:
1245: return;
1246: }
1247:
1248: sub CreateMainMenu {
1249: my ($status, $reports)=@_;
1250:
1251: my $Str = '';
1252:
1253: $Str .= '<table border="0"><tbody><tr>'."\n";
1254: $Str .= '<td></td>'."\n";
1255: $Str .= '<td align="center"><b>Select a Report</b></td>'."\n";
1256: $Str .= '<td align="center"><b>Student Status</b></td></tr>'."\n";
1257: $Str .= '<tr>'."\n";
1258: $Str .= '<td align="center"><input type="submit" name="Refresh" ';
1259: $Str .= 'value="Update Display" /></td>'."\n";
1260: $Str .= '<td align="center">';
1261: $Str .= '<select name="reportSelected" onchange="document.';
1262: $Str .= 'Statistics.submit()">'."\n";
1263:
1264: foreach (sort(keys(%$reports))) {
1265: next if($_ eq 'reportSelected');
1266: $Str .= '<option name="'.$_.'"';
1267: if($reports->{'reportSelected'} eq $reports->{$_}) {
1268: $Str .= ' selected=""';
1269: }
1270: $Str .= '>'.$reports->{$_}.'</option>'."\n";
1271: }
1272: $Str .= '</select></td>'."\n";
1273:
1274: $Str .= '<td align="center">';
1275: $Str .= &Apache::lonhtmlcommon::StatusOptions($status, 'Statistics');
1276: $Str .= '</td>'."\n";
1277:
1278: $Str .= '</tr></tbody></table>'."\n";
1279: $Str .= '<hr>'."\n";
1280:
1281: return $Str;
1282: }
1283:
1284: sub BuildStatistics {
1285: my ($r)=@_;
1286:
1287: my $c = $r->connection;
1288: my @studentInformation=('fullname','section','id','domain','username',
1289: 'updateTime');
1290: my @headings=('Full Name', 'Section', 'PID', 'Domain', 'User Name',
1291: 'Last Updated');
1292: my $spacing = ' ';
1293:
1294: my %reports = ('classlist' => 'Class list',
1295: 'problem_statistics' => 'Problem Statistics',
1296: 'student_assessment' => 'Student Assessment',
1297: 'percentage' => 'Correct-problems Plot',
1298: # 'activitylog' => 'Activity Log',
1299: 'reportSelected' => 'Class list');
1300:
1301: my %cache;
1302: my $courseID=$ENV{'request.course.id'};
1303: my $cacheDB = "/home/httpd/perl/tmp/$ENV{'user.name'}".
1304: "_$ENV{'user.domain'}_$courseID\_statistics.db";
1305:
1306: $r->print(&Apache::lonhtmlcommon::Title('Course Statistics and Charts'));
1307:
1308: my ($returnValue, $students) = &PrepareData($c, $cacheDB,
1309: \@studentInformation,
1310: \@headings,$r);
1311: if($returnValue ne 'OK') {
1312: $r->print($returnValue."\n".'</body></html>');
1313: return OK;
1314: }
1315: if(!$c->aborted()) {
1316: &Apache::loncoursedata::CheckForResidualDownload($cacheDB,
1317: 'true', 'true',
1318: $courseID,
1319: $r, $c);
1320: }
1321:
1322: my $GoToPage;
1323: if(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
1324: $GoToPage = $cache{'reportSelected'};
1325: $reports{'reportSelected'} = $cache{'reportSelected'};
1326: if(defined($cache{'reportKey'}) &&
1327: !exists($reports{$cache{'reportKey'}}) &&
1328: $cache{'reportKey'} ne 'false') {
1329: $reports{$cache{'reportKey'}} = $cache{'reportSelected'};
1330: }
1331:
1332: if(defined($cache{'OptionResponses'})) {
1333: $reports{'problem_analysis'} = 'Option Response Analysis';
1334: }
1335:
1336: $r->print('<form name="Statistics" ');
1337: $r->print('method="post" action="/adm/statistics">');
1338: $r->print(&CreateMainMenu($cache{'Status'}, \%reports));
1339: $r->rflush();
1340: untie(%cache);
1341: } else {
1342: $r->print('<html><body>Unable to tie database.</body></html>');
1343: return OK;
1344: }
1345:
1346: if($GoToPage eq 'Activity Log') {
1347: &Apache::lonproblemstatistics::Activity();
1348: } elsif($GoToPage eq 'Problem Statistics') {
1349: &Apache::lonproblemstatistics::BuildProblemStatisticsPage($cacheDB,
1350: $students,
1351: $courseID,
1352: $c,$r);
1353: } elsif($GoToPage eq 'Option Response Analysis') {
1354: &Apache::lonproblemanalysis::BuildProblemAnalysisPage($cacheDB, $r);
1355: } elsif($GoToPage eq 'Student Assessment') {
1356: &Apache::lonstudentassessment::BuildStudentAssessmentPage($r, $c);
1357: } elsif($GoToPage eq 'Analyze') {
1358: &Apache::lonproblemanalysis::BuildAnalyzePage($cacheDB, $students,
1359: $courseID, $r);
1360: } elsif($GoToPage eq 'DoDiffGraph' || $GoToPage eq 'PercentWrongGraph') {
1361: my $courseDescription = $ENV{'course.'.$courseID.'.description'};
1362: $courseDescription =~ s/\ /"_"/eg;
1363: &Apache::lonproblemstatistics::BuildGraphicChart($GoToPage, $cacheDB,
1364: $courseDescription,
1365: $students, $courseID,
1366: $r, $c);
1367: } elsif($GoToPage eq 'Class list') {
1368: &DisplayClasslist($r);
1369: # &BuildClasslist($cacheDB, $students, \@studentInformation,
1370: # \@headings, $r);
1371: } elsif($GoToPage eq 'Correct-problems Plot') {
1372: &Apache::lonpercentage::BuildPercentageGraph($cacheDB, $students,
1373: $courseID, $c, $r);
1374: }
1375:
1376: $r->print('</form>'."\n");
1377: $r->print("\n".'</body>'."\n".'</html>');
1378: $r->rflush();
1379:
1380: return OK;
1381: }
1382:
1383: # ================================================================ Main Handler
1384:
1385: sub handler {
1386: my $r=shift;
1387:
1388: # $jr = $r;
1389:
1390: my $loaderror=&Apache::lonnet::overloaderror($r);
1391: if ($loaderror) { return $loaderror; }
1392: $loaderror=
1393: &Apache::lonnet::overloaderror($r,
1394: $ENV{'course.'.$ENV{'request.course.id'}.'.home'});
1395: if ($loaderror) { return $loaderror; }
1396:
1397: unless(&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'})) {
1398: $ENV{'user.error.msg'}=
1399: $r->uri.":vgr:0:0:Cannot view grades for complete course";
1400: return HTTP_NOT_ACCEPTABLE;
1401: }
1402:
1403: # Set document type for header only
1404: if($r->header_only) {
1405: if ($ENV{'browser.mathml'}) {
1406: $r->content_type('text/xml');
1407: } else {
1408: $r->content_type('text/html');
1409: }
1410: &Apache::loncommon::no_cache($r);
1411: $r->send_http_header;
1412: return OK;
1413: }
1414:
1415: unless($ENV{'request.course.fn'}) {
1416: my $requrl=$r->uri;
1417: $ENV{'user.error.msg'}="$requrl:bre:0:0:Course not initialized";
1418: return HTTP_NOT_ACCEPTABLE;
1419: }
1420:
1421: $r->content_type('text/html');
1422: $r->send_http_header;
1423:
1424: &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
1425: ['sort',
1426: 'StudentAssessmentStudent']);
1427:
1428: &PrepareClasslist($r);
1429:
1430: &PrepareCourseData($r);
1431:
1432: &BuildStatistics($r);
1433:
1434: return OK;
1435: }
1436: 1;
1437:
1438: =pod
1439:
1440: =back
1441:
1442: =cut
1443:
1444: __END__
1445:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>