# The LearningOnline Network with CAPA # # $Id: lonstatistics.pm,v 1.130 2006/05/01 05:39:44 raeburn 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 # ### =pod =head1 NAME lonstatistics =head1 SYNOPSIS Main handler for statistics and chart. =over 4 =cut package Apache::lonstatistics; use strict; use Apache::Constants qw(:common :http); use vars qw( @FullClasslist @Students @Sections @Groups %StudentData @StudentDataOrder @SelectedStudentData $enrollment_status); use Apache::lonnet; use Apache::lonhomework; use Apache::loncommon; use Apache::loncoursedata; use Apache::lonhtmlcommon; use Apache::lonmysql; use Apache::lonlocal; use Time::HiRes; # # Statistics Packages use Apache::lonproblemanalysis(); use Apache::lonsubmissiontimeanalysis(); use Apache::loncorrectproblemplot(); use Apache::lonproblemstatistics(); use Apache::lonstudentassessment(); use Apache::lonpercentage; use Apache::lonstudentsubmissions(); use Apache::lonsurveyreports(); use Apache::longradinganalysis(); ####################################################### ####################################################### =pod =item Package Variables =item @FullClasslist The full classlist =item @Students The students we are concerned with for this invocation =item @Sections The sections available in this class =item @Groups The groups available in the class =item $curr_student The student currently being examined =item $prev_student The student previous in the classlist =item $next_student The student next in the classlist =over =cut ####################################################### ####################################################### # # Classlist variables # my $curr_student; my $prev_student; my $next_student; ####################################################### ####################################################### =pod =item &clear_classlist_variables() undef the following package variables: =over =item @FullClasslist =item @Students =item @Sections =item @Groups =item %StudentData =item @StudentDataOrder =item @SelectedStudentData =item $curr_student =item $prev_student =item $next_student =back =cut ####################################################### ####################################################### sub clear_classlist_variables { undef(@FullClasslist); undef(@Students); undef(@Sections); undef(@Groups); undef(%StudentData); undef(@SelectedStudentData); undef($curr_student); undef($prev_student); undef($next_student); } ####################################################### ####################################################### =pod =item &PrepareClasslist() Build up the classlist information. The classlist information is kept in the following package variables: =over =item @FullClasslist =item @Students =item @Sections =item @Groups =item %StudentData =item @SelectedStudentData =item $curr_student =item $prev_student =item $next_student =back $curr_student, $prev_student, and $next_student may not be defined, depending upon the calling context. =cut ####################################################### ####################################################### sub PrepareClasslist { my %Sections; &clear_classlist_variables(); # # Retrieve the classlist my $cid = $env{'request.course.id'}; my $cdom = $env{'course.'.$cid.'.domain'}; my $cnum = $env{'course.'.$cid.'.num'}; my ($classlist,$field_names) = &Apache::loncoursedata::get_classlist($cdom, $cnum); my @selected_sections = &get_selected_sections(); my @selected_groups = &get_selected_groups(); # # Deal with instructors with restricted section access if ($env{'request.course.sec'} !~ /^\s*$/) { @selected_sections = ($env{'request.course.sec'}); } # # Set up %StudentData @StudentDataOrder = qw/fullname username domain id section status groups comments/; foreach my $field (@StudentDataOrder) { $StudentData{$field}->{'title'} = &mt($field); $StudentData{$field}->{'base_width'} = length(&mt($field)); $StudentData{$field}->{'width'} = $StudentData{$field}->{'base_width'}; } # # get the status requested $enrollment_status = 'Active'; $enrollment_status = $env{'form.Status'} if (exists($env{'form.Status'})); # # Get groupmembership my (%curr_groups,$classgroups,$studentgroups); my $numgroups = &Apache::loncommon::coursegroups(\%curr_groups,$cdom,$cnum); if ($numgroups) { ($classgroups,$studentgroups) = &Apache::loncoursedata::get_group_memberships($classlist, $cdom,$cnum); } my $now = time; # Process the classlist while (my ($student,$student_data) = each (%$classlist)) { my $studenthash = (); for (my $i=0; $i< scalar(@$field_names);$i++) { my $field = $field_names->[$i]; # Store the data $studenthash->{$field}=$student_data->[$i]; # Keep track of the width of the fields next if (! exists($StudentData{$field})); my $length = length($student_data->[$i]); if ($StudentData{$field}->{'width'} < $length) { $StudentData{$field}->{'width'} = $length; } } my @studentsgroups = &Apache::loncoursedata::get_students_groups ($student,$enrollment_status, $classgroups); if (@studentsgroups) { $studenthash->{'groups'} = join(', ',@studentsgroups); $studenthash->{'groupref'} = \@studentsgroups; } else { $studenthash->{'groups'} = 'none'; $studenthash->{'groupref'} = []; } push (@FullClasslist,$studenthash); # # Build up a list of sections my $section = $studenthash->{'section'}; if (! defined($section) || $section =~/^\s*$/ || $section == -1) { $studenthash->{'section'} = 'none'; $section = $studenthash->{'section'}; } $Sections{$section}++; # # Only put in the list those students we are interested in foreach my $sect (@selected_sections) { if ( (($sect eq 'all') || ($section eq $sect)) && (($studenthash->{'status'} eq $enrollment_status) || ($enrollment_status eq 'Any')) ){ my $groupcheck = 0; if (grep/^all$/,@selected_groups) { push (@Students,$studenthash); last; } elsif (grep/^none$/,@selected_groups) { if ($studenthash->{'groups'} eq 'none') { push (@Students,$studenthash); last; } } else { foreach my $group (@selected_groups) { if (grep/^$group$/,@studentsgroups) { push (@Students,$studenthash); $groupcheck = 1; last; } } if ($groupcheck) { last; } } } } } # # Put the consolidated section data in the right place if ($env{'request.course.sec'} !~ /^\s*$/) { @Sections = ($env{'request.course.sec'}); } else { @Sections = sort {$a cmp $b} keys(%Sections); unshift(@Sections,'all'); # Put 'all' at the front of the list } # Sort the groups @Groups = sort {$a cmp $b} keys(%{$studentgroups}); unshift(@Groups,'all'); # Put 'all' at the front of the list # # Sort the Students my $sortby = 'fullname'; $sortby = $env{'form.sort'} if (exists($env{'form.sort'})); my @TmpStudents = sort { lc($a->{$sortby}) cmp lc($b->{$sortby}) || lc($a->{'fullname'}) cmp lc($b->{'fullname'}) || lc($a->{'username'}) cmp lc($b->{'username'}) } @Students; @Students = @TmpStudents; # # Now deal with that current student thing.... $curr_student = undef; if (exists($env{'form.SelectedStudent'})) { my ($current_uname,$current_dom) = split(':',$env{'form.SelectedStudent'}); my $i; for ($i = 0; $i<=$#Students; $i++) { next if (($Students[$i]->{'username'} ne $current_uname) || ($Students[$i]->{'domain'} ne $current_dom)); $curr_student = $Students[$i]; last; # If we get here, we have our student. } if (defined($curr_student)) { if ($i == 0) { $prev_student = undef; } else { $prev_student = $Students[$i-1]; } if ($i == $#Students) { $next_student = undef; } else { $next_student = $Students[$i+1]; } } } # if (exists($env{'form.StudentData'})) { @SelectedStudentData = &Apache::loncommon::get_env_multiple('form.StudentData'); } else { @SelectedStudentData = ('username'); } foreach (@SelectedStudentData) { if ($_ eq 'all') { @SelectedStudentData = ('all'); last; } } # return; } ####################################################### ####################################################### =pod =item get_selected_sections Returns an array of the selected sections =cut ####################################################### ####################################################### sub get_selected_sections { my @selected_sections = &Apache::loncommon::get_env_multiple('form.Section'); @selected_sections = ('all') if (! @selected_sections); foreach (@selected_sections) { if ($_ eq 'all') { @selected_sections = ('all'); } } # # Deal with instructors with restricted section access if ($env{'request.course.sec'} !~ /^\s*$/) { @selected_sections = ($env{'request.course.sec'}); } return @selected_sections; } ####################################################### ####################################################### =pod =item get_selected_groups Returns an array of the selected groups =cut ####################################################### ####################################################### sub get_selected_groups { my @selected_groups = &Apache::loncommon::get_env_multiple('form.Group'); @selected_groups = ('all') if (! @selected_groups); foreach my $grp (@selected_groups) { if ($grp eq 'all') { @selected_groups = ('all'); last; } } return @selected_groups; } =pod =item §ion_and_enrollment_description Returns a string describing the currently selected section(s), group(s) and enrollment status. Inputs: mode = 'plaintext' or 'localized' (defaults to 'localized') 'plaintext' is used for example in Excel spreadsheets. Returns: scalar description string. =cut ####################################################### ####################################################### sub section_and_enrollment_description { my ($mode) = @_; if (! defined($mode)) { $mode = 'localized'; } my @sections = &Apache::lonstatistics::get_selected_sections(); my @groups = &Apache::lonstatistics::get_selected_groups(); my $description; if ($mode eq 'localized') { $description = &mt('Unable to determine section, groups and enrollment'); } elsif ($mode eq 'plaintext') { $description = 'Unable to determine section, groups and enrollment'; } else { $description = 'Bad parameter passed to lonstatistics::section_and_enrollment_description'; &Apache::lonnet::logthis($description); } $description = §ion_or_group_text($mode,'section',@sections). ' '.§ion_or_group_text($mode,'group',@groups); if ($mode eq 'localized') { $description .= &mt(' [_1] enrollment status.',$env{'form.Status'}); } elsif ($mode eq 'plaintext') { $description .= ' '.$env{'form.Status'}.' enrollment status.'; } return $description; } ####################################################### ####################################################### sub section_or_group_text { my ($mode,$type,@items) = @_; my $text; my %phrases = (); %{$phrases{'section'}} = ( single => 'Section', all => 'All sections', plural => 'Sections', ); %{$phrases{'group'}} = ( single => 'Group', all => 'All groups', plural => 'Groups', ); if (scalar(@items) == 1 && $items[0] ne 'all') { if ($mode eq 'localized') { $text = &mt('[_1] [_2].',$phrases{$type}{single},$items[0]); } elsif ($mode eq 'plaintext') { $text = $phrases{$type}{single}.' '.$items[0].'.'; } } elsif (scalar(@items) && $items[0] eq 'all') { if ($mode eq 'localized') { $text = &mt('[_1].',$phrases{$type}{all}); } elsif ($mode eq 'plaintext') { $text = $phrases{$type}{all}.'.'; } } elsif (scalar(@items)) { my $lastitem = pop(@items); if ($mode eq 'localized') { $text = &mt('[_1] [_2] and [_3].',$phrases{$type}{plural}, join(', ',@items),$lastitem); } elsif ($mode eq 'plaintext') { $text = $phrases{$type}{plural}.' '.join(', ',@items).' and '. $lastitem.'.'; } } return $text; } =pod =item get_students Returns a list of the selected students =cut ####################################################### ####################################################### sub get_students { if (! @Students) { &PrepareClasslist() } return @Students; } ####################################################### ####################################################### =pod =item ¤t_student() Returns a pointer to a hash containing data about the currently selected student. =cut ####################################################### ####################################################### sub current_student { return $curr_student; } ####################################################### ####################################################### =pod =item &previous_student() Returns a pointer to a hash containing data about the student prior in the list of students. Or something. =cut ####################################################### ####################################################### sub previous_student { return $prev_student; } ####################################################### ####################################################### =pod =item &next_student() Returns a pointer to a hash containing data about the next student to be viewed. =cut ####################################################### ####################################################### sub next_student { return $next_student; } ############################################## ############################################## =pod =item &StudentDataSelect($elementname,$status,$numvisible,$selected) Returns html for a selection box allowing the user to choose one (or more) of the fields of student data available (fullname, username, id, section, etc) =over 4 =item $elementname The name of the HTML form element =item $status 'multiple' or 'single' selection box =item $numvisible The number of options to be visible =back =cut ############################################## ############################################## sub StudentDataSelect { my ($elementname,$status,$numvisible)=@_; if ($numvisible < 1) { return; } # # Build the form element my $Str = "\n"; $Str .= '\n"; return $Str; } ####################################################### ####################################################### =pod =item &get_selected_maps($elementname) Input: Name of the '."\n"; # # Put in option for 'all' $form .= ' \n"; } $form .= "\n"; return $form; } ############################################## ############################################## =pod =item &SectionSelect($elementname,$status,$numvisible) Returns html for a selection box allowing the user to choose one (or more) of the sections in the course. Uses the package variables @Sections =over 4 =item $elementname The name of the HTML form element =item $status 'multiple' or 'single' selection box =item $numvisible The number of options to be visible =back =cut ############################################## ############################################## sub SectionSelect { my ($elementname,$status,$numvisible)=@_; if ($numvisible < 1) { return; } # # Make sure we have the data we need to continue if (! @Sections) { &PrepareClasslist() } # # Build the form element my $Str = "\n"; $Str .= '\n"; return $Str; } ############################################## ############################################## =pod =item &GroupSelect($elementname,$status,$numvisible) Returns html for a selection box allowing the user to choose one (or more) of the groups in the course. Uses the package variables @Groups =over 4 =item $elementname The name of the HTML form element =item $status 'multiple' or 'single' selection box =item $numvisible The number of options to be visible =back =cut ############################################## ############################################## sub GroupSelect { my ($elementname,$status,$numvisible)=@_; if ($numvisible < 1) { return; } # # Make sure we have the data we need to continue if (! @Groups) { &PrepareClasslist(); } # # Build the form element my $Str = "\n"; $Str .= '\n"; } ################################################## ################################################## sub DisplayClasslist { my ($r)=@_; &Apache::lonhtmlcommon::add_breadcrumb ({text=>'Select One Student'}); # # Output some of the standard interface components my $Str; $Str .= &Apache::lonhtmlcommon::breadcrumbs(undef,'Select One Student'); $Str .= '

'."\n"; $Str .= ''; $Str .= ''; $Str .= ''; $Str .= ''; $Str .= ''.$/; $Str .= ''; $Str .= ''; $Str .= ''; $Str .= ''; $Str .= ''.$/; $Str .= '
'.&mt('Sections').''.&mt('Groups').''.&mt('Enrollment Status').'
'. &Apache::lonstatistics::SectionSelect('Section','multiple',5). ''. &Apache::lonstatistics::GroupSelect('Group','multiple',5). ''. &Apache::lonhtmlcommon::StatusOptions(undef,undef,5). '

'; $Str .= ''; $r->print($Str); $r->rflush(); # my @Fields = ('fullname','username','domain','id','section','status','groups'); # $Str = ''; my @selected_sections = &get_selected_sections(); if (! @Students) { if ($selected_sections[0] eq 'all') { if (lc($env{'form.Status'}) eq 'any') { $Str .= '

'. &mt('There are no students in the course.'). '

'; } elsif (lc($env{'form.Status'}) eq 'active') { $Str .= '

'. &mt('There are no currently enrolled students in the course.'). '

'; } elsif (lc($env{'form.Status'}) eq 'expired') { $Str .= '

'. &mt('There are no previously enrolled students in the course.'). '

'; } } else { my $sections; if (lc($env{'form.Status'}) eq 'any') { $Str .= '

'. &mt('There are no students in the selected sections.'). '

'; } elsif (lc($env{'form.Status'}) eq 'active') { $Str .= '

'. &mt('There are no currently enrolled students in the selected sections.'). '

'; } elsif (lc($env{'form.Status'}) eq 'expired') { $Str .= '

'. &mt('There are no previously enrolled students in the selected sections.'). '

'; } } $Str.= ''. &mt('Click here to return to the chart').''; $r->print($Str); $r->rflush(); return; } # "Click" is asinine but it is probably not my place to change the world. $Str .= '

Click on a students name or username to view their chart

'; $Str .= '
'."\n"; $Str .= ''."\n"; foreach my $field (@Fields) { $Str .= ''; } $Str .= ''."\n"; # my $alternate = 0; foreach my $student (@Students) { # @Students is a package variable my $sname = $student->{'username'}.':'.$student->{'domain'}; if($alternate) { $Str .= ''; } else { $Str .= ''; } $alternate = ($alternate + 1) % 2; # foreach my $field (@Fields) { $Str .= ''; } $Str .= "\n"; } $Str .= '
'.&mt($field). '
'; if ($field eq 'fullname' || $field eq 'username') { $Str .= ''; $Str .= $student->{$field}.' '; $Str .= ''; } elsif ($field eq 'status') { $Str .= &mt($student->{$field}); } else { $Str .= $student->{$field}; } $Str .= '
'."\n"; # $r->print($Str); $r->rflush(); # return; } ############################################## ############################################## sub CreateMainMenu { # # Define menu data my @reports = ({ internal_name => 'problem_statistics', name => &mt('Overall Problem Statistics'), short_description => &mt('Student performance statistics on all problems.'), }, { internal_name => 'problem_analysis', name => &mt('Detailed Problem Analysis'), short_description => &mt('Detailed statistics and graphs of student performance on problems.'), }, { internal_name => 'submissiontime_analysis', name => &mt('Submission Time Plots'), short_description => &mt('Display and analysis of submission times on assessments.'), }, { internal_name => 'student_submission_reports', name => &mt('Student Submission Reports'), short_description => &mt('Prepare reports of student submissions.'), }, { internal_name => 'survey_reports', name => &mt('Survey Reports'), short_description => &mt('Prepare reports on survey results.'), }, { internal_name => 'correct_problems_plot', name => &mt('Correct Problems Plot'), short_description => &mt('Display a histogram of student performance in the course.'), }, # { internal_name => 'grading_analysis', # name => &mt('Detailed Grading Analysis'), # short_description => # &mt('Display statistics about who graded who.'), # }, # { internal_name => 'student_assessment', # name => &mt('Problem Status Chart'), # short_description => # &mt('Brief view of each students performance in course.'), # }, # 'percentage' => 'Correct-problems Plot', # 'activitylog' => 'Activity Log', ); # # Create the menu my $Str; $Str .= '

'.&mt('Please select a report to generate').'

'; foreach my $reportdata (@reports) { $Str .='

'. $reportdata->{'name'}."

\n"; $Str .= ' '.(' 'x8).$reportdata->{'short_description'}. "\n"; } $Str .="\n"; # return $Str; } ############################################## ############################################## sub handler { my $r=shift; my $c = $r->connection(); # # Check for overloading 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; } # # Check for access if (! &Apache::lonnet::allowed('vgr',$env{'request.course.id'})) { $env{'user.error.msg'}= $r->uri.":vgr:0:0:Cannot view grades for complete course"; if (! &Apache::lonnet::allowed('vgr', $env{'request.course.id'}.'/'.$env{'request.course.sec'})) { $env{'user.error.msg'}= $r->uri.":vgr:0:0:Cannot view grades with given role"; return HTTP_NOT_ACCEPTABLE; } } # # Send the header &Apache::loncommon::no_cache($r); &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; if ($r->header_only) { return OK; } # # Extract form elements from query string &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, ['sort','reportSelected', 'SelectedStudent']); # # Give the LON-CAPA page header my $style = < ul.sub_studentans { list-style-type: none } ul.sub_correctans { list-style-type: none } tr.even { background-color: \#CCCCCC } td.essay { border: 1px solid gray; } ENDSTYLE $r->print(&Apache::loncommon::start_page('Course Statistics and Charts', $style)); $r->rflush(); # # Either print out a menu for them or send them to a report &Apache::lonhtmlcommon::clear_breadcrumbs(); &Apache::lonhtmlcommon::add_breadcrumb({href=>'/adm/statistics', title=>'Statistics', text =>'Statistics', faq=>139, bug=>'Statistics and Charts'}); if (! exists($env{'form.reportSelected'}) || $env{'form.reportSelected'} eq '') { $r->print(&Apache::lonhtmlcommon::breadcrumbs (undef,&mt('Statistics Main Page')). &CreateMainMenu()); } else { # if (! &Apache::lonmysql::verify_sql_connection()) { my $serveradmin = $r->dir_config('lonAdmEMail'); $r->print('

'. &mt('Unable to connect to database!'). '

'); $r->print('

'. &mt('Please notify the server administrator '). ''.$serveradmin.'

'); $r->print('

'. &mt('Course Statistics and Charts cannot be '. 'retrieved until the database is restarted. '. 'Your data is intact but cannot be displayed '. 'at this time.').'

'); $r->print(&Apache::loncommon::end_page()); return; } # # Clean out the caches if (exists($env{'form.ClearCache'})) { &Apache::loncoursedata::delete_caches($env{'requres.course.id'}); } # # Begin form output $r->print('
print('method="post" action="/adm/statistics">'); $r->rflush(); # my $GoToPage = $env{'form.reportSelected'}; # $r->print(''); if($GoToPage eq 'activitylog') { # &Apache::lonproblemstatistics::Activity(); } elsif($GoToPage eq 'problem_statistics') { &Apache::lonhtmlcommon::add_breadcrumb ({href=>'/adm/statistics?reportselected=problem_statistics', text=>'Overall Problem Statistics'}); &Apache::lonproblemstatistics::BuildProblemStatisticsPage($r,$c); } elsif($GoToPage eq 'problem_analysis') { &Apache::lonhtmlcommon::add_breadcrumb ({href=>'/adm/statistics?reportselected=problem_analysis', text=>'Detailed Problem Analysis'}); &Apache::lonproblemanalysis::BuildProblemAnalysisPage($r,$c); } elsif($GoToPage eq 'submissiontime_analysis') { &Apache::lonhtmlcommon::add_breadcrumb ({href=> '/adm/statistics?reportselected=submissiontime_analysis', text=>'Submission Time Plots'}); &Apache::lonsubmissiontimeanalysis::BuildSubmissionTimePage($r,$c); } elsif($GoToPage eq 'student_submission_reports') { &Apache::lonhtmlcommon::add_breadcrumb ({href=> '/adm/statistics?reportselected=student_submission_reports', text=>'Student Submission Reports'}); &Apache::lonstudentsubmissions::BuildStudentSubmissionsPage($r,$c); } elsif($GoToPage eq 'survey_reports') { &Apache::lonhtmlcommon::add_breadcrumb ({href=> '/adm/statistics?reportselected=survey_reports', text=>'Survey Reports'}); &Apache::lonsurveyreports::BuildSurveyReportsPage($r,$c); } elsif($GoToPage eq 'correct_problems_plot') { &Apache::lonhtmlcommon::add_breadcrumb ({href=>'/adm/statistics?reportselected=correct_problems_plot', text=>'Correct Problems Plot'}); &Apache::loncorrectproblemplot::BuildCorrectProblemsPage($r,$c); } elsif($GoToPage eq 'student_assessment') { &Apache::lonhtmlcommon::clear_breadcrumbs(); &Apache::lonhtmlcommon::add_breadcrumb ({href=>'/adm/statistics?reportselected=student_assessment', text=>'Chart'}); &Apache::lonstudentassessment::BuildStudentAssessmentPage($r,$c); } elsif($GoToPage eq 'grading_analysis') { &Apache::lonhtmlcommon::add_breadcrumb ({href=>'/adm/statistics?reportselected=grading_anaylsis', text=>'Grading Analysis'}); &Apache::longradinganalysis::build_grading_analysis_page($r,$c); } # $r->print("
\n"); } $r->print(&Apache::loncommon::end_page()); $r->rflush(); # return OK; } 1; ####################################################### ####################################################### =pod =back =cut ####################################################### ####################################################### __END__