--- loncom/interface/Attic/lonchart.pm 2002/02/19 20:22:00 1.31 +++ loncom/interface/Attic/lonchart.pm 2002/07/03 14:11:14 1.54 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # (Publication Handler # -# $Id: lonchart.pm,v 1.31 2002/02/19 20:22:00 minaeibi Exp $ +# $Id: lonchart.pm,v 1.54 2002/07/03 14:11:14 stredwic Exp $ # # Copyright Michigan State University Board of Trustees # @@ -25,7 +25,6 @@ # # http://www.lon-capa.org/ # -# # Homework Performance Chart # # (Navigate Maps Handler @@ -43,10 +42,14 @@ # 9/8 Gerd Kortemeyer # 10/1, 10/19, 11/17, 11/22, 11/24, 11/28 12/18 Behrouz Minaei # YEAR=2002 -# 2/1, 2/6, 2/19 Behrouz Minaei +# 2/1, 2/6, 2/19, 2/28 Behrouz Minaei # ### +=pod + +=cut + package Apache::lonchart; use strict; @@ -56,413 +59,1147 @@ use Apache::loncommon(); use HTML::TokeParser; use GDBM_File; -# -------------------------------------------------------------- Module Globals -my %hash; -my %CachData; -my @cols; -my @rowlabels; -my @students; -my @PreCol; -my $r; +#my $jr; +# ----- FORMAT PRINT DATA ---------------------------------------------- -# ------------------------------------------------------------- Find out status +sub FormatStudentInformation { + my ($cache,$name,$studentInformation,$spacePadding)=@_; + my $Str=''; + + for(my $index=0; $index<(scalar @$studentInformation); $index++) { + if(!&ShouldShowColumn($cache, 'heading'.$index)) { + next; + } + my $data=$cache->{$name.':'.$studentInformation->[$index]}; + $Str .= $data; -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; + my @dataLength=split(//,$data); + my $length=scalar @dataLength; + $Str .= (' 'x($cache->{$studentInformation->[$index].'Length'}- + $length)); + $Str .= $spacePadding; + } + + return $Str; +} + +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/) { + $Str .= '
'; + untie(%CacheData); + return $Str; + } + + if($CacheData{$name.':error'} =~ /course/) { + $Str .= '
'; + untie(%CacheData); + return $Str; + } + + # Handle problem data ------------------------------------------------ my $Version; - my $ProbNo; - my $ProbSolved; - my $ProbTotal; - my $LatestVersion; - my $Str=substr($students[$index]. - ' ',0,14).' ! '. - substr($rowlabels[$index]. - ' ',0,45).' ! '; - unless ($reply=~/^error\:/) { - foreach (split(/\&/,$reply)) { - my ($name,$value)=split(/\=/,&Apache::lonnet::unescape($_)); - $result{$name}=$value; - } - $ProbNo = 0; - $ProbTotal = 0; - $ProbSolved = 0; - my $IterationNo = 0; - foreach $ResId (@cols) { - if ($IterationNo == 0) {$IterationNo++; next;} - if (!$ResId) { - my $PrNo = sprintf( "%3d", $ProbNo ); - $Str .= ' '.''.$PrNo.' '; - $ProbSolved += $ProbNo; - $ProbNo=0; - next; - } - $ResId=~/(\d+)\.(\d+)/; - my $meta=$hash{'src_'.$ResId}; - my $PartNo = 0; - undef %TempHash; - foreach (split(/\,/,&Apache::lonnet::metadata($meta,'keys'))) { - if ($_=~/^stores\_(\d+)\_tries$/) { - my $Part=&Apache::lonnet::metadata($meta,$_.'.part'); - if ( $TempHash{"$Part"} eq '' ) { - $TempHash{"$Part"} = $Part; - $TempHash{$PartNo}=$Part; - $TempHash{"$Part.Code"} = ' '; - $PartNo++; - } - } + my $problemsCorrect = 0; + my $totalProblems = 0; + my $problemsSolved = 0; + my $numberOfParts = 0; + foreach my $sequence (split(/\:/,$CacheData{'orderedSequences'})) { + if(!&ShouldShowColumn(\%CacheData, 'sequence'.$sequence)) { + next; + } + + my $characterCount=0; + foreach my $problemID (split(/\:/,$CacheData{$sequence.':problems'})) { + my $problem = $CacheData{$problemID.':problem'}; + my $LatestVersion = $CacheData{$name.":version:$problem"}; + + if(!$LatestVersion) { + foreach my $part (split(/\:/,$CacheData{$sequence.':'. + $problemID. + ':parts'})) { + $Str .= ' '; + $totalProblems++; + $characterCount++; + } + next; } - my $Prob = &Apache::lonnet::declutter( $hash{'map_id_'.$1} ). - '___'.$2.'___'. - &Apache::lonnet::declutter( $hash{'src_'.$ResId} ); - $Code=' '; - $Tries = 0; - $LatestVersion = $result{"version:$Prob"}; - - if ( $LatestVersion ) { - for ( my $Version=1; $Version<=$LatestVersion; $Version++ ) { - my $vkeys = $result{"$Version:keys:$Prob"}; - my @keys = split(/\:/,$vkeys); - - foreach my $Key (@keys) { - if (($Key=~/\.(\w+)\.solved$/) && ($Key!~/^\d+\:/)) { - my $Part = $1; - $Tries = $result{"$Version:$Prob:resource.$Part.tries"}; - $TempHash{"$Part.Tries"}=($Tries) ? $Tries : 0; - my $Val = $result{"$Version:$Prob:resource.$Part.solved"}; - if ($Val eq 'correct_by_student'){$Code='*';} - elsif ($Val eq 'correct_by_override'){$Code = '+';} - elsif ($Val eq 'incorrect_attempted'){$Code = '.';} - elsif ($Val eq 'incorrect_by_override'){$Code = '-';} - elsif ($Val eq 'excused'){$Code = 'x';} - elsif ($Val eq 'ungraded_attempted'){$Code = '#';} - else {$Code = ' ';} - $TempHash{"$Part.Code"} = $Code; - } - } - } - for ( my $n = 0; $n < $PartNo; $n++ ) { - my $part = $TempHash{$n}; - my $Code = $TempHash{"$part.Code"}; - if ( $Code eq '*') { - $ProbNo++; - if (($TempHash{"$part.Tries"}<10) || - ($TempHash{"$part.Tries"} eq '')) { - $TempHash{"$part.Code"}=$TempHash{"$part.Tries"}; - } + my %partData=undef; + #initialize data, displays skips correctly + foreach my $part (split(/\:/,$CacheData{$sequence.':'. + $problemID. + ':parts'})) { + $partData{$part.':tries'}=0; + $partData{$part.':code'}=' '; + } + for(my $Version=1; $Version<=$LatestVersion; $Version++) { + foreach my $part (split(/\:/,$CacheData{$sequence.':'. + $problemID. + ':parts'})) { + + if(!defined($CacheData{$name.":$Version:$problem". + ":resource.$part.solved"})) { + next; } - elsif ( $Code eq '+' ) {$ProbNo++;} - $Str .= $TempHash{"$part.Code"}; - if ( $Code ne 'x' ) {$ProbTotal++;} - } - } - else { - for(my $n=0; $n<$PartNo; $n++) { - $Str.=' '; - $ProbTotal++; - } - } - } + + my $tries=0; + my $code=' '; + + $tries = $CacheData{$name.":$Version:$problem". + ":resource.$part.tries"}; + $partData{$part.':tries'}=($tries) ? $tries : 0; + + my $val = $CacheData{$name.":$Version:$problem". + ":resource.$part.solved"}; + if ($val eq 'correct_by_student') {$code = '*';} + elsif ($val eq 'correct_by_override') {$code = '+';} + elsif ($val eq 'incorrect_attempted') {$code = '.';} + elsif ($val eq 'incorrect_by_override'){$code = '-';} + elsif ($val eq 'excused') {$code = 'x';} + elsif ($val eq 'ungraded_attempted') {$code = '#';} + else {$code = ' ';} + $partData{$part.':code'}=$code; + } + } + + $Str.=''; + foreach(split(/\:/,$CacheData{$sequence.':'.$problemID. + ':parts'})) { + if($partData{$_.':code'} eq '*') { + $problemsCorrect++; + if (($partData{$_.':tries'}<10) && + ($partData{$_.':tries'} ne '')) { + $partData{$_.':code'}=$partData{$_.':tries'}; + } + } elsif($partData{$_.':code'} eq '+') { + $problemsCorrect++; + } + + $Str .= $partData{$_.':code'}; + $characterCount++; + + if($partData{$_.':code'} ne 'x') { + $totalProblems++; + } + } + $Str.=''; + } + + my $spacesNeeded=$CacheData{$sequence.':columnWidth'}-$characterCount; + $spacesNeeded -= 3; + $Str .= (' 'x$spacesNeeded); + + my $outputProblemsCorrect = sprintf( "%3d", $problemsCorrect ); + $Str .= ''.$outputProblemsCorrect.''; + $problemsSolved += $problemsCorrect; + $problemsCorrect=0; + + $Str .= $spacePadding; } - my $PrTot = sprintf( "%5d", $ProbTotal ); - my $PrSvd = sprintf( "%5d", $ProbSolved ); - $Str .= ' '.''.$PrSvd.' /'.$PrTot.' '; - - return $Str ; + + my $outputProblemsSolved = sprintf( "%4d", $problemsSolved ); + my $outputTotalProblems = sprintf( "%4d", $totalProblems ); + $Str .= ''.$outputProblemsSolved. + ' / '.$outputTotalProblems.'
'; + + untie(%CacheData); + return $Str; } +sub CreateTableHeadings { + my ($CacheData,$studentInformation,$headings,$spacePadding)=@_; + my $Str=''; + + for(my $index=0; $index<(scalar @$headings); $index++) { + if(!&ShouldShowColumn($CacheData, 'heading'.$index)) { + next; + } -# ------------------------------------------------------------ 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})) { - foreach (split(/\,/,$hash{'to_'.$rid})){ - &tracetable($hash{'goesto_'.$_},$beenhere); - } - } + $Str .= '
';
+	my $data=$$headings[$index];
+	$Str .= $data;
+
+	my @dataLength=split(//,$data);
+	my $length=scalar @dataLength;
+	$Str .= (' 'x($CacheData->{$$studentInformation[$index].'Length'}-
+                      $length));
+	$Str .= $spacePadding;
+        $Str .= '
'; } -} + foreach my $sequence (split(/\:/,$CacheData->{'orderedSequences'})) { + if(!&ShouldShowColumn($CacheData, 'sequence'.$sequence)) { + next; + } -sub BuildChart { -# ----------------------- 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 - my $classlst=&Apache::lonnet::reply - ('dump:'.$cdom.':'.$cnum.':classlist',$chome); - my $now=time; - unless ($classlst=~/^error\:/) { - foreach my $KeyPoint(sort split(/\&/,$classlst)) { - my ($name,$value)=split(/\=/,$KeyPoint); - 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); - $PreCol[$thisindex]=$sname.':'; - 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)); - #$ssec=(int($ssec)) ? int($ssec) : $ssec; - my $sec=sprintf('%3s',$ssec); - $rowlabels[$thisindex]=$sec.' '.$reply{$sname}.' '; - $PreCol[$thisindex] .= $reply.':'.$sec; - my $i=0; - foreach (split(/\&/,$reply)) { - $i++; - if ( $_ ne '') { - $rowlabels[$thisindex].=&Apache::lonnet::unescape($_).' '; - } - if ($i == 2) { - chop($rowlabels[$thisindex]); - $rowlabels[$thisindex].=', '; - } - } - } - } - } + $Str .= '
';
+        my $name = $CacheData->{$sequence.':title'};
+	$Str .= $name;
+	my @titleLength=split(//,$CacheData->{$sequence.':title'});
+	my $leftover=$CacheData->{$sequence.':columnWidth'}-
+                     (scalar @titleLength);
+	$Str .= (' 'x$leftover);
+	$Str .= $spacePadding;
+        $Str .= '
'; + } - } else { - $r->print('

Could not access course data

'); - } + $Str .= '
Total Solved/Total Problems
'; + $Str .= ''; - my $allstudents=$#students+1; - $r->print('

'.$allstudents.' students

'); - &CreateForm(); - $r->rflush(); - -# --------------- Find all assessments and put them into some linear-like order - &tracetable($firstres,'&'.$lastres.'&'); -# ----------------------------------------------------------------- Start table - - $r->print('

');
- 	my $index;
-        for ($index=0;$index<=$#students;$index++) {
-	    my $Str=&ExtractStudentData($index,$cid);
-	    $r->print($Str.'
'); - $r->rflush(); - $CachData{$PreCol[$index]}=$Str; + return $Str; +} + +sub CreateColumnSelectionBox { + my ($CacheData,$studentInformation,$headings,$spacePadding)=@_; + + my $missing=0; + my $notThere='Select column to view:'; + my $name; + $notThere .= ''; + $notThere .= ''; } else { - $r->print('

Undefined course sequence

'); + $notThere=''; } -} -sub CreateForm { - my $Ptr = '
'."\n"; - $Ptr .= ' Sort by:   '."\n"; - $Ptr .= '   '; - $Ptr .= ''."\n"; - $Ptr .= '   '; - $Ptr .= ''."\n"; - $Ptr .= '   '; - $Ptr .= ''."\n"; - $Ptr .= '
'; - $Ptr .= ''."\n"; - $Ptr .= '
'."\n"; - $r->print( $Ptr ); + return $notThere.''; } -sub CacheChart { - my @list = (); - my $count=0; +sub CreateColumnSelectors { + my ($CacheData,$studentInformation,$headings,$spacePadding)=@_; + + my $found=0; + my ($name, $length, $position); - my $Pos = $ENV{'form.sort'}; - if ( $Pos eq 'Last Name' ) {$Pos=1;} - elsif ( $Pos eq 'Section' ) {$Pos=2;} - else {$Pos=0;} + my $present='
Note: Uncheck the boxes above a column to ';
+    $present .= 'remove that column from the display.
'."\n"; - foreach my $key( keys %CachData) { - my @Temp=split(/\:/,$key); - my $Use = $Temp[$Pos]; - $list[$count]=$Use.$key.'*'.$CachData{$key}; - $count++; + $present .= ''; + $present .= ''; + for(my $index=0; $index<(scalar @$headings); $index++) { + if(!&ShouldShowColumn($CacheData, 'heading'.$index)) { + next; + } + $present .= ''; + $found++; } - @list = sort (@list); + foreach my $sequence (split(/\:/,$CacheData->{'orderedSequences'})) { + if(!&ShouldShowColumn($CacheData, 'sequence'.$sequence)) { + next; + } + $present .= ''; + $found++; + } - $r->print('

'.$count.' students

'); - &CreateForm(); - $r->rflush(); - - $r->print('

');
-    for ( my $n; $n < $count; $n++) {
-	my ($dummy, $Line) = split(/\*/,$list[$n]);
-	$r->print($Line.'
'); - } - $r->print('
'); -} - -sub Start { - undef %hash; - undef %CachData; - undef @students; - undef @cols; - undef @rowlabels; - undef @PreCol; - - $r->print(''. - 'LON-CAPA Assessment Chart'); - $r->print(''. - ''. - ''. - '

Assessment Chart

'); -# ---------------------------------------------------------------- Course title - $r->print('

'.$ENV{'course.'.$ENV{'request.course.id'}. - '.description'}.'

'.localtime(). - "

1..9: correct by student in 1..9 tries\n".
+    if(!$found) {
+        $present = '';
+    }
+
+    return $present.'
'."\n";; +} + +sub CreateForm { + my ($CacheData)=@_; + my $OpSel1=''; + my $OpSel2=''; + my $OpSel3=''; + my $Status = $CacheData->{'form.status'}; + if ( $Status eq 'Any' ) { $OpSel3='selected'; } + elsif ($Status eq 'Expired' ) { $OpSel2 = 'selected'; } + else { $OpSel1 = 'selected'; } + + my $Ptr .= ''."\n"; + $Ptr .= '
'; + $present .= ''; + $present .= ''; + $present .= ''; + $present .= '
'; + $Ptr .= ''; + + 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

"); -# ------------------------------- This is going to take a while, produce output - $r->rflush(); + " x: excused". + "

"; + return $Str; +} - my $cid=$ENV{'request.course.id'}; - my $ChartDB = "/home/httpd/perl/tmp/$ENV{'user.name'}". - "_$ENV{'user.domain'}_$cid\_chart.db"; +sub StartDocument { + my $Str = ''; + $Str .= ''; + $Str .= ''; + $Str .= 'LON-CAPA Assessment Chart'; + $Str .= ''; + $Str .= ''; + $Str .= ''; + $Str .= '

Assessment Chart

'; + $Str .= '

'.$ENV{'course.'.$ENV{'request.course.id'}.'.description'}; + $Str .= '

'; - if ((-e "$ChartDB") && ($ENV{'form.sort'} ne 'Recalculate Chart')) { - if (tie(%CachData,'GDBM_File',"$ChartDB",&GDBM_READER,0640)) { - &CacheChart(); - } - else { - $r->print("Unable to tie hash to db file"); - } + return $Str; +} + +# ----- END FORMAT PRINT DATA ------------------------------------------ + +# ----- DOWNLOAD INFORMATION ------------------------------------------- + +sub DownloadPrerequisiteData { + my ($courseID, $c)=@_; + my ($courseDomain,$courseNumber)=split(/\_/,$courseID); + + my %classlist=&Apache::lonnet::dump('classlist',$courseDomain, + $courseNumber); + my ($checkForError)=keys (%classlist); + if($checkForError =~ /^(con_lost|error|no_such_host)/i) { + return \%classlist; } - else { - if (tie(%CachData,'GDBM_File',$ChartDB,&GDBM_WRCREAT,0640)) { - foreach (keys %CachData) {delete $CachData{$_};} - &BuildChart(); - } - else { - $r->print("Unable to tie hash to db file"); - } + + foreach my $name (keys(%classlist)) { + if($c->aborted()) { + $classlist{'error'}='aborted'; + return \%classlist; + } + + my ($studentName,$studentDomain) = split(/\:/,$name); + # Download student environment data, specifically the full name and id. + my %studentInformation=&Apache::lonnet::get('environment', + ['lastname','generation', + 'firstname','middlename', + 'id'], + $studentDomain, + $studentName); + $classlist{$name.':studentInformation'}=\%studentInformation; + + if($c->aborted()) { + $classlist{'error'}='aborted'; + return \%classlist; + } + + #Section + my %section=&Apache::lonnet::dump('roles',$studentDomain,$studentName); + $classlist{$name.':section'}=\%section; } - untie(%CachData); + + return \%classlist; } -# ================================================================ Main Handler +sub DownloadStudentCourseInformation { + my ($name,$courseID)=@_; + my ($studentName,$studentDomain) = split(/\:/,$name); + + # Download student course data + my %courseData=&Apache::lonnet::dump($courseID,$studentDomain, + $studentName); + return \%courseData; +} -sub handler { - $r=shift; - if (&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'})) { -# ------------------------------------------- Set document type for header only - if ($r->header_only) { - if ($ENV{'browser.mathml'}) { - $r->content_type('text/xml'); +# ----- 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}}; + + # Mark sequence as containing problems. If it doesn't, then + # it will be removed when processing for this sequence is + # complete. This allows the problems in a sequence + # to be outputed before problems in the subsequences + if(!defined($CacheData{'orderedSequences'})) { + $CacheData{'orderedSequences'}=$currentSequence; + } else { + $CacheData{'orderedSequences'}.=':'.$currentSequence; + } + + $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 { - $r->content_type('text/html'); + $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; + } + } } - &Apache::loncommon::no_cache($r); - $r->send_http_header; - return OK; - } - - my $requrl=$r->uri; -# ----------------------------------------------------------------- Tie db file - if ($ENV{'request.course.fn'}) { - my $fn=$ENV{'request.course.fn'}; - if (-e "$fn.db") { - if (tie(%hash,'GDBM_File',"$fn.db",&GDBM_READER,0640)) { -# ------------------------------------------------------------------- Hash tied -# ---------------------------------------------------------------- Send headers - $r->content_type('text/html'); - $r->send_http_header; - &Start(); - $r->print(''); -# ------------------------------------------------------------- End render page + } + + #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 + $CacheData{$currentSequence.':title'}= + $hash{'title_'.$currentResourceID}; + + my $totalProblems=0; + foreach my $currentProblem (split(/\:/, + $CacheData{$currentSequence. + ':problems'})) { + foreach (split(/\:/,$CacheData{$currentSequence.':'. + $currentProblem. + ':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 { - $r->content_type('text/html'); - $r->send_http_header; - $r->print('Coursemap undefined.'); + $CacheData{$currentSequence.':columnWidth'}= + (scalar @titleLength); } -# ------------------------------------------------------------------ Untie hash - unless (untie(%hash)) { - &Apache::lonnet::logthis("WARNING: ". - "Could not untie coursemap $fn (browse)."); + } else { + $CacheData{'orderedSequences'}=~s/$currentSequence//; + $CacheData{'orderedSequences'}=~s/::/:/g; + $CacheData{'orderedSequences'}=~s/^:|:$//g; + } + + $currentSequence=pop(@sequences); + if($currentSequence eq $topLevelSequenceNumber) { + last; + } + } + + # 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, $CacheData->{'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/ || + $name eq '') { + next; + } + if($c->aborted()) { + last; + } + push(@names,$name); + &ProcessStudentInformation( + \%CacheData, + $classlist->{$name.':studentInformation'}, + $classlist->{$name.':section'}, + $classlist->{$name}, + $name,$courseID,$c); + } + + # Time of download + $CacheData{'time'}=localtime(); + 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); + } -# -------------------------------------------------------------------- All done - return OK; -# ----------------------------------------------- Errors, hash could no be tied + 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 { + if($firstname ne '') { + $Str .= $firstname.' '; + } + if($middlename ne '') { + $Str .= $middlename.' '; + } + if($generation ne '') { + $Str .= $generation; } else { - $ENV{'user.error.msg'}="$requrl:bre:0:0:Course not initialized"; - return HTTP_NOT_ACCEPTABLE; + chop($Str); + } + } + + return $Str; +} + +sub SortStudents { + my ($students,$CacheData)=@_; + + my @sorted1Students=(); + foreach (@$students) { + my ($end,$start)=split(/\:/,$CacheData->{$_.':date'}); + my $active=1; + my $now=time; + my $Status=$CacheData->{'form.status'}; + $Status = ($Status) ? $Status : 'Active'; + if((($end) && $now > $end) && (($Status eq 'Active'))) { + $active=0; + } + if(($Status eq 'Expired') && ($end == 0 || $now < $end)) { + $active=0; + } + if($active) { + push(@sorted1Students, $_); + } + } + + my $Pos = $CacheData->{'form.sort'}; + my %sortData; + if($Pos eq 'Last Name') { + for(my $index=0; $index{$sorted1Students[$index].':fullname'}}= + $sorted1Students[$index]; + } + } elsif($Pos eq 'Section') { + for(my $index=0; $index{$sorted1Students[$index].':section'}. + $sorted1Students[$index]}=$sorted1Students[$index]; } } else { - $ENV{'user.error.msg'}= - $r->uri.":vgr:0:0:Cannot view grades for complete course"; - return HTTP_NOT_ACCEPTABLE; + # Sort by user name + for(my $index=0; $index= 10) { + return -1; + } + + untie(%testData); + return $isCached; +} +sub ExtractStudentData { + my ($courseData, $name, $ChartDB)=@_; + my %CacheData; + if(tie(%CacheData,'GDBM_File',$ChartDB,&GDBM_WRCREAT,0640)) { + my ($checkForError) = keys(%$courseData); + if($checkForError =~ /^(con_lost|error|no_such_host)/i) { + $CacheData{$name.':error'}='Could not download course data.'; + } else { + foreach my $key (keys (%$courseData)) { + $CacheData{$name.':'.$key}=$courseData->{$key}; + } + if(defined($CacheData{'NamesOfStudents'})) { + $CacheData{'NamesOfStudents'}.=':::'.$name; + } else { + $CacheData{'NamesOfStudents'}=$name; + } + } + untie(%CacheData); + } + return; +} +sub ShouldShowColumn { + my ($cache,$test)=@_; + if($cache->{'form.reset'} eq 'true') { + return 1; + } + + my $headings=$cache->{'form.headings'}; + my $sequences=$cache->{'form.sequences'}; + if($headings eq 'ALLHEADINGS' || $sequences eq 'ALLSEQUENCES' || + $headings=~/$test/ || $sequences=~/$test/) { + return 1; + } + +# my $reselected=$cache->{'form.reselect'}; +# if($reselected=~/$test/) { +# return 1; +# } + + return 0; +} + +sub ProcessFormData { + my ($ChartDB)=@_; + my %CacheData; + + if(tie(%CacheData,'GDBM_File',$ChartDB,&GDBM_WRCREAT,0640)) { + if(defined($ENV{'form.sort'})) { + $CacheData{'form.sort'}=$ENV{'form.sort'}; + } elsif(!defined($CacheData{'form.sort'})) { + $CacheData{'form.sort'}='username'; + } + + # Ignore $ENV{'form.refresh'} + # Ignore $ENV{'form.recalculate'} + + if(defined($ENV{'form.status'})) { + $CacheData{'form.status'}=$ENV{'form.status'}; + } elsif(!defined($CacheData{'form.status'})) { + $CacheData{'form.status'}='Active'; + } + + my @headings=(); + my @sequences=(); + my $found=0; + foreach (keys(%ENV)) { + if(/form\.heading/) { + $found++; + push(@headings, $_); + } elsif(/form\.sequence/) { + $found++; + push(@sequences, $_); + } elsif(/form\./) { + $found++; + } + } + + if($found) { + $CacheData{'form.headings'}=join(":::",@headings); + $CacheData{'form.sequences'}=join(":::",@sequences); + } + + if(defined($ENV{'form.reselect'})) { + my @reselected = (ref($ENV{'form.reselect'}) ? + @{$ENV{'form.reselect'}} + : ($ENV{'form.reselect'})); + foreach (@reselected) { + if(/heading/) { + $CacheData{'form.headings'}.=":::".$_; + } elsif(/sequence/) { + $CacheData{'form.sequences'}.=":::".$_; + } + } + } + + if(defined($ENV{'form.reset'})) { + $CacheData{'form.reset'}='true'; + $CacheData{'form.status'}='Active'; + $CacheData{'form.sort'}='username'; + $CacheData{'form.headings'}='ALLHEADINGS'; + $CacheData{'form.sequences'}='ALLSEQUENCES'; + } else { + $CacheData{'form.reset'}='false'; + } + + untie(%CacheData); + } + + return; +} + +# ----- END HELPER FUNCTIONS -------------------------------------------- + +sub BuildChart { + my ($r)=@_; + my $c = $r->connection; + + # Start the lonchart document + $r->content_type('text/html'); + $r->send_http_header; + $r->print(&StartDocument()); + $r->rflush(); + + # Test for access to the CacheData + my $isCached=0; + my $cid=$ENV{'request.course.id'}; + my $ChartDB = "/home/httpd/perl/tmp/$ENV{'user.name'}". + "_$ENV{'user.domain'}_$cid\_chart.db"; + + $isCached=&TestCacheData($ChartDB); + if($isCached < 0) { + $r->print("Unable to tie hash to db file"); + $r->rflush(); + return; + } + &ProcessFormData($ChartDB); + + # Download class list information if not using cached data + my %CacheData; + my @students=(); + my @studentInformation=('username','domain','section','id','fullname'); + my @headings=('User Name','Domain','Section','PID','Full Name'); + my $spacePadding=' '; + if(!$isCached) { + my $processTopResourceMapReturn=&ProcessTopResourceMap($ChartDB,$c); + if($processTopResourceMapReturn ne 'OK') { + $r->print($processTopResourceMapReturn); + return; + } + if($c->aborted()) { return; } + my $classlist=&DownloadPrerequisiteData($cid, $c); + my ($checkForError)=keys(%$classlist); + if($checkForError =~ /^(con_lost|error|no_such_host)/i || + defined($classlist->{'error'})) { + return; + } + if($c->aborted()) { return; } + @students=&ProcessClassList($classlist,$cid,$ChartDB,$c); + if($c->aborted()) { return; } + &SpaceColumns(\@students,\@studentInformation,\@headings, + $ChartDB); + if($c->aborted()) { return; } + } else { + if(!$c->aborted() && tie(%CacheData,'GDBM_File',$ChartDB, + &GDBM_READER,0640)) { + @students=split(/:::/,$CacheData{'NamesOfStudents'}); + } + } + + # Sort students and print out table desciptive data + if(tie(%CacheData,'GDBM_File',$ChartDB,&GDBM_READER,0640)) { + if(!$c->aborted()) { @students=&SortStudents(\@students,\%CacheData); } + my $downloadTime=0; + if(defined($CacheData{'time'})) { $downloadTime=$CacheData{'time'}; } + else { $downloadTime=localtime(); } + if(!$c->aborted()) { $r->print('

'.$downloadTime.'

'); } + if(!$c->aborted()) { $r->print('

'.(scalar @students). + ' students

'); } + if(!$c->aborted()) { $r->rflush(); } + if(!$c->aborted()) { $r->print(&CreateLegend()); } + if(!$c->aborted()) { $r->print(&CreateForm(\%CacheData)); } + if(!$c->aborted()) { $r->print(&CreateColumnSelectionBox( + \%CacheData, + \@studentInformation, + \@headings, + $spacePadding)); } + if(!$c->aborted()) { $r->print(&CreateColumnSelectors( + \%CacheData, + \@studentInformation, + \@headings, + $spacePadding)); } + if(!$c->aborted()) { $r->print(&CreateTableHeadings( + \%CacheData, + \@studentInformation, + \@headings, + $spacePadding)); } + if(!$c->aborted()) { $r->rflush(); } + untie(%CacheData); + } else { + $r->print("Init2: Unable to tie hash to db file"); + return; + } + + my @updateStudentList = (); + my $courseData; + $r->print('
');
+    foreach (@students) {
+        if($c->aborted()) {
+            last;
+        }
+
+        if(!$isCached) {
+            $courseData=&DownloadStudentCourseInformation($_, $cid);
+            if($c->aborted()) { last; }
+            push(@updateStudentList, $_);
+            &ExtractStudentData($courseData, $_, $ChartDB);
+        }
+        $r->print(&FormatStudentData($_, $cid, \@studentInformation,
+                                     $spacePadding, $ChartDB));
+        $r->rflush();
+    }
+
+    if(!$isCached && tie(%CacheData,'GDBM_File',$ChartDB,&GDBM_WRCREAT,0640)) {
+        $CacheData{'NamesOfStudents'}=join(":::", @updateStudentList);
+#		    $CacheData{'NamesOfStudents'}=
+#		            &Apache::lonnet::arrayref2str(\@updateStudentList);
+        untie(%CacheData);
+    }
+
+    $r->print('
'); + $r->rflush(); + + return; +} + +# ================================================================ Main Handler + +sub handler { + my $r=shift; +# $jr=$r; + 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; + } + + &BuildChart($r); + + return OK; +} +1; +__END__
'; + $Ptr .= ''; + $Ptr .= ''. + ' '."\n"; + $Ptr .= '