# The LearningOnline Network with CAPA
# (Publication Handler
#
# $Id: lonstatistics.pm,v 1.55.2.1 2003/01/13 20:34:24 albertel 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'};
} else {
$sortBy = ':fullname';
}
my @order = sort { lc($cache->{$a.$sortBy}) cmp lc($cache->{$b.$sortBy}) ||
lc($cache->{$a.':fullname'}) cmp lc($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 $Ptr = '';
# $Ptr .= '';
# $Ptr .= 'Select Sections';
# $Ptr .= ' | '."\n";
# $Ptr .= ''."\n";
# my @sectionsSelected = split(':',$cache{'sectionsSelected'});
# my @sections = split(':',$cache{'sectionList'});
# $Ptr .= &Apache::lonhtmlcommon::MultipleSectionSelect(\@sections,
# \@sectionsSelected,
# 'Statistics');
# $Ptr .= ' |
';
# $r->print($Ptr);
# $r->rflush();
# my %mySections = ();
# foreach (@sections) { $mySections{$_} = 'True'; }
# $r->print("
$cache{'sectionsSelected'}
");
my $Str='';
$Str .= ''."\n";
$Str .= ''."\n";
my $displayString = 'DISPLAYDATA  | '."\n";
$Str .= &Apache::lonhtmlcommon::CreateHeadings(\%cache,
$studentInformation,
$headings, $displayString);
$Str .= ' '."\n";
my $alternate=0;
foreach (@$students) {
# if ($mySections{$cache{$_.':'.'section'}} ne 'True') {next;}
my ($username, $domain) = split(':', $_);
if($alternate) {
$Str .= '';
} else {
$Str .= ' ';
}
$alternate = ($alternate + 1) % 2;
foreach my $data (@$studentInformation) {
$Str .= '';
if($data eq 'fullname') {
$Str .= '';
$Str .= $cache{$_.':'.$data}.' ';
$Str .= '';
} elsif($data eq 'updateTime') {
$Str .= '';
$Str .= $cache{$_.':'.$data}.' ';
$Str .= ' ';
} else {
$Str .= $cache{$_.':'.$data}.' ';
}
$Str .= ' | '."\n";
}
}
$Str .= ' '."\n";
$Str .= '
|
'."\n";
$r->print($Str);
$r->rflush();
untie(%cache);
return;
}
sub CreateMainMenu {
my ($status, $reports)=@_;
my $Str = '';
$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' => 'Correct-problems Plot',
# '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".'