# The LearningOnline Network with CAPA # (Publication Handler # # $Id: lonstatistics.pm,v 1.53 2002/10/22 16:30:34 minaeibi Exp $ # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). # # LON-CAPA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # LON-CAPA is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LON-CAPA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ # # (Navigate problems for statistical reports # YEAR=2001 # 5/5,7/9,7/25/1,8/11,9/13,9/26,10/5,10/9,10/22,10/26 Behrouz Minaei # 11/1,11/4,11/16,12/14,12/16,12/18,12/20,12/31 Behrouz Minaei # YEAR=2002 # 1/22,2/1,2/6,2/25,3/2,3/6,3/17,3/21,3/22,3/26,4/7,5/6 Behrouz Minaei # 5/12,5/14,5/15,5/19,5/26,7/16,25/7,29/7 Behrouz Minaei # ### package Apache::lonstatistics; use strict; use Apache::Constants qw(:common :http); use Apache::lonnet(); use Apache::lonhomework; use Apache::loncommon; use Apache::loncoursedata; use Apache::lonhtmlcommon; use Apache::lonproblemanalysis; use Apache::lonproblemstatistics; use Apache::lonstudentassessment; use Apache::lonpercentage; use HTML::TokeParser; use GDBM_File; sub CheckFormElement { my ($cache, $ENVName, $cacheName, $default)=@_; if(defined($ENV{'form.'.$ENVName})) { $cache->{$cacheName} = $ENV{'form.'.$ENVName}; } elsif(!defined($cache->{$cacheName})) { $cache->{$cacheName} = $default; } return; } sub ProcessFormData{ my ($cache)=@_; $cache->{'reportKey'} = 'false'; &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, ['sort','download', 'reportSelected', 'StudentAssessmentStudent', 'ProblemStatisticsSort']); &CheckFormElement($cache, 'Status', 'Status', 'Active'); &CheckFormElement($cache, 'postdata', 'reportSelected', 'Class list'); &CheckFormElement($cache, 'reportSelected', 'reportSelected', 'Class list'); $cache->{'reportSelected'} = &Apache::lonnet::unescape($cache->{'reportSelected'}); &CheckFormElement($cache, 'DownloadAll', 'DownloadAll', 'false'); &CheckFormElement($cache, 'sort', 'sort', 'fullname'); &CheckFormElement($cache, 'download', 'download', 'false'); &CheckFormElement($cache, 'StatisticsMaps', 'StatisticsMaps', 'All Maps'); &CheckFormElement($cache, 'StatisticsProblemSelect', 'StatisticsProblemSelect', 'All Problems'); &CheckFormElement($cache, 'StatisticsPartSelect', 'StatisticsPartSelect', 'All Parts'); if(defined($ENV{'form.Section'})) { my @sectionsSelected = (ref($ENV{'form.Section'}) ? @{$ENV{'form.Section'}} : ($ENV{'form.Section'})); $cache->{'sectionsSelected'} = join(':', @sectionsSelected); } elsif(!defined($cache->{'sectionsSelected'})) { $cache->{'sectionsSelected'} = $cache->{'sectionList'}; } # student assessment if(defined($ENV{'form.CreateStudentAssessment'}) || defined($ENV{'form.NextStudent'}) || defined($ENV{'form.PreviousStudent'})) { $cache->{'reportSelected'} = 'Student Assessment'; } if(defined($ENV{'form.NextStudent'})) { $cache->{'StudentAssessmentMove'} = 'next'; } elsif(defined($ENV{'form.PreviousStudent'})) { $cache->{'StudentAssessmentMove'} = 'previous'; } else { $cache->{'StudentAssessmentMove'} = 'selected'; } &CheckFormElement($cache, 'StudentAssessmentStudent', 'StudentAssessmentStudent', 'All Students'); $cache->{'StudentAssessmentStudent'} = &Apache::lonnet::unescape($cache->{'StudentAssessmentStudent'}); &CheckFormElement($cache, 'DefaultColumns', 'DefaultColumns', 'false'); # Problem analysis &CheckFormElement($cache, 'Interval', 'Interval', '1'); # ProblemStatistcs &CheckFormElement($cache, 'DisplayCSVFormat', 'DisplayFormat', 'Display Table Format'); &CheckFormElement($cache, 'ProblemStatisticsAscend', 'ProblemStatisticsAscend', 'Ascending'); &CheckFormElement($cache, 'ProblemStatisticsSort', 'ProblemStatisticsSort', 'Homework Sets Order'); &CheckFormElement($cache, 'DisplayLegend', 'DisplayLegend', 'Hide Legend'); &CheckFormElement($cache, 'SortProblems', 'SortProblems', 'Sort Within Sequence'); # Search only form elements my @headingColumns=(); my @sequenceColumns=(); my $foundColumn = 0; if(defined($ENV{'form.ReselectColumns'})) { my @reselected = (ref($ENV{'form.ReselectColumns'}) ? @{$ENV{'form.ReselectColumns'}} : ($ENV{'form.ReselectColumns'})); foreach (@reselected) { if(/HeadingColumn/) { push(@headingColumns, $_); $foundColumn = 1; } elsif(/SequenceColumn/) { push(@sequenceColumns, $_); $foundColumn = 1; } } } $cache->{'reportKey'} = 'false'; if($cache->{'reportSelected'} eq 'Analyze') { $cache->{'reportKey'} = 'Analyze'; } elsif($cache->{'reportSelected'} eq 'DoDiffGraph') { $cache->{'reportKey'} = 'DoDiffGraph'; } elsif($cache->{'reportSelected'} eq 'PercentWrongGraph') { $cache->{'reportKey'} = 'PercentWrongGraph'; } if(defined($ENV{'form.DoDiffGraph'})) { $cache->{'reportSelected'} = 'DoDiffGraph'; $cache->{'reportKey'} = 'DoDiffGraph'; } elsif(defined($ENV{'form.PercentWrongGraph'})) { $cache->{'reportSelected'} = 'PercentWrongGraph'; $cache->{'reportKey'} = 'PercentWrongGraph'; } foreach (keys(%ENV)) { if(/form\.Analyze/) { $cache->{'reportSelected'} = 'Analyze'; $cache->{'reportKey'} = 'Analyze'; my $data; (undef, $data)=split(':::', $_); $cache->{'AnalyzeInfo'}=$data; } elsif(/form\.HeadingColumn/) { my $value = $_; $value =~ s/form\.//; push(@headingColumns, $value); $foundColumn=1; } elsif(/form\.SequenceColumn/) { my $value = $_; $value =~ s/form\.//; push(@sequenceColumns, $value); $foundColumn=1; } } if($foundColumn) { $cache->{'HeadingsFound'} = join(':', @headingColumns); $cache->{'SequencesFound'} = join(':', @sequenceColumns);; } if(!defined($cache->{'HeadingsFound'}) || $cache->{'DefaultColumns'} ne 'false') { $cache->{'HeadingsFound'}='HeadingColumnFull Name'; } if(!defined($cache->{'SequencesFound'}) || $cache->{'DefaultColumns'} ne 'false') { $cache->{'SequencesFound'}='All Sequences'; } $cache->{'DefaultColumns'} = 'false'; return; } =pod =item &SortStudents() Determines which students to display and in which order. Which are displayed are determined by their status(active/expired). The order is determined by the sort button pressed (default to username). The type of sorting is username, lastname, or section. =over 4 Input: $students, $CacheData $students: A array pointer to a list of students (username:domain) $CacheData: A pointer to the hash tied to the cached data Output: \@order @order: An ordered list of students (username:domain) =back =cut sub SortStudents { my ($cache)=@_; my @students = split(':::',$cache->{'NamesOfStudents'}); my @sorted1Students=(); foreach (@students) { if($cache->{'Status'} eq 'Any' || $cache->{$_.':Status'} eq $cache->{'Status'}) { push(@sorted1Students, $_); } } my $sortBy = ''; if(defined($cache->{'sort'})) { $sortBy = ':'.$cache->{'sort'}; } my @order = sort { $cache->{$a.$sortBy} cmp $cache->{$b.$sortBy} || $cache->{$a.':fullname'} cmp $cache->{$b.':fullname'} } @sorted1Students; return \@order; } =pod =item &SpaceColumns() Determines the width of all the columns in the chart. It is based on the max of the data for that column and its header. =over 4 Input: $students, $studentInformation, $headings, $ChartDB $students: An array pointer to a list of students (username:domain) $studentInformatin: The type of data for the student information. It is used as part of the key in $CacheData. $headings: The name of the student information columns. $ChartDB: The name of the cache database which is opened for read/write. Output: None - All data stored in cache. =back =cut sub SpaceColumns { my ($students,$studentInformation,$headings,$cache)=@_; # Initialize Lengths for(my $index=0; $index<(scalar @$headings); $index++) { my @titleLength=split(//,$headings->[$index]); $cache->{$studentInformation->[$index].':columnWidth'}= scalar @titleLength; } foreach my $name (@$students) { foreach (@$studentInformation) { my @dataLength=split(//,$cache->{$name.':'.$_}); my $length=(scalar @dataLength); if($length > $cache->{$_.':columnWidth'}) { $cache->{$_.':columnWidth'}=$length; } } } return; } sub PrepareData { my ($c, $cacheDB, $studentInformation, $headings,$r)=@_; # Test for access to the cache data my $courseID=$ENV{'request.course.id'}; my $isRecalculate=0; if(defined($ENV{'form.Recalculate'})) { $isRecalculate=1; } my $isCached = &Apache::loncoursedata::TestCacheData($cacheDB, $isRecalculate); if($isCached < 0) { return "Unable to tie hash to db file."; } # Download class list information if not using cached data my %cache; unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_WRCREAT(),0640)) { return "Unable to tie hash to db file."; } # if(!$isCached) { my $processTopResourceMapReturn= &Apache::loncoursedata::ProcessTopResourceMap(\%cache, $c); if($processTopResourceMapReturn ne 'OK') { untie(%cache); return $processTopResourceMapReturn; } # } if($c->aborted()) { untie(%cache); return 'aborted'; } my $classlist=&Apache::loncoursedata::DownloadClasslist($courseID, $cache{'ClasslistTimestamp'}, $c); foreach (keys(%$classlist)) { if(/^(con_lost|error|no_such_host)/i) { untie(%cache); return "Error getting student data."; } } if($c->aborted()) { untie(%cache); return 'aborted'; } # Active is a temporary solution, remember to change Apache::loncoursedata::ProcessClasslist(\%cache,$classlist,$courseID,$c); if($c->aborted()) { untie(%cache); return 'aborted'; } &ProcessFormData(\%cache); my $students = &SortStudents(\%cache); &SpaceColumns($students, $studentInformation, $headings, \%cache); $cache{'updateTime:columnWidth'}=24; my $download = $cache{'download'}; my $downloadAll = $cache{'DownloadAll'}; my @allStudents=(); if($download ne 'false') { $cache{'download'} = 'false'; } elsif($downloadAll ne 'false') { $cache{'DownloadAll'} = 'false'; if($downloadAll eq 'sorted') { @allStudents = @$students; } else { @allStudents = split(':::', $cache{'NamesOfStudents'}); } } untie(%cache); if($download ne 'false') { my @who = ($download); if(&Apache::loncoursedata::DownloadStudentCourseData(\@who, 'false', $cacheDB, 'true', 'false', $courseID, $r, $c) ne 'OK') { return 'Stop at download individual'; } } elsif($downloadAll ne 'false') { if(&Apache::loncoursedata::DownloadStudentCourseData(\@allStudents, 'false', $cacheDB, 'true', 'true', $courseID, $r, $c) ne 'OK') { return 'Stop at download all'; } } return ('OK', $students); } sub BuildClasslist { my ($cacheDB,$students,$studentInformation,$headings,$r)=@_; my %cache; unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) { return 'Unable to tie database.'; } my $Str=''; $Str .= '
'."\n"; $Str .= ''."\n"; my $displayString = ''."\n"; $Str .= &Apache::lonhtmlcommon::CreateHeadings(\%cache, $studentInformation, $headings, $displayString); $Str .= ''."\n"; my $alternate=0; foreach (@$students) { my ($username, $domain) = split(':', $_); if($alternate) { $Str .= ''; } else { $Str .= ''; } $alternate = ($alternate + 1) % 2; foreach my $data (@$studentInformation) { $Str .= ''."\n"; } } $Str .= ''."\n"; $Str .= '
DISPLAYDATA 
'; if($data eq 'fullname') { $Str .= ''; $Str .= $cache{$_.':'.$data}.' '; $Str .= ''; } elsif($data eq 'updateTime') { $Str .= ''; $Str .= $cache{$_.':'.$data}.' '; $Str .= ' '; } else { $Str .= $cache{$_.':'.$data}.' '; } $Str .= '
'."\n"; $r->print($Str); $r->rflush(); untie(%cache); return; } sub CreateMainMenu { my ($status, $reports)=@_; my $Str = ''; $Str .= ''."\n"; $Str .= ''."\n"; $Str .= ''."\n"; $Str .= ''."\n"; $Str .= ''."\n"; $Str .= ''."\n"; $Str .= ''."\n"; $Str .= '
Analysis Reports:Student Status:
{'reportSelected'} eq $reports->{$_}) { $Str .= ' selected=""'; } $Str .= '>'.$reports->{$_}.''."\n"; } $Str .= ''; $Str .= &Apache::lonhtmlcommon::StatusOptions($status, 'Statistics'); $Str .= '
'."\n"; $Str .= '
'."\n"; return $Str; } sub BuildStatistics { my ($r)=@_; my $c = $r->connection; my @studentInformation=('fullname','section','id','domain','username', 'updateTime'); my @headings=('Full Name', 'Section', 'PID', 'Domain', 'User Name', 'Last Updated'); my $spacing = ' '; my %reports = ('classlist' => 'Class list', 'problem_statistics' => 'Problem Statistics', 'student_assessment' => 'Student Assessment', 'percentage' => 'Percentage Graphs', # 'activitylog' => 'Activity Log', 'reportSelected' => 'Class list'); my %cache; my $courseID=$ENV{'request.course.id'}; my $cacheDB = "/home/httpd/perl/tmp/$ENV{'user.name'}". "_$ENV{'user.domain'}_$courseID\_statistics.db"; $r->print(&Apache::lonhtmlcommon::Title('Course Statistics and Charts')); my ($returnValue, $students) = &PrepareData($c, $cacheDB, \@studentInformation, \@headings,$r); if($returnValue ne 'OK') { $r->print($returnValue."\n".''); return OK; } if(!$c->aborted()) { &Apache::loncoursedata::CheckForResidualDownload($cacheDB, 'true', 'true', $courseID, $r, $c); } my $GoToPage; if(tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) { $GoToPage = $cache{'reportSelected'}; $reports{'reportSelected'} = $cache{'reportSelected'}; if(defined($cache{'reportKey'}) && !exists($reports{$cache{'reportKey'}}) && $cache{'reportKey'} ne 'false') { $reports{$cache{'reportKey'}} = $cache{'reportSelected'}; } if(defined($cache{'OptionResponses'})) { $reports{'problem_analysis'} = 'Option Response Analysis'; } $r->print('
print('method="post" action="/adm/statistics">'); $r->print(&CreateMainMenu($cache{'Status'}, \%reports)); $r->rflush(); untie(%cache); } else { $r->print('Unable to tie database.'); return OK; } if($GoToPage eq 'Activity Log') { &Apache::lonproblemstatistics::Activity(); } elsif($GoToPage eq 'Problem Statistics') { &Apache::lonproblemstatistics::BuildProblemStatisticsPage($cacheDB, $students, $courseID, $c,$r); } elsif($GoToPage eq 'Option Response Analysis') { &Apache::lonproblemanalysis::BuildProblemAnalysisPage($cacheDB, $r); } elsif($GoToPage eq 'Student Assessment') { &Apache::lonstudentassessment::BuildStudentAssessmentPage($cacheDB, $students, $courseID, 'Statistics', \@headings, $spacing, \@studentInformation, $r, $c); } elsif($GoToPage eq 'Analyze') { &Apache::lonproblemanalysis::BuildAnalyzePage($cacheDB, $students, $courseID, $r); } elsif($GoToPage eq 'DoDiffGraph' || $GoToPage eq 'PercentWrongGraph') { my $courseDescription = $ENV{'course.'.$courseID.'.description'}; $courseDescription =~ s/\ /"_"/eg; &Apache::lonproblemstatistics::BuildGraphicChart($GoToPage, $cacheDB, $courseDescription, $students, $courseID, $r, $c); } elsif($GoToPage eq 'Class list') { &BuildClasslist($cacheDB, $students, \@studentInformation, \@headings, $r); } elsif($GoToPage eq 'Percentage Graphs') { &Apache::lonpercentage::BuildPercentageGraph($cacheDB, $students, $courseID, $c, $r); } $r->print('
'."\n"); $r->print("\n".''."\n".''); $r->rflush(); return OK; } # ================================================================ Main Handler sub handler { my $r=shift; # $jr = $r; my $loaderror=&Apache::lonnet::overloaderror($r); if ($loaderror) { return $loaderror; } $loaderror= &Apache::lonnet::overloaderror($r, $ENV{'course.'.$ENV{'request.course.id'}.'.home'}); if ($loaderror) { return $loaderror; } unless(&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'})) { $ENV{'user.error.msg'}= $r->uri.":vgr:0:0:Cannot view grades for complete course"; return HTTP_NOT_ACCEPTABLE; } # Set document type for header only if($r->header_only) { if ($ENV{'browser.mathml'}) { $r->content_type('text/xml'); } else { $r->content_type('text/html'); } &Apache::loncommon::no_cache($r); $r->send_http_header; return OK; } unless($ENV{'request.course.fn'}) { my $requrl=$r->uri; $ENV{'user.error.msg'}="$requrl:bre:0:0:Course not initialized"; return HTTP_NOT_ACCEPTABLE; } $r->content_type('text/html'); $r->send_http_header; &BuildStatistics($r); return OK; } 1; __END__