--- loncom/interface/Attic/lonchart.pm 2001/11/17 20:30:47 1.10 +++ loncom/interface/Attic/lonchart.pm 2002/06/28 21:12:46 1.46 @@ -1,4 +1,30 @@ # The LearningOnline Network with CAPA +# (Publication Handler +# +# $Id: lonchart.pm,v 1.46 2002/06/28 21:12:46 stredwic 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/ +# # Homework Performance Chart # # (Navigate Maps Handler @@ -6,349 +32,975 @@ # (Page Handler # # (TeX Content Handler -# +# YEAR=2000 # 05/29/00,05/30 Gerd Kortemeyer) # 08/30,08/31,09/06,09/14,09/15,09/16,09/19,09/20,09/21,09/23, # 10/02,10/10,10/14,10/16,10/18,10/19,10/31,11/6,11/14,11/16 Gerd Kortemeyer) -# -# 3/1/1,6/1,17/1,29/1,30/1 Gerd Kortemeyer) -# -# 1/31 Gerd Kortemeyer -# +# YEAR=2001 +# 3/1/1,6/1,17/1,29/1,30/1,31/1 Gerd Kortemeyer) # 7/10/01 Behrouz Minaei # 9/8 Gerd Kortemeyer -# 10/18/01, 10/19/01 Behrouz Minaei -# 11/17/01 Behrouz Minaei +# 10/1, 10/19, 11/17, 11/22, 11/24, 11/28 12/18 Behrouz Minaei +# YEAR=2002 +# 2/1, 2/6, 2/19, 2/28 Behrouz Minaei +# +### package Apache::lonchart; use strict; use Apache::Constants qw(:common :http); use Apache::lonnet(); +use Apache::loncommon(); use HTML::TokeParser; use GDBM_File; -# -------------------------------------------------------------- Module Globals -my %hash; -my @cols; -my @rowlabels; -my @students; +my $jr; +# ----- FORMAT PRINT DATA ---------------------------------------------- + +sub FormatStudentInformation { + my ($cache,$name,$studentInformation,$spacePadding)=@_; + my $Str='
'; + + foreach (@$studentInformation) { + my $data=$cache->{$name.':'.$_}; + $Str .= $data; + + my @dataLength=split(//,$data); + my $length=scalar @dataLength; + $Str .= (' 'x($cache->{$_.'Length'}-$length)); + $Str .= $spacePadding; + } -# ------------------------------------------------------------- Find out status + return $Str; +} -sub ExtractStudentData { - my ($index,$coid)=@_; - my ($sname,$sdom) = split( /\:/, $students[$index] ); - my $shome=&Apache::lonnet::homeserver( $sname,$sdom ); - my $reply=&Apache::lonnet::reply('dump:'.$sdom.':'.$sname.':'.$coid,$shome ); - my %result=(); - my $ResId; - my $Code; - my $Tries; - my $Wrongs; - my %TempHash; +sub FormatStudentData { + my ($name,$coid,$studentInformation,$spacePadding,$ChartDB)=@_; + my ($sname,$sdom) = split(/\:/,$name); + my $Str; + my %CacheData; + + unless(tie(%CacheData,'GDBM_File',$ChartDB,&GDBM_READER,0640)) { + return ''; + } + # Handle Student information ------------------------------------------ + # Handle user data + $Str=&FormatStudentInformation(\%CacheData, $name, $studentInformation, + $spacePadding); + + # Handle errors + if($CacheData{$name.':error'} =~ /environment/) { + untie(%CacheData); + $Str .= ''; + return $Str; +# my $errorMessage = $CacheData{$name.':error'}; +# return '
'; + + for(my $index=0; $index<(scalar @$headings); $index++) { + my $data=$$headings[$index]; + $Str .= $data; + + my @dataLength=split(//,$data); + my $length=scalar @dataLength; + $Str .= (' 'x($CacheData->{$$studentInformation[$index].'Length'}- + $length)); + $Str .= $spacePadding; + } + + foreach my $sequence (split(/\:/,$CacheData->{'orderedSequences'})) { + $Str .= $CacheData->{$sequence.':title'}; + my @titleLength=split(//,$CacheData->{$sequence.':title'}); + my $leftover=$CacheData->{$sequence.':columnWidth'}- + (scalar @titleLength); + $Str .= (' 'x$leftover); + $Str .= $spacePadding; + } + + $Str .= 'Total Solved/Total Problems'; + $Str .= ''; + + return $Str; } +sub CreateColumnSelectors { + my ($CacheData,$studentInformation,$headings,$spacePadding)=@_; + my $Str=''; + + $Str .= ''."\n"; + return $Str; + + for(my $index=0; $index<(scalar @$headings); $index++) { + my $data=$$headings[$index]; + $Str .= $data; + + my @dataLength=split(//,$data); + my $length=scalar @dataLength; + $Str .= (' 'x($CacheData->{$$studentInformation[$index].'Length'}- + $length)); + $Str .= $spacePadding; + } -# ------------------------------------------------------------ Build page table - -sub tracetable { - my ($rid,$beenhere)=@_; - unless ($beenhere=~/\&$rid\&/) { - $beenhere.=$rid.'&'; -# new ... updating the map according to sequence and page - if (defined($hash{'is_map_'.$rid})) { - my $cmap=$hash{'map_type_'.$hash{'map_pc_'.$hash{'src_'.$rid}}}; - if ( $cmap eq 'sequence' || $cmap eq 'page' ) { - $cols[$#cols+1]=0; - } - if ((defined($hash{'map_start_'.$hash{'src_'.$rid}})) && - (defined($hash{'map_finish_'.$hash{'src_'.$rid}}))) { - my $frid=$hash{'map_finish_'.$hash{'src_'.$rid}}; - - &tracetable($hash{'map_start_'.$hash{'src_'.$rid}}, - '&'.$frid.'&'); - - if ($hash{'src_'.$frid}) { - if ($hash{'src_'.$frid}=~ - /\.(problem|exam|quiz|assess|survey|form)$/) { - $cols[$#cols+1]=$frid; - } - } - - } - } else { - if ($hash{'src_'.$rid}) { - if ($hash{'src_'.$rid}=~ - /\.(problem|exam|quiz|assess|survey|form)$/) { - $cols[$#cols+1]=$rid; - } - } - } - if (defined($hash{'to_'.$rid})) { - map { - &tracetable($hash{'goesto_'.$_},$beenhere); - } split(/\,/,$hash{'to_'.$rid}); - } + foreach my $sequence (split(/\:/,$CacheData->{'orderedSequences'})) { + $Str .= $CacheData->{$sequence.':title'}; + my @titleLength=split(//,$CacheData->{$sequence.':title'}); + my $leftover=$CacheData->{$sequence.':columnWidth'}- + (scalar @titleLength); + $Str .= (' 'x$leftover); + $Str .= $spacePadding; } + + return $Str; } -# ================================================================ Main Handler +sub CreateForm { + my $OpSel1=''; + my $OpSel2=''; + my $OpSel3=''; + my $Status = $ENV{'form.status'}; + if ( $Status eq 'Any' ) { $OpSel3='selected'; } + elsif ($Status eq 'Expired' ) { $OpSel2 = 'selected'; } + else { $OpSel1 = 'selected'; } + + my $Ptr = ''."\n"; -sub handler { - my $r=shift; + return $Ptr; +} + +sub CreateLegend { + my $Str = '
1..9: correct by student in 1..9 tries\n". + " *: correct by student in more than 9 tries\n". + " +: correct by override\n". + " -: incorrect by override\n". + " .: incorrect attempted\n". + " #: ungraded attempted\n". + " : not attempted\n". + " x: excused
"; + return $Str; +} - if (&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'})) { -# ------------------------------------------- Set document type for header only +sub StartDocument { + my $Str = ''; + $Str .= ''; + $Str .= '
1..9: correct by student in 1..9 tries\n". - " *: correct by student in more than 9 tries\n". - " +: correct by override\n". - " -: incorrect by override\n". - " .: incorrect attempted\n". - " : not attempted\n". - " x: excused
"); - -# ------------------------------- This is going to take a while, produce output - - $r->rflush(); - -# ----------------------- Get first and last resource, see if there is anything - - - my $firstres=$hash{'map_start_/res/'.$ENV{'request.course.uri'}}; - my $lastres=$hash{'map_finish_/res/'.$ENV{'request.course.uri'}}; - if (($firstres) && ($lastres)) { -# ----------------------------------------------------------------- Render page - - my $cid=$ENV{'request.course.id'}; - my $chome=$ENV{'course.'.$cid.'.home'}; - my ($cdom,$cnum)=split(/\_/,$cid); - -# ---------------------------------------------- Read class list and row labels - - undef @rowlabels; - undef @students; - - my $classlst=&Apache::lonnet::reply - ('dump:'.$cdom.':'.$cnum.':classlist',$chome); - my $now=time; - unless ($classlst=~/^error\:/) { - map { - my ($name,$value)=split(/\=/,$_); - my ($end,$start)=split(/\:/,&Apache::lonnet::unescape($value)); - my $active=1; - if (($end) && ($now>$end)) { $active=0; } - if ($active) { - my $thisindex=$#students+1; - $name=&Apache::lonnet::unescape($name); - $students[$thisindex]=$name; - my ($sname,$sdom)=split(/\:/,$name); - my $ssec=&Apache::lonnet::usection($sdom,$sname,$cid); - if ($ssec==-1) { - $rowlabels[$thisindex]= - 'Data not available: '.$name; - } else { - my %reply=&Apache::lonnet::idrget($sdom,$sname); - my $reply=&Apache::lonnet::reply('get:'.$sdom.':'.$sname. - ':environment:lastname&generation&firstname&middlename', - &Apache::lonnet::homeserver($sname,$sdom)); - $rowlabels[$thisindex]= - sprintf('%3s',$ssec).' '.$reply{$sname}.' '; - my $i=0; - map { - $i++; - if ( $_ ne '') { - $rowlabels[$thisindex].=&Apache::lonnet::unescape($_).' '; - } - if ($i == 2) { - chop($rowlabels[$thisindex]); - $rowlabels[$thisindex].=', '; - } - } split(/\&/,$reply); +# ----- END DOWNLOAD INFORMATION --------------------------------------- +# ----- END PROCESSING FUNCTIONS --------------------------------------- + +sub ProcessTopResourceMap { + my ($ChartDB,$c)=@_; + my %hash; + my $fn=$ENV{'request.course.fn'}; + if(-e "$fn.db") { + my $tieTries=0; + while($tieTries < 3) { + if(tie(%hash,'GDBM_File',"$fn.db",&GDBM_READER,0640)) { + last; + } + $tieTries++; + sleep 1; + } + if($tieTries >= 3) { + return 'Coursemap undefined.'; + } + } else { + return 'Can not open Coursemap.'; + } + + my %CacheData; + unless(tie(%CacheData,'GDBM_File',$ChartDB,&GDBM_WRCREAT,0640)) { + untie(%hash); + return 'Could not tie cache hash.'; + } + + my (@sequences, @currentResource, @finishResource); + my ($currentSequence, $currentResourceID, $lastResourceID); + + $currentResourceID=$hash{'ids_/res/'.$ENV{'request.course.uri'}}; + push(@currentResource, $currentResourceID); + $lastResourceID=-1; + $currentSequence=-1; + my $topLevelSequenceNumber = $currentSequence; + + while(1) { + if($c->aborted()) { + last; + } + # HANDLE NEW SEQUENCE! + #if page || sequence + if(defined($hash{'map_pc_'.$hash{'src_'.$currentResourceID}})) { + push(@sequences, $currentSequence); + push(@currentResource, $currentResourceID); + push(@finishResource, $lastResourceID); + + $currentSequence=$hash{'map_pc_'.$hash{'src_'.$currentResourceID}}; + $lastResourceID=$hash{'map_finish_'. + $hash{'src_'.$currentResourceID}}; + $currentResourceID=$hash{'map_start_'. + $hash{'src_'.$currentResourceID}}; + + if(!($currentResourceID) || !($lastResourceID)) { + $currentSequence=pop(@sequences); + $currentResourceID=pop(@currentResource); + $lastResourceID=pop(@finishResource); + if($currentSequence eq $topLevelSequenceNumber) { + last; + } + } + } + + # Handle gradable resources: exams, problems, etc + $currentResourceID=~/(\d+)\.(\d+)/; + my $partA=$1; + my $partB=$2; + if($hash{'src_'.$currentResourceID}=~ + /\.(problem|exam|quiz|assess|survey|form)$/ && + $partA eq $currentSequence) { + my $Problem = &Apache::lonnet::symbclean( + &Apache::lonnet::declutter($hash{'map_id_'.$partA}). + '___'.$partB.'___'. + &Apache::lonnet::declutter($hash{'src_'. + $currentResourceID})); + + $CacheData{$currentResourceID.':problem'}=$Problem; + if(!defined($CacheData{$currentSequence.':problems'})) { + $CacheData{$currentSequence.':problems'}=$currentResourceID; + } else { + $CacheData{$currentSequence.':problems'}.= + ':'.$currentResourceID; + } + + #Get Parts for problem + my $meta=$hash{'src_'.$currentResourceID}; + foreach (split(/\,/,&Apache::lonnet::metadata($meta,'keys'))) { + if($_=~/^stores\_(\d+)\_tries$/) { + my $Part=&Apache::lonnet::metadata($meta,$_.'.part'); + if(!defined($CacheData{$currentSequence.':'. + $currentResourceID.':parts'})) { + $CacheData{$currentSequence.':'.$currentResourceID. + ':parts'}=$Part; + } else { + $CacheData{$currentSequence.':'.$currentResourceID. + ':parts'}.=':'.$Part; + } + } + } + } + + #if resource == finish resource + if($currentResourceID eq $lastResourceID) { + #pop off last resource of sequence + $currentResourceID=pop(@currentResource); + $lastResourceID=pop(@finishResource); + + if(defined($CacheData{$currentSequence.':problems'})) { + # Capture sequence information here + if(!defined($CacheData{'orderedSequences'})) { + $CacheData{'orderedSequences'}=$currentSequence; + } else { + $CacheData{'orderedSequences'}.=':'.$currentSequence; + } + + $CacheData{$currentSequence.':title'}= + $hash{'title_'.$currentResourceID}; + + my $totalProblems=0; + foreach (split(/\:/,$CacheData{$currentSequence. + ':problems'})) { + foreach ($CacheData{$currentSequence.':'.$_.':parts'}) { + $totalProblems++; + } } + my @titleLength=split(//,$CacheData{$currentSequence. + ':title'}); + # $extra is 3 for problems correct and 3 for space + # between problems correct and problem output + my $extra = 6; + if(($totalProblems + $extra) > (scalar @titleLength)) { + $CacheData{$currentSequence.':columnWidth'}= + $totalProblems + $extra; + } else { + $CacheData{$currentSequence.':columnWidth'}= + (scalar @titleLength); + } + } + + $currentSequence=pop(@sequences); + if($currentSequence eq $topLevelSequenceNumber) { + last; + } + #else + } + + # MOVE!!! + #move to next resource + unless(defined($hash{'to_'.$currentResourceID})) { + # big problem, need to handle. Next is probably wrong + last; + } + my @nextResources=(); + foreach (split(/\,/,$hash{'to_'.$currentResourceID})) { + push(@nextResources, $hash{'goesto_'.$_}); + } + push(@currentResource, @nextResources); + # Set the next resource to be processed + $currentResourceID=pop(@currentResource); + } + + unless (untie(%hash)) { + &Apache::lonnet::logthis("WARNING: ". + "Could not untie coursemap $fn (browse)". + "."); + } + + unless (untie(%CacheData)) { + &Apache::lonnet::logthis("WARNING: ". + "Could not untie Cache Hash (browse)". + "."); + } + + return 'OK'; +} + +sub ProcessSection { + my ($sectionData, $courseid,$ActiveFlag)=@_; + $courseid=~s/\_/\//g; + $courseid=~s/^(\w)/\/$1/; + + my $cursection='-1'; + my $oldsection='-1'; + my $status='Expired'; + my $section=''; + foreach my $key (keys (%$sectionData)) { + my $value = $sectionData->{$key}; + if ($key=~/^$courseid(?:\/)*(\w+)*\_st$/) { + $section=$1; + if($key eq $courseid.'_st') { + $section=''; + } + my ($dummy,$end,$start)=split(/\_/,$value); + my $now=time; + my $notactive=0; + if ($start) { + if($now<$start) { + $notactive=1; + } + } + if($end) { + if ($now>$end) { + $notactive=1; + } + } + if($notactive == 0) { + $status='Active'; + $cursection=$section; + last; + } + if($notactive == 1) { + $oldsection=$section; + } + } + } + if($status eq $ActiveFlag) { + if($cursection eq '-1') { + return $oldsection; + } + return $cursection; + } + if($ActiveFlag eq 'Any') { + if($cursection eq '-1') { + return $oldsection; + } + return $cursection; + } + return '-1'; +} + +sub ProcessStudentInformation { + my ($CacheData,$studentInformation,$section,$date,$name,$courseID,$c)=@_; + my ($studentName,$studentDomain) = split(/\:/,$name); + + $CacheData->{$name.':username'}=$studentName; + $CacheData->{$name.':domain'}=$studentDomain; + $CacheData->{$name.':date'}=$date; + + my ($checkForError)=keys(%$studentInformation); + if($checkForError =~ /^(con_lost|error|no_such_host)/i) { + $CacheData->{$name.':error'}= + 'Could not download student environment data.'; + $CacheData->{$name.':fullname'}=''; + $CacheData->{$name.':id'}=''; + } else { + $CacheData->{$name.':fullname'}=&ProcessFullName( + $studentInformation->{'lastname'}, + $studentInformation->{'generation'}, + $studentInformation->{'firstname'}, + $studentInformation->{'middlename'}); + $CacheData->{$name.':id'}=$studentInformation->{'id'}; + } + + # Get student's section number + my $sec=&ProcessSection($section, $courseID, $ENV{'form.status'}); + if($sec != -1) { + $CacheData->{$name.':section'}=$sec; + } else { + $CacheData->{$name.':section'}=''; + } + + return 0; +} + +sub ProcessClassList { + my ($classlist,$courseID,$ChartDB,$c)=@_; + my @names=(); + + my %CacheData; + if(tie(%CacheData,'GDBM_File',$ChartDB,&GDBM_WRCREAT,0640)) { + foreach my $name (keys(%$classlist)) { + if($name =~ /\:section/ || $name =~ /\:studentInformation/) { + next; + } + if($c->aborted()) { + last; } - } sort split(/\&/,$classlst); + push(@names,$name); + &ProcessStudentInformation( + \%CacheData, + $classlist->{$name.':studentInformation'}, + $classlist->{$name.':section'}, + $classlist->{$name}, + $name,$courseID,$c); + } + + $CacheData{'NamesOfStudents'}=join(":::",@names); +# $CacheData{'NamesOfStudents'}=&Apache::lonnet::arrayref2str(\@names); + untie(%CacheData); + } + + return @names; +} + +# ----- END PROCESSING FUNCTIONS --------------------------------------- +# ----- HELPER FUNCTIONS ----------------------------------------------- + +sub SpaceColumns { + my ($students,$studentInformation,$headings,$ChartDB)=@_; + + my %CacheData; + if(tie(%CacheData,'GDBM_File',$ChartDB,&GDBM_WRCREAT,0640)) { + # Initialize Lengths + for(my $index=0; $index<(scalar @$headings); $index++) { + my @titleLength=split(//,$$headings[$index]); + $CacheData{$$studentInformation[$index].'Length'}= + scalar @titleLength; + } + + foreach my $name (@$students) { + foreach (@$studentInformation) { + my @dataLength=split(//,$CacheData{$name.':'.$_}); + my $length=scalar @dataLength; + if($length > $CacheData{$_.'Length'}) { + $CacheData{$_.'Length'}=$length; + } + } + } + untie(%CacheData); + } + + return; +} + +sub ProcessFullName { + my ($lastname, $generation, $firstname, $middlename)=@_; + my $Str = ''; + + if($lastname ne '') { + $Str .= $lastname.' '; + if($generation ne '') { + $Str .= $generation; + } else { + chop($Str); + } + $Str .= ', '; + if($firstname ne '') { + $Str .= $firstname.' '; + } + if($middlename ne '') { + $Str .= $middlename; + } else { + chop($Str); + if($firstname eq '') { + chop($Str); + } + } } else { - $r->print('
'); - my $index; - for ($index=0;$index<=$#students;$index++) { - $r->print(&ExtractStudentData($index,$cid).''); - - } else { - $r->print('
'); - $r->rflush(); - } - $r->print('