Annotation of loncom/interface/lonstatistics.pm, revision 1.148.2.7
1.1 albertel 1: # The LearningOnline Network with CAPA
2: #
1.148.2.7! raeburn 3: # $Id: lonstatistics.pm,v 1.148.2.6 2010/11/09 15:01:53 raeburn Exp $
1.1 albertel 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
1.14 minaeibi 28: #
1.1 albertel 29: ###
30:
1.59 matthew 31:
32:
1.55 minaeibi 33: package Apache::lonstatistics;
1.1 albertel 34:
1.30 stredwic 35: use strict;
1.1 albertel 36: use Apache::Constants qw(:common :http);
1.61 matthew 37: use vars qw(
38: @FullClasslist
39: @Students
1.130 raeburn 40: @Sections
41: @Groups
1.61 matthew 42: %StudentData
43: @StudentDataOrder
44: @SelectedStudentData
1.94 matthew 45: $enrollment_status);
1.61 matthew 46:
1.123 albertel 47: use Apache::lonnet;
1.1 albertel 48: use Apache::lonhomework;
1.12 minaeibi 49: use Apache::loncommon;
1.29 stredwic 50: use Apache::loncoursedata;
51: use Apache::lonhtmlcommon;
1.97 matthew 52: use Apache::lonmysql;
53: use Apache::lonlocal;
1.135 raeburn 54: use Apache::longroup;
1.97 matthew 55: use Time::HiRes;
56: #
57: # Statistics Packages
1.61 matthew 58: use Apache::lonproblemanalysis();
1.89 matthew 59: use Apache::lonsubmissiontimeanalysis();
1.94 matthew 60: use Apache::loncorrectproblemplot();
1.61 matthew 61: use Apache::lonproblemstatistics();
62: use Apache::lonstudentassessment();
1.49 stredwic 63: use Apache::lonpercentage;
1.97 matthew 64: use Apache::lonstudentsubmissions();
1.104 matthew 65: use Apache::lonsurveyreports();
1.128 albertel 66: use Apache::longradinganalysis();
1.136 www 67: use LONCAPA;
1.60 matthew 68:
69: #
70: # Classlist variables
71: #
1.59 matthew 72: my $curr_student;
73: my $prev_student;
74: my $next_student;
75:
1.61 matthew 76:
1.59 matthew 77: sub clear_classlist_variables {
78: undef(@FullClasslist);
79: undef(@Students);
80: undef(@Sections);
1.130 raeburn 81: undef(@Groups);
1.61 matthew 82: undef(%StudentData);
83: undef(@SelectedStudentData);
1.59 matthew 84: undef($curr_student);
85: undef($prev_student);
86: undef($next_student);
87: }
88:
1.130 raeburn 89:
1.59 matthew 90: sub PrepareClasslist {
91: my %Sections;
92: &clear_classlist_variables();
93: #
94: # Retrieve the classlist
1.123 albertel 95: my $cid = $env{'request.course.id'};
96: my $cdom = $env{'course.'.$cid.'.domain'};
97: my $cnum = $env{'course.'.$cid.'.num'};
1.125 albertel 98: my ($classlist,$field_names) = &Apache::loncoursedata::get_classlist($cdom,
99: $cnum);
1.119 matthew 100: my @selected_sections = &get_selected_sections();
1.130 raeburn 101: my @selected_groups = &get_selected_groups();
1.61 matthew 102: #
1.69 matthew 103: # Deal with instructors with restricted section access
1.123 albertel 104: if ($env{'request.course.sec'} !~ /^\s*$/) {
105: @selected_sections = ($env{'request.course.sec'});
1.69 matthew 106: }
107: #
1.61 matthew 108: # Set up %StudentData
1.130 raeburn 109: @StudentDataOrder = qw/fullname username domain id section status groups comments/;
1.61 matthew 110: foreach my $field (@StudentDataOrder) {
1.108 matthew 111: $StudentData{$field}->{'title'} = &mt($field);
112: $StudentData{$field}->{'base_width'} = length(&mt($field));
1.61 matthew 113: $StudentData{$field}->{'width'} =
114: $StudentData{$field}->{'base_width'};
115: }
1.59 matthew 116: #
1.68 matthew 117: # get the status requested
1.94 matthew 118: $enrollment_status = 'Active';
1.123 albertel 119: $enrollment_status = $env{'form.Status'} if (exists($env{'form.Status'}));
1.68 matthew 120: #
1.130 raeburn 121: # Get groupmembership
1.133 albertel 122: my ($classgroups,$studentgroups);
1.135 raeburn 123: my %curr_groups = &Apache::longroup::coursegroups($cdom,$cnum);
1.133 albertel 124: if (%curr_groups) {
1.130 raeburn 125: ($classgroups,$studentgroups) =
1.133 albertel 126: &Apache::loncoursedata::get_group_memberships($classlist,
1.134 raeburn 127: $field_names,
1.133 albertel 128: $cdom,$cnum);
1.130 raeburn 129: }
130: my $now = time;
131:
1.59 matthew 132: # Process the classlist
133: while (my ($student,$student_data) = each (%$classlist)) {
134: my $studenthash = ();
135: for (my $i=0; $i< scalar(@$field_names);$i++) {
1.61 matthew 136: my $field = $field_names->[$i];
137: # Store the data
138: $studenthash->{$field}=$student_data->[$i];
139: # Keep track of the width of the fields
140: next if (! exists($StudentData{$field}));
1.63 matthew 141: my $length = length($student_data->[$i]);
1.61 matthew 142: if ($StudentData{$field}->{'width'} < $length) {
143: $StudentData{$field}->{'width'} = $length;
144: }
1.59 matthew 145: }
1.130 raeburn 146: my @studentsgroups = &Apache::loncoursedata::get_students_groups
147: ($student,$enrollment_status,
148: $classgroups);
149: if (@studentsgroups) {
150: $studenthash->{'groups'} = join(', ',@studentsgroups);
151: $studenthash->{'groupref'} = \@studentsgroups;
152: } else {
153: $studenthash->{'groups'} = 'none';
154: $studenthash->{'groupref'} = [];
155: }
1.59 matthew 156: push (@FullClasslist,$studenthash);
157: #
158: # Build up a list of sections
159: my $section = $studenthash->{'section'};
1.60 matthew 160: if (! defined($section) || $section =~/^\s*$/ || $section == -1) {
161: $studenthash->{'section'} = 'none';
162: $section = $studenthash->{'section'};
163: }
1.59 matthew 164: $Sections{$section}++;
165: #
166: # Only put in the list those students we are interested in
1.119 matthew 167: foreach my $sect (@selected_sections) {
1.68 matthew 168: if ( (($sect eq 'all') ||
169: ($section eq $sect)) &&
1.94 matthew 170: (($studenthash->{'status'} eq $enrollment_status) ||
171: ($enrollment_status eq 'Any'))
1.68 matthew 172: ){
1.130 raeburn 173: my $groupcheck = 0;
1.131 albertel 174: if (grep(/^all$/,@selected_groups)) {
175: push(@Students,$studenthash);
1.130 raeburn 176: last;
1.131 albertel 177: } elsif (grep(/^none$/,@selected_groups)) {
1.130 raeburn 178: if ($studenthash->{'groups'} eq 'none') {
1.131 albertel 179: push(@Students,$studenthash);
1.130 raeburn 180: last;
181: }
182: } else {
183: foreach my $group (@selected_groups) {
1.131 albertel 184: if (grep(/^$group$/,@studentsgroups)) {
185: push(@Students,$studenthash);
1.130 raeburn 186: $groupcheck = 1;
187: last;
188: }
189: }
190: if ($groupcheck) {
191: last;
192: }
193: }
1.60 matthew 194: }
1.59 matthew 195: }
196: }
197: #
198: # Put the consolidated section data in the right place
1.123 albertel 199: if ($env{'request.course.sec'} !~ /^\s*$/) {
200: @Sections = ($env{'request.course.sec'});
1.69 matthew 201: } else {
1.138 albertel 202: @Sections = sort {
203: if ($a == $a && $b == $b ) { return $a <=> $b; }
204: return $a cmp $b;
205: } keys(%Sections);
206:
1.69 matthew 207: unshift(@Sections,'all'); # Put 'all' at the front of the list
208: }
1.130 raeburn 209: # Sort the groups
210: @Groups = sort {$a cmp $b} keys(%{$studentgroups});
211: unshift(@Groups,'all'); # Put 'all' at the front of the list
212:
1.59 matthew 213: #
214: # Sort the Students
215: my $sortby = 'fullname';
1.123 albertel 216: $sortby = $env{'form.sort'} if (exists($env{'form.sort'}));
1.127 albertel 217: my @TmpStudents = sort { lc($a->{$sortby}) cmp lc($b->{$sortby}) ||
1.126 albertel 218: lc($a->{'fullname'}) cmp lc($b->{'fullname'}) ||
219: lc($a->{'username'}) cmp lc($b->{'username'}) } @Students;
1.60 matthew 220: @Students = @TmpStudents;
1.59 matthew 221: #
222: # Now deal with that current student thing....
1.72 matthew 223: $curr_student = undef;
1.123 albertel 224: if (exists($env{'form.SelectedStudent'})) {
1.59 matthew 225: my ($current_uname,$current_dom) =
1.123 albertel 226: split(':',$env{'form.SelectedStudent'});
1.59 matthew 227: my $i;
228: for ($i = 0; $i<=$#Students; $i++) {
229: next if (($Students[$i]->{'username'} ne $current_uname) ||
230: ($Students[$i]->{'domain'} ne $current_dom));
1.60 matthew 231: $curr_student = $Students[$i];
1.59 matthew 232: last; # If we get here, we have our student.
233: }
1.72 matthew 234: if (defined($curr_student)) {
235: if ($i == 0) {
236: $prev_student = undef;
237: } else {
238: $prev_student = $Students[$i-1];
239: }
240: if ($i == $#Students) {
241: $next_student = undef;
242: } else {
243: $next_student = $Students[$i+1];
244: }
1.59 matthew 245: }
246: }
1.61 matthew 247: #
1.123 albertel 248: if (exists($env{'form.StudentData'})) {
1.124 albertel 249: @SelectedStudentData =
250: &Apache::loncommon::get_env_multiple('form.StudentData');
1.61 matthew 251: } else {
1.72 matthew 252: @SelectedStudentData = ('username');
1.61 matthew 253: }
254: foreach (@SelectedStudentData) {
255: if ($_ eq 'all') {
256: @SelectedStudentData = ('all');
257: last;
258: }
259: }
260: #
261: return;
262: }
263:
1.119 matthew 264:
265: sub get_selected_sections {
1.124 albertel 266: my @selected_sections =
267: &Apache::loncommon::get_env_multiple('form.Section');
1.119 matthew 268: @selected_sections = ('all') if (! @selected_sections);
269: foreach (@selected_sections) {
270: if ($_ eq 'all') {
271: @selected_sections = ('all');
272: }
273: }
274: #
275: # Deal with instructors with restricted section access
1.123 albertel 276: if ($env{'request.course.sec'} !~ /^\s*$/) {
277: @selected_sections = ($env{'request.course.sec'});
1.119 matthew 278: }
279: return @selected_sections;
280: }
281:
1.144 jms 282:
1.130 raeburn 283: sub get_selected_groups {
284: my @selected_groups =
285: &Apache::loncommon::get_env_multiple('form.Group');
286: @selected_groups = ('all') if (! @selected_groups);
287: foreach my $grp (@selected_groups) {
288: if ($grp eq 'all') {
289: @selected_groups = ('all');
290: last;
291: }
292: }
293: return @selected_groups;
294: }
295:
1.122 matthew 296:
1.119 matthew 297:
298: sub section_and_enrollment_description {
1.122 matthew 299: my ($mode) = @_;
300: if (! defined($mode)) { $mode = 'localized'; }
1.119 matthew 301: my @sections = &Apache::lonstatistics::get_selected_sections();
1.130 raeburn 302: my @groups = &Apache::lonstatistics::get_selected_groups();
1.122 matthew 303: my $description;
304: if ($mode eq 'localized') {
1.137 raeburn 305: $description = &mt('Unable to determine section, groups and access status');
1.122 matthew 306: } elsif ($mode eq 'plaintext') {
1.137 raeburn 307: $description = 'Unable to determine section, groups and access status';
1.122 matthew 308: } else {
309: $description = 'Bad parameter passed to lonstatistics::section_and_enrollment_description';
310: &Apache::lonnet::logthis($description);
311: }
1.130 raeburn 312: $description = §ion_or_group_text($mode,'section',@sections).
1.131 albertel 313: ' '.§ion_or_group_text($mode,'group',@groups);
1.130 raeburn 314: if ($mode eq 'localized') {
1.142 bisitz 315: $description .= ' '.&mt($env{'form.Status'}.' access status.');
1.130 raeburn 316: } elsif ($mode eq 'plaintext') {
1.137 raeburn 317: $description .= ' '.$env{'form.Status'}.' access status.';
1.130 raeburn 318: }
319: return $description;
320: }
321:
1.144 jms 322:
323:
1.130 raeburn 324:
325: sub section_or_group_text {
326: my ($mode,$type,@items) = @_;
327: my $text;
328: my %phrases = ();
329: %{$phrases{'section'}} = (
330: single => 'Section',
331: all => 'All sections',
332: plural => 'Sections',
333: );
334: %{$phrases{'group'}} = (
335: single => 'Group',
336: all => 'All groups',
337: plural => 'Groups',
338: );
339: if (scalar(@items) == 1 && $items[0] ne 'all') {
1.122 matthew 340: if ($mode eq 'localized') {
1.142 bisitz 341: $text = &mt($phrases{$type}{single}.' [_1].',$items[0]);
1.122 matthew 342: } elsif ($mode eq 'plaintext') {
1.130 raeburn 343: $text = $phrases{$type}{single}.' '.$items[0].'.';
344:
1.122 matthew 345: }
1.130 raeburn 346: } elsif (scalar(@items) && $items[0] eq 'all') {
1.122 matthew 347: if ($mode eq 'localized') {
1.142 bisitz 348: $text = &mt($phrases{$type}{all}.'.');
1.122 matthew 349: } elsif ($mode eq 'plaintext') {
1.130 raeburn 350: $text = $phrases{$type}{all}.'.';
1.122 matthew 351: }
1.130 raeburn 352: } elsif (scalar(@items)) {
353: my $lastitem = pop(@items);
1.122 matthew 354: if ($mode eq 'localized') {
1.142 bisitz 355: $text = &mt($phrases{$type}{plural}.' [_1] and [_2].',
1.130 raeburn 356: join(', ',@items),$lastitem);
1.122 matthew 357: } elsif ($mode eq 'plaintext') {
1.130 raeburn 358: $text = $phrases{$type}{plural}.' '.join(', ',@items).' and '.
359: $lastitem.'.';
1.122 matthew 360: }
1.119 matthew 361: }
1.130 raeburn 362: return $text;
1.119 matthew 363: }
1.71 matthew 364:
365:
366: sub get_students {
367: if (! @Students) {
368: &PrepareClasslist()
369: }
370: return @Students;
371: }
372:
1.61 matthew 373:
374:
375: sub current_student {
1.72 matthew 376: return $curr_student;
1.61 matthew 377: }
378:
379:
380:
381: sub previous_student {
1.72 matthew 382: return $prev_student;
1.59 matthew 383: }
384:
1.61 matthew 385:
386:
387: sub next_student {
1.72 matthew 388: return $next_student;
1.61 matthew 389: }
1.60 matthew 390:
391:
392:
1.61 matthew 393: sub StudentDataSelect {
394: my ($elementname,$status,$numvisible)=@_;
395: if ($numvisible < 1) {
396: return;
397: }
398: #
399: # Build the form element
400: my $Str = "\n";
401: $Str .= '<select name="'.$elementname.'" ';
402: if ($status ne 'single') {
1.147 bisitz 403: $Str .= 'multiple="multiple" ';
1.61 matthew 404: }
405: $Str .= 'size="'.$numvisible.'" >'."\n";
406: #
407: # Deal with 'all'
408: $Str .= ' <option value="all" ';
409: foreach (@SelectedStudentData) {
410: if ($_ eq 'all') {
411: $Str .= 'selected ';
412: last;
413: }
414: }
415: $Str .= ">all</option>\n";
416: #
417: # Loop through the student data fields
418: foreach my $item (@StudentDataOrder) {
419: $Str .= ' <option value="'.$item.'" ';
420: foreach (@SelectedStudentData) {
421: if ($item eq $_ ) {
422: $Str .= 'selected ';
423: last;
424: }
425: }
426: $Str .= '>'.$item."</option>\n";
427: }
428: $Str .= "</select>\n";
429: return $Str;
1.60 matthew 430: }
431:
1.115 matthew 432:
433:
434: sub get_selected_maps {
435: my ($elementname) = @_;
1.124 albertel 436: my @selected_maps =
437: &Apache::loncommon::get_env_multiple('form.'.$elementname);
438: @selected_maps = ('all') if (! @selected_maps);
1.118 matthew 439: foreach my $map (@selected_maps) {
440: if ($map eq 'all') {
441: @selected_maps = ('all');
442: last;
443: }
444: }
1.115 matthew 445: return @selected_maps;
446: }
447:
448:
449:
450:
1.116 matthew 451: sub selected_sequences_with_assessments {
1.115 matthew 452: my ($mode) = @_;
453: $mode = 'selected' if (! defined($mode));
454: my $navmap = Apache::lonnavmaps::navmap->new();
455: if (!defined($navmap)) {
456: return ('Can not open Coursemap');
457: }
458: #
1.148.2.5 raeburn 459: my $mapurl;
460: if (&Apache::loncommon::needs_gci_custom()) {
461: my $cid = $env{'request.course.id'};
462: if ($cid) {
1.148.2.7! raeburn 463: my $cdom = $env{'course.'.$cid.'.domain'};
! 464: if ($cdom =~ /^\w+citest$/) {
! 465: my $sequence = &Apache::loncommon::get_citest_map($cdom);
! 466: my $cnum = $env{'course.'.$cid.'.num'};
! 467: if ($sequence) {
! 468: $mapurl = '/uploaded/'.$cdom.'/'.$cnum.'/'.$sequence;
! 469: }
! 470: }
1.148.2.5 raeburn 471: }
472: }
473: my @sequences = $navmap->retrieveResources($mapurl,
1.115 matthew 474: sub { shift->is_map(); },1,0,1);
1.148.2.5 raeburn 475: unless (&Apache::loncommon::needs_gci_custom()) {
476: my $toplevelseq = $navmap->getById('0.0');
477: if (!grep(/^\Q$toplevelseq\E$/,@sequences)) {
478: unshift(@sequences,$toplevelseq);
479: }
1.139 raeburn 480: }
481:
1.115 matthew 482: my @sequences_with_assessments;
1.139 raeburn 483: foreach my $sequence (@sequences) {
1.120 matthew 484: if ($navmap->hasResource($sequence,sub { shift->is_problem(); },0,1)){
1.115 matthew 485: push(@sequences_with_assessments,$sequence);
486: }
487: }
488: #
489: my @sequences_to_show;
490: foreach my $sequence (@sequences_with_assessments) {
491: if ($mode eq 'all') {
492: push (@sequences_to_show,$sequence);
493: } elsif ($mode eq 'selected') {
1.116 matthew 494: foreach my $map_symb (&get_selected_maps('Maps')) {
1.115 matthew 495: if ($sequence->symb eq $map_symb || $map_symb eq 'all'){
496: push (@sequences_to_show,$sequence);
497: last; # Only put it in once
498: }
499: }
500: }
501:
502: }
503: return $navmap,@sequences_to_show;
504: }
505:
1.60 matthew 506:
1.115 matthew 507: sub map_select {
508: my ($elementname,$status,$numvisible)=@_;
1.60 matthew 509: if ($numvisible < 1) {
510: return;
511: }
512: #
513: # Set up array of selected items
1.115 matthew 514: my @selected_maps = &get_selected_maps($elementname);
1.60 matthew 515: #
516: # Build the form element
1.115 matthew 517: my $form = "\n";
518: $form .= '<select name="'.$elementname.'" ';
1.60 matthew 519: if ($status ne 'single') {
1.147 bisitz 520: $form .= 'multiple="multiple" ';
1.60 matthew 521: }
1.115 matthew 522: $form .= 'size="'.$numvisible.'" >'."\n";
1.148.2.5 raeburn 523: unless (&Apache::loncommon::needs_gci_custom()) {
524: #
525: # Put in option for 'all'
526: $form .= ' <option value="all" ';
527: if ($selected_maps[0] eq 'all') {
528: $form .= 'selected ';
529: }
530: $form .= ">all</option>\n";
1.61 matthew 531: }
532: #
1.60 matthew 533: # Loop through the sequences
1.117 matthew 534: my @sequences = &selected_sequences_with_assessments('all');
1.115 matthew 535: my $navmap;
536: if (!ref($sequences[0])) {
537: return $sequences[0];
538: } else {
539: $navmap = shift(@sequences);
540: }
541: foreach my $seq (@sequences){
542: $form .= ' <option value="'.$seq->symb.'" ';
1.148.2.5 raeburn 543: if ((&Apache::loncommon::needs_gci_custom()) && (@selected_maps == 1)) {
544: $form .= 'selected ';
545: } else {
546: foreach (@selected_maps) {
547: if ($seq->symb eq $_) {
548: $form .= 'selected ';
549: last;
550: }
1.60 matthew 551: }
552: }
1.115 matthew 553: $form .= '>'.$seq->compTitle."</option>\n";
1.60 matthew 554: }
1.115 matthew 555: $form .= "</select>\n";
556: return $form;
1.60 matthew 557: }
558:
559:
560: sub SectionSelect {
561: my ($elementname,$status,$numvisible)=@_;
562: if ($numvisible < 1) {
563: return;
564: }
565: #
1.71 matthew 566: # Make sure we have the data we need to continue
567: if (! @Sections) {
568: &PrepareClasslist()
569: }
570: #
1.60 matthew 571: # Build the form element
572: my $Str = "\n";
573: $Str .= '<select name="'.$elementname.'" ';
574: if ($status ne 'single') {
1.147 bisitz 575: $Str .= 'multiple="multiple" ';
1.60 matthew 576: }
577: $Str .= 'size="'.$numvisible.'" >'."\n";
578: #
579: # Loop through the sequences
580: foreach my $s (@Sections) {
581: $Str .= ' <option value="'.$s.'" ';
1.119 matthew 582: foreach (&get_selected_sections()) {
1.61 matthew 583: if ($s eq $_) {
1.60 matthew 584: $Str .= 'selected ';
585: last;
586: }
587: }
588: $Str .= '>'.$s."</option>\n";
589: }
590: $Str .= "</select>\n";
591: return $Str;
1.80 matthew 592: }
593:
1.144 jms 594:
1.130 raeburn 595: sub GroupSelect {
596: my ($elementname,$status,$numvisible)=@_;
597: if ($numvisible < 1) {
598: return;
599: }
600: #
601: # Make sure we have the data we need to continue
602: if (! @Groups) {
603: &PrepareClasslist();
604: }
605: #
606: # Build the form element
607: my $Str = "\n";
608: $Str .= '<select name="'.$elementname.'" ';
609: if ($status ne 'single') {
1.147 bisitz 610: $Str .= 'multiple="multiple" ';
1.130 raeburn 611: }
612: $Str .= 'size="'.$numvisible.'" >'."\n";
613: #
614: # Loop through the groups
615: foreach my $s (@Groups) {
616: $Str .= ' <option value="'.$s.'" ';
617: foreach my $group (&get_selected_groups()) {
618: if ($s eq $group) {
619: $Str .= 'selected ';
620: last;
621: }
622: }
623: $Str .= '>'.$s."</option>\n";
624: }
625: $Str .= "</select>\n";
626: }
627:
628:
1.144 jms 629:
630:
1.60 matthew 631: sub DisplayClasslist {
632: my ($r)=@_;
1.107 matthew 633: &Apache::lonhtmlcommon::add_breadcrumb
634: ({text=>'Select One Student'});
1.60 matthew 635: #
1.105 matthew 636: # Output some of the standard interface components
637: my $Str;
1.132 albertel 638: $Str .= &Apache::lonhtmlcommon::breadcrumbs('Select One Student');
1.105 matthew 639: $Str .= '<p><table cellspacing="5">'."\n";
640: $Str .= '<tr>';
641: $Str .= '<th align="center"><b>'.&mt('Sections').'</b></th>';
1.130 raeburn 642: $Str .= '<th align="center"><b>'.&mt('Groups').'</b></th>';
1.137 raeburn 643: $Str .= '<th align="center"><b>'.&mt('Access Status').'</b></th>';
1.105 matthew 644: $Str .= '</tr>'.$/;
645: $Str .= '<tr>';
646: $Str .= '<td>'.
647: &Apache::lonstatistics::SectionSelect('Section','multiple',5).
648: '</td>';
1.130 raeburn 649: $Str .= '<td>'.
650: &Apache::lonstatistics::GroupSelect('Group','multiple',5).
651: '</td>';
1.105 matthew 652: $Str .= '<td>'.
653: &Apache::lonhtmlcommon::StatusOptions(undef,undef,5).
654: '</td>';
655:
656: $Str .= '</tr>'.$/;
657: $Str .= '</table></p>';
658: $Str .= '<input type="submit" name="selectstudent" value="'.
659: &mt('Update Display').'" />';
660: $r->print($Str);
661: $r->rflush();
662: #
1.130 raeburn 663: my @Fields = ('fullname','username','domain','id','section','status','groups');
1.60 matthew 664: #
1.105 matthew 665: $Str = '';
1.119 matthew 666: my @selected_sections = &get_selected_sections();
1.78 matthew 667: if (! @Students) {
1.119 matthew 668: if ($selected_sections[0] eq 'all') {
1.141 bisitz 669: if (lc($env{'form.Status'}) eq 'active') {
670: $Str .= '<p class="LC_info">'.
1.106 matthew 671: &mt('There are no currently enrolled students in the course.').
1.141 bisitz 672: '</p>';
1.123 albertel 673: } elsif (lc($env{'form.Status'}) eq 'expired') {
1.141 bisitz 674: $Str .= '<p class="LC_info">'.
1.106 matthew 675: &mt('There are no previously enrolled students in the course.').
1.141 bisitz 676: '</p>';
677: } elsif (lc($env{'form.Status'}) eq 'future') {
678: $Str .= '<p class="LC_info">'.
679: &mt('There are no students with future access in the course.').
680: '</p>';
681: } else { # 'any' and any others
682: $Str .= '<p class="LC_info">'.
683: &mt('There are no students in the course.').
684: '</p>';
1.78 matthew 685: }
686: } else {
1.141 bisitz 687: if (lc($env{'form.Status'}) eq 'active') {
688: $Str .= '<p class="LC_info">'.
1.106 matthew 689: &mt('There are no currently enrolled students in the selected sections.').
1.141 bisitz 690: '</p>';
1.123 albertel 691: } elsif (lc($env{'form.Status'}) eq 'expired') {
1.141 bisitz 692: $Str .= '<p class="LC_info">'.
1.106 matthew 693: &mt('There are no previously enrolled students in the selected sections.').
1.141 bisitz 694: '</p>';
695: } elsif (lc($env{'form.Status'}) eq 'future') {
696: $Str .= '<p class="LC_info">'.
697: &mt('There are no students with future access in the selected sections.').
698: '</p>';
699: } else { # 'any' and any others
700: $Str .= '<p class="LC_info">'.
701: &mt('There are no students in the selected sections.').
702: '</p>';
1.78 matthew 703: }
704: }
1.141 bisitz 705: $Str.= '<p>'
706: .'<a href="/adm/statistics?reportSelected=student_assessment">'
707: .&mt('Return to the chart').'</a>'
708: .'</p>';
1.78 matthew 709: $r->print($Str);
710: $r->rflush();
711: return;
712: }
713:
1.141 bisitz 714: $Str .= '<h2>'.&mt('Select One Student').'</h2>'
715: .'<p>'.&mt("Click on a student's name or username to view their chart").'</p>'
716: .&Apache::loncommon::start_data_table()
717: .&Apache::loncommon::start_data_table_header_row();
1.60 matthew 718: foreach my $field (@Fields) {
1.101 matthew 719: $Str .= '<th><a href="/adm/statistics?'.
720: 'reportSelected=student_assessment&'.
721: 'selectstudent=1&'.
1.106 matthew 722: 'sort='.$field.'">'.&mt($field).
1.60 matthew 723: '</a></th>';
724: }
1.141 bisitz 725: $Str .= &Apache::loncommon::end_data_table_header_row();
1.60 matthew 726: #
1.65 matthew 727: foreach my $student (@Students) { # @Students is a package variable
1.60 matthew 728: my $sname = $student->{'username'}.':'.$student->{'domain'};
1.141 bisitz 729: $Str .= &Apache::loncommon::start_data_table_row();
1.60 matthew 730: #
731: foreach my $field (@Fields) {
732: $Str .= '<td>';
1.78 matthew 733: if ($field eq 'fullname' || $field eq 'username') {
1.60 matthew 734: $Str .= '<a href="/adm/statistics?reportSelected=';
1.136 www 735: $Str .= &escape('student_assessment');
736: $Str .= '&sort='.&escape($env{'form.sort'});
1.72 matthew 737: $Str .= '&SelectedStudent=';
1.136 www 738: $Str .= &escape($sname).'">';
1.146 bisitz 739: $Str .= $student->{$field}.' ';
1.60 matthew 740: $Str .= '</a>';
1.106 matthew 741: } elsif ($field eq 'status') {
742: $Str .= &mt($student->{$field});
1.60 matthew 743: } else {
744: $Str .= $student->{$field};
745: }
746: $Str .= '</td>';
747: }
1.141 bisitz 748: $Str .= &Apache::loncommon::end_data_table_row();
1.60 matthew 749: }
1.141 bisitz 750: $Str .= &Apache::loncommon::end_data_table();
1.60 matthew 751: #
752: $r->print($Str);
753: $r->rflush();
754: #
755: return;
756: }
757:
1.144 jms 758:
759:
1.33 stredwic 760: sub CreateMainMenu {
1.65 matthew 761: #
1.85 matthew 762: # Define menu data
1.145 diwert 763: my @reports = (
764: {categorytitle => 'Statistics and Analyses',
765: items => [
766: {url => '/adm/statistics?reportSelected=problem_statistics',
767: permission => 'F',
768: icon => 'document-open.png',
769: linktext => ('Overall Problem Statistics'),
770: linktitle => ('Student performance statistics on all problems.')},
771:
772: {url => '/adm/statistics?reportSelected=problem_analysis',
773: permission => 'F',
774: icon => 'edit-find.png',
775: linktext => ('Detailed Problem Analysis'),
776: linktitle => ('Detailed statistics and graphs of student performance on problems.')},
777: ]},
778: {categorytitle => 'Plots',
779: items => [
780: {url => '/adm/statistics?reportSelected=submissiontime_analysis',
781: permission => 'F',
782: icon => 'subtimpl.png',
783: linktext => ('Submission Time Plots'),
784: linktitle => ('Display and analysis of submission times on assessments.')},
785:
786: {url => '/adm/statistics?reportSelected=correct_problems_plot',
787: permission => 'F',
788: icon => 'coprplot.png',
789: linktext => ('Correct Problems Plot'),
790: linktitle => ('Display a histogram of student performance in the course.')},
1.148.2.3 raeburn 791: ]});
1.148.2.4 raeburn 792: if (&Apache::loncommon::needs_gci_custom()) {
793: push(@reports,
794: {categorytitle => 'Reports',
795: items => [
796: {url => '/adm/statistics?reportSelected=student_submission_reports',
797: permission => 'F',
798: icon => 'edit-copy.png',
799: linktext => ('Student Submission Reports'),
800: linktitle => ('Prepare reports of student submissions.')},
801: ]});
802: } else {
1.148.2.3 raeburn 803: push(@reports,
1.145 diwert 804: {categorytitle => 'Reports',
805: items => [
806: {url => '/adm/statistics?reportSelected=student_submission_reports',
807: permission => 'F',
808: icon => 'edit-copy.png',
809: linktext => ('Student Submission Reports'),
810: linktitle => ('Prepare reports of student submissions.')},
811:
812: {url => '/adm/statistics?reportSelected=survey_reports',
813: permission => 'F',
814: icon => 'docs.png',
815: linktext => ('Survey Reports'),
816: linktitle => ('Prepare reports on survey results.')},
817: ]});
1.148.2.3 raeburn 818: }
1.145 diwert 819:
820: return &Apache::lonhtmlcommon::generate_menu(@reports);
821:
1.33 stredwic 822: }
823:
1.1 albertel 824: sub handler {
1.31 minaeibi 825: my $r=shift;
1.65 matthew 826: my $c = $r->connection();
827: #
828: # Check for overloading
1.51 www 829: my $loaderror=&Apache::lonnet::overloaderror($r);
830: if ($loaderror) { return $loaderror; }
831: $loaderror=
832: &Apache::lonnet::overloaderror($r,
1.123 albertel 833: $env{'course.'.$env{'request.course.id'}.'.home'});
1.51 www 834: if ($loaderror) { return $loaderror; }
1.65 matthew 835: #
836: # Check for access
1.123 albertel 837: if (! &Apache::lonnet::allowed('vgr',$env{'request.course.id'})) {
838: $env{'user.error.msg'}=
1.69 matthew 839: $r->uri.":vgr:0:0:Cannot view grades for complete course";
840: if (! &Apache::lonnet::allowed('vgr',
1.123 albertel 841: $env{'request.course.id'}.'/'.$env{'request.course.sec'})) {
842: $env{'user.error.msg'}=
1.69 matthew 843: $r->uri.":vgr:0:0:Cannot view grades with given role";
844: return HTTP_NOT_ACCEPTABLE;
845: }
1.27 stredwic 846: }
1.65 matthew 847: #
848: # Send the header
1.92 www 849: &Apache::loncommon::no_cache($r);
850: &Apache::loncommon::content_type($r,'text/html');
1.27 stredwic 851: $r->send_http_header;
1.92 www 852: if ($r->header_only) { return OK; }
1.148.2.1 raeburn 853: my $gcicustom = &Apache::loncommon::needs_gci_custom();
854: if ($gcicustom) {
855: my $now = time;
1.148.2.2 raeburn 856: my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
857: my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
1.148.2.1 raeburn 858: my $courseopt=&Apache::lonnet::get_courseresdata($cnum,$cdom);
859: my $duedate = $courseopt->{$env{'request.course.id'}.'.0.duedate'};
860: if ((!$duedate) || ($duedate > $now)) {
1.148.2.6 raeburn 861: my $brcrum = [{href=> '/adm/statistics',
862: text=> 'Statistics',}];
863: my $args = {bread_crumbs => $brcrum,
864: bread_crumbs_component => 'Statistics Main Page'};
865: $r->print(&Apache::loncommon::start_page('Course Statistics',undef,$args));
1.148.2.1 raeburn 866: $r->print('<h3>'.&mt('Display of statistics').'</h3>');
867: if ($duedate > $now) {
868: $r->print('<p>'.&mt('Aggregate test performance data will be available after the Concept Test end date: [_1].',
1.148.2.2 raeburn 869: &Apache::lonlocal::locallocaltime($duedate)).'</p>');
1.148.2.1 raeburn 870: } else {
871: $r->print('<p>'.
872: &mt('Aggregate test performance data unavailable without definition of an end date for the Concept Test.').'</p>');
873: }
874: $r->print(&Apache::loncommon::end_page());
875: return OK;
876: }
1.148.2.2 raeburn 877: }
1.148.2.1 raeburn 878:
1.65 matthew 879: #
880: # Extract form elements from query string
1.60 matthew 881: &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
1.65 matthew 882: ['sort','reportSelected',
1.72 matthew 883: 'SelectedStudent']);
1.65 matthew 884: #
885: # Give the LON-CAPA page header
1.109 matthew 886: my $style = <<ENDSTYLE;
887: <style type="text/css">
888: ul.sub_studentans { list-style-type: none }
889: ul.sub_correctans { list-style-type: none }
1.110 matthew 890: tr.even { background-color: \#CCCCCC }
891: td.essay { border: 1px solid gray; }
1.109 matthew 892: </style>
893: ENDSTYLE
1.129 albertel 894:
895: $r->print(&Apache::loncommon::start_page('Course Statistics and Charts',
896: $style));
1.65 matthew 897: $r->rflush();
1.85 matthew 898: #
899: # Either print out a menu for them or send them to a report
1.98 matthew 900: &Apache::lonhtmlcommon::clear_breadcrumbs();
901: &Apache::lonhtmlcommon::add_breadcrumb({href=>'/adm/statistics',
1.100 matthew 902: title=>'Statistics',
903: text =>'Statistics',
1.98 matthew 904: faq=>139,
905: bug=>'Statistics and Charts'});
1.123 albertel 906: if (! exists($env{'form.reportSelected'}) ||
907: $env{'form.reportSelected'} eq '') {
1.132 albertel 908: $r->print(&Apache::lonhtmlcommon::breadcrumbs('Statistics Main Page').
1.98 matthew 909: &CreateMainMenu());
1.85 matthew 910: } else {
1.65 matthew 911: #
1.85 matthew 912: if (! &Apache::lonmysql::verify_sql_connection()) {
913: my $serveradmin = $r->dir_config('lonAdmEMail');
1.140 bisitz 914: $r->print('<h2 class="LC_error">'.
1.85 matthew 915: &mt('Unable to connect to database!').
1.140 bisitz 916: '</h2>');
917: $r->print('<p>'
918: .&mt('Please notify the server administrator [_1]',
919: ,'<b>'.$serveradmin.'</b>')
920: .'</p>');
1.85 matthew 921: $r->print('<p>'.
922: &mt('Course Statistics and Charts cannot be '.
1.148 bisitz 923: 'retrieved until the database is restarted. '.
1.85 matthew 924: 'Your data is intact but cannot be displayed '.
925: 'at this time.').'</p>');
1.129 albertel 926: $r->print(&Apache::loncommon::end_page());
1.85 matthew 927: return;
928: }
929: #
930: # Clean out the caches
1.123 albertel 931: if (exists($env{'form.ClearCache'})) {
932: &Apache::loncoursedata::delete_caches($env{'requres.course.id'});
1.85 matthew 933: }
934: #
935: # Begin form output
936: $r->print('<form name="Statistics" ');
937: $r->print('method="post" action="/adm/statistics">');
938: $r->rflush();
939: #
1.123 albertel 940: my $GoToPage = $env{'form.reportSelected'};
1.90 matthew 941: #
1.85 matthew 942: $r->print('<input type="hidden" name="reportSelected" value="'.
943: $GoToPage.'">');
944: if($GoToPage eq 'activitylog') {
1.65 matthew 945: # &Apache::lonproblemstatistics::Activity();
1.85 matthew 946: } elsif($GoToPage eq 'problem_statistics') {
1.98 matthew 947: &Apache::lonhtmlcommon::add_breadcrumb
948: ({href=>'/adm/statistics?reportselected=problem_statistics',
1.100 matthew 949: text=>'Overall Problem Statistics'});
1.85 matthew 950: &Apache::lonproblemstatistics::BuildProblemStatisticsPage($r,$c);
951: } elsif($GoToPage eq 'problem_analysis') {
1.98 matthew 952: &Apache::lonhtmlcommon::add_breadcrumb
953: ({href=>'/adm/statistics?reportselected=problem_analysis',
1.100 matthew 954: text=>'Detailed Problem Analysis'});
1.85 matthew 955: &Apache::lonproblemanalysis::BuildProblemAnalysisPage($r,$c);
1.89 matthew 956: } elsif($GoToPage eq 'submissiontime_analysis') {
1.98 matthew 957: &Apache::lonhtmlcommon::add_breadcrumb
958: ({href=>
959: '/adm/statistics?reportselected=submissiontime_analysis',
1.100 matthew 960: text=>'Submission Time Plots'});
1.89 matthew 961: &Apache::lonsubmissiontimeanalysis::BuildSubmissionTimePage($r,$c);
1.97 matthew 962: } elsif($GoToPage eq 'student_submission_reports') {
1.98 matthew 963: &Apache::lonhtmlcommon::add_breadcrumb
1.148.2.3 raeburn 964: ({href=>
965: '/adm/statistics?reportselected=student_submission_reports',
966: text=>'Student Submission Reports'});
1.148.2.4 raeburn 967: &Apache::lonstudentsubmissions::BuildStudentSubmissionsPage($r,$c);
1.104 matthew 968: } elsif($GoToPage eq 'survey_reports') {
969: &Apache::lonhtmlcommon::add_breadcrumb
970: ({href=>
971: '/adm/statistics?reportselected=survey_reports',
972: text=>'Survey Reports'});
973: &Apache::lonsurveyreports::BuildSurveyReportsPage($r,$c);
1.94 matthew 974: } elsif($GoToPage eq 'correct_problems_plot') {
1.98 matthew 975: &Apache::lonhtmlcommon::add_breadcrumb
976: ({href=>'/adm/statistics?reportselected=correct_problems_plot',
1.100 matthew 977: text=>'Correct Problems Plot'});
1.94 matthew 978: &Apache::loncorrectproblemplot::BuildCorrectProblemsPage($r,$c);
1.85 matthew 979: } elsif($GoToPage eq 'student_assessment') {
1.98 matthew 980: &Apache::lonhtmlcommon::clear_breadcrumbs();
981: &Apache::lonhtmlcommon::add_breadcrumb
982: ({href=>'/adm/statistics?reportselected=student_assessment',
1.100 matthew 983: text=>'Chart'});
1.148.2.1 raeburn 984: if ($gcicustom) {
1.148.2.3 raeburn 985: $r->print(&mt('Only aggregate performance data are available for Concept Tests.'));
1.148.2.1 raeburn 986: } else {
987: &Apache::lonstudentassessment::BuildStudentAssessmentPage($r,$c);
988: }
1.128 albertel 989: } elsif($GoToPage eq 'grading_analysis') {
990: &Apache::lonhtmlcommon::add_breadcrumb
991: ({href=>'/adm/statistics?reportselected=grading_anaylsis',
992: text=>'Grading Analysis'});
993: &Apache::longradinganalysis::build_grading_analysis_page($r,$c);
994: }
1.85 matthew 995: #
996: $r->print("</form>\n");
1.65 matthew 997: }
1.129 albertel 998: $r->print(&Apache::loncommon::end_page());
1.65 matthew 999: $r->rflush();
1000: #
1.27 stredwic 1001: return OK;
1.1 albertel 1002: }
1.65 matthew 1003:
1.1 albertel 1004: 1;
1.59 matthew 1005:
1.144 jms 1006: __END__
1.65 matthew 1007:
1.59 matthew 1008: =pod
1009:
1.144 jms 1010: =head1 NAME
1011:
1012: lonstatistics
1013:
1014: =head1 SYNOPSIS
1015:
1016: Main handler for statistics and chart.
1017:
1018: This is part of the LearningOnline Network with CAPA project
1019: described at http://www.lon-capa.org.
1020:
1021:
1022: =head1 PACKAGE VARIABLES
1023:
1024: =over
1025:
1026: =item @FullClasslist The full classlist
1027:
1028: =item @Students The students we are concerned with for this invocation
1029:
1030: =item @Sections The sections available in this class
1031:
1032: =item @Groups The groups available in the class
1033:
1034: =item $curr_student The student currently being examined
1035:
1036: =item $prev_student The student previous in the classlist
1037:
1038: =item $next_student The student next in the classlist
1039:
1040: =back
1041:
1042: =head1 SUBROUTINES
1043:
1044: =over
1045:
1046: =item &clear_classlist_variables()
1047:
1048: undef the following package variables:
1049:
1050: =over 4
1051:
1052: =item * @FullClasslist
1053:
1054: =item * @Students
1055:
1056: =item * @Sections
1057:
1058: =item * @Groups
1059:
1060: =item * %StudentData
1061:
1062: =item * @StudentDataOrder
1063:
1064: =item * @SelectedStudentData
1065:
1066: =item * $curr_student
1067:
1068: =item * $prev_student
1069:
1070: =item * $next_student
1071:
1072: =back
1073:
1074: =item &PrepareClasslist()
1075:
1076: Build up the classlist information. The classlist information is kept in
1077: the following package variables:
1078:
1079: =over 4
1080:
1081: =item * @FullClasslist
1082:
1083: =item * @Students
1084:
1085: =item * @Sections
1086:
1087: =item * @Groups
1088:
1089: =item * %StudentData
1090:
1091: =item * @SelectedStudentData
1092:
1093: =item * $curr_student
1094:
1095: =item * $prev_student
1096:
1097: =item * $next_student
1098:
1099: =back
1100:
1101: $curr_student, $prev_student, and $next_student may not be defined, depending
1102: upon the calling context.
1103:
1104: =item get_selected_sections()
1105:
1106: Returns an array of the selected sections
1107:
1108: =item get_selected_groups()
1109:
1110: Returns an array of the selected groups
1111:
1112: =item §ion_and_enrollment_description()
1113:
1114: Returns a string describing the currently selected section(s), group(s) and
1115: access status.
1116:
1117: Inputs: mode = 'plaintext' or 'localized' (defaults to 'localized')
1118: 'plaintext' is used for example in Excel spreadsheets.
1119: Returns: scalar description string.
1120:
1121: =item section_or_group_text()
1122:
1123: =item get_students()
1124:
1125: Returns a list of the selected students
1126:
1127: =item ¤t_student()
1128:
1129: Returns a pointer to a hash containing data about the currently
1130: selected student.
1131:
1132: =item &previous_student()
1133:
1134: Returns a pointer to a hash containing data about the student prior
1135: in the list of students. Or something.
1136:
1137: =item &next_student()
1138:
1139: Returns a pointer to a hash containing data about the next student
1140: to be viewed.
1141:
1142: =item &StudentDataSelect($elementname,$status,$numvisible,$selected)
1143:
1144: Returns html for a selection box allowing the user to choose one (or more)
1145: of the fields of student data available (fullname, username, id, section, etc)
1146:
1147: =over 4
1148:
1149: =item * $elementname The name of the HTML form element
1150:
1151: =item * $status 'multiple' or 'single' selection box
1152:
1153: =item * $numvisible The number of options to be visible
1154:
1155: =back
1156:
1157: =item &get_selected_maps($elementname)
1158:
1159: Input: Name of the <select> form element used to specify the maps.
1160:
1161: Returns: Array of symbs of selected maps or the description 'all'.
1162: If form.$elementname does not exist, 'all' is returned.
1163:
1164: =item &selected_sequences_with_assessments()
1165:
1166: Retrieve the sequences which were selected by the user to show.
1167:
1168: Input: $mode: scalar. Either 'selected' or 'all'. If not specified,
1169: 'selected' is used.
1170:
1171: Returns: an array containing a navmap object and navmap resources,
1172: or an array containing a scalar with an error message.
1173:
1174: =item &map_select($elementname,$status,$numvisible,$restriction)
1175:
1176: Returns html for a selection box allowing the user to choose one (or more)
1177: of the sequences in the course. The values of the sequences are the symbs.
1178: If the top sequence is selected, the value 'top' will result.
1179:
1180: =over 4
1181:
1182: =item * $elementname The name of the HTML form element
1183:
1184: =item * $status 'multiple' or 'single' selection box
1185:
1186: =item * $numvisible The number of options to be visible
1187:
1.59 matthew 1188: =back
1189:
1.144 jms 1190: =item &SectionSelect($elementname,$status,$numvisible)
1191:
1192: Returns html for a selection box allowing the user to choose one (or more)
1193: of the sections in the course.
1194:
1195: Uses the package variables @Sections
1196:
1197: =over 4
1198:
1199: =item * $elementname The name of the HTML form element
1200:
1201: =item * $status 'multiple' or 'single' selection box
1202:
1203: =item * $numvisible The number of options to be visible
1204:
1205: =back
1206:
1207: =item &GroupSelect($elementname,$status,$numvisible)
1208:
1209: Returns html for a selection box allowing the user to choose one (or more)
1210: of the groups in the course.
1211:
1212: Uses the package variables @Groups
1213:
1214: =over 4
1215:
1216: =item * $elementname The name of the HTML form element
1217:
1218: =item * $status 'multiple' or 'single' selection box
1219:
1220: =item * $numvisible The number of options to be visible
1221:
1222: =back
1223:
1224: =item CreateMainMenu()
1.65 matthew 1225:
1.144 jms 1226: =back
1.59 matthew 1227:
1.144 jms 1228: =cut
1.31 minaeibi 1229:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>