--- loncom/interface/lonnavmaps.pm 2002/11/16 22:45:22 1.112 +++ loncom/interface/lonnavmaps.pm 2002/11/26 16:25:36 1.116 @@ -2,7 +2,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.112 2002/11/16 22:45:22 bowersj2 Exp $ +# $Id: lonnavmaps.pm,v 1.116 2002/11/26 16:25:36 bowersj2 Exp $ # # Copyright Michigan State University Board of Trustees # @@ -171,6 +171,8 @@ sub handler { # is not yet done and due in less then 24 hours my $hurryUpColor = "#FF0000"; + # Keep these mappings in sync with lonquickgrades, which uses the colors + # instead of the icons. my %statusIconMap = ( $res->NETWORK_FAILURE => '', $res->NOTHING_SET => '', @@ -206,7 +208,6 @@ sub handler { # Is this a new-style course? If so, we want to suppress showing the top-level # maps in their own folders, in favor of "inlining" them. my $topResource = $navmap->getById("0.0"); - my $inlineTopLevelMaps = $topResource->src() =~ m|^/uploaded/.*default\.sequence$|; # Begin the HTML table # four cols: resource + indent, chat+feedback, icon, text string @@ -229,8 +230,10 @@ sub handler { $mapIterator->next(); # discard the first BEGIN_MAP my $curRes = $mapIterator->next(); my $counter = 0; - - while ($depth > 0) { + + # We only need to do this if we need to open the maps to show the + # current position + while ($depth > 0 && !$ENV{'form.alreadyHere'}) { if ($curRes == $mapIterator->BEGIN_MAP()) { $depth++; } if ($curRes == $mapIterator->END_MAP()) { $depth--; } @@ -259,17 +262,6 @@ sub handler { $ENV{'form.alreadyHere'} = 1; } - # Preprocessing: If we're inlining nav maps into the top-level display, - # make sure we show this map! - if ($inlineTopLevelMaps && ref($curRes) && $curRes->is_map && - scalar(@{$mapStack}) == 1) { - if ($condition) { - undef $filterHash{$curRes->map_pc()}; - } else { - $filterHash{$curRes->map_pc()} = 1; - } - } - $curRes = $mapIterator->next(); } @@ -288,7 +280,6 @@ sub handler { $condition); $mapIterator->next(); $curRes = $mapIterator->next(); - my $deltadepth = 0; $depth = 1; my @backgroundColors = ("#FFFFFF", "#F6F6F6"); @@ -302,15 +293,6 @@ sub handler { } while ($depth > 0) { - # If this is an inlined map, cancel the shift to the right, - # which has the effect of making the map look inlined - if ($inlineTopLevelMaps && scalar(@{$mapIterator->getStack()}) == 1 && - ref($curRes) && $curRes->is_map()) { - $deltadepth = -1; - $curRes = $mapIterator->next(); - next; - } - if ($curRes == $mapIterator->BEGIN_MAP() || $curRes == $mapIterator->BEGIN_BRANCH()) { $indentLevel++; @@ -327,9 +309,6 @@ sub handler { if (ref($curRes)) { $counter++; } - if ($depth == 1) { $deltadepth = 0; } # we're done shifting, because we're - # out of the inlined map - # Is this resource being ignored because it is in a random-out # map and it was not selected? if (ref($curRes) && !advancedUser() && $curRes->randomout()) { @@ -337,7 +316,17 @@ sub handler { next; # if yes, then just ignore this resource } - if (ref($curRes) && $curRes->src()) { + if (ref($curRes)) { + + my $deltalevel = $isNewBranch? 1 : 0; # reserves space for branch icon + + if ($indentLevel - $deltalevel < 0) { + # If this would be at a negative depth (top-level maps in + # new-style courses, we want to suppress their title display) + # then ignore it. + $curRes = $mapIterator->next(); + next; + } # Step one: Decide which parts to show my @parts = @{$curRes->parts()}; @@ -424,7 +413,6 @@ sub handler { # For each part we intend to display... foreach my $part (@parts) { - my $deltalevel = 0; # for inserting the branch icon my $nonLinkedText = ""; # unlinked stuff after title my $stack = $mapIterator->getStack(); @@ -446,7 +434,6 @@ sub handler { if ($isNewBranch) { $newBranchText = ""; $isNewBranch = 0; - $deltalevel = 1; # reserves space for the branch icon } # links to open and close the folders @@ -482,27 +469,13 @@ sub handler { my $colorizer = ""; my $color; if ($curRes->is_problem()) { - my $status = $curRes->status($part); - $color = $colormap{$status}; + $color = $colormap{$curRes->status}; - # Special case in the navmaps: If in less then - # 24 hours, give it a bit of urgency - if (($status == $curRes->OPEN() || $status == $curRes->ATTEMPTED() || - $status == $curRes->TRIES_LEFT()) - && $curRes->duedate() && - $curRes->duedate() < time()+(24*60*60) && - $curRes->duedate() > time()) { - $color = $hurryUpColor; - } - # Special case: If this is the last try, and there is - # more then one available, and it's not due yet, give a bit of urgency - my $tries = $curRes->tries($part); - my $maxtries = $curRes->maxtries($part); - if ($tries && $maxtries && $maxtries > 1 && - $maxtries - $tries == 1 && $curRes->duedate() && - $curRes->duedate() > time()) { + if (dueInLessThen24Hours($curRes, $part) || + lastTry($curRes, $part)) { $color = $hurryUpColor; } + if ($color ne "") { $colorizer = "bgcolor=\"$color\""; } @@ -516,7 +489,7 @@ sub handler { my $backgroundColor = $backgroundColors[$rowNum % scalar(@backgroundColors)]; # FIRST COL: The resource indentation, branch icon, name, and anchor - $r->print(" \n"); + $r->print(" \n"); # Print the anchor if necessary if ($counter == $currentUrlIndex - $currentUrlDelta) { @@ -524,7 +497,7 @@ sub handler { } # print indentation - for (my $i = 0; $i < $indentLevel - $deltalevel + $deltadepth; $i++) { + for (my $i = 0; $i < $indentLevel - $deltalevel; $i++) { $r->print($indentString); } @@ -555,6 +528,8 @@ sub handler { 'Host down')); } + $r->print("\n"); + # SECOND COL: Is there text, feedback, errors?? my $discussionHTML = ""; my $feedbackHTML = ""; @@ -613,6 +588,9 @@ sub handler { } $r->print(" \n"); + + if (!($counter % 20)) { $r->rflush(); } + if ($counter == 2) { $r->rflush(); } } } $curRes = $mapIterator->next(); @@ -730,6 +708,33 @@ sub getDescription { } } +# Convenience function, so others can use it: Is the problem due in less then +# 24 hours, and still can be done? + +sub dueInLessThen24Hours { + my $res = shift; + my $part = shift; + my $status = $res->status($part); + + return ($status == $res->OPEN() || $status == $res->ATTEMPTED() || + $status == $res->TRIES_LEFT()) && + $res->duedate() && $res->duedate() < time()+(24*60*60) && + $res->duedate() > time(); +} + +# Convenience function, so others can use it: Is there only one try remaining for the +# part, with more then one try to begin with, not due yet and still can be done? +sub lastTry { + my $res = shift; + my $part = shift; + + my $tries = $res->tries($part); + my $maxtries = $res->maxtries($part); + return $tries && $maxtries && $maxtries > 1 && + $maxtries - $tries == 1 && $res->duedate() && + $res->duedate() > time(); +} + # This puts a human-readable name on the ENV variable. sub advancedUser { return $ENV{'user.adv'}; @@ -895,7 +900,6 @@ sub new { &GDBM_READER(), 0640))) { return undef; } - $self->{NAV_HASH} = \%navmaphash; my %parmhash; if (!(tie(%parmhash, 'GDBM_File', $self->{PARM_HASH_FILE}, @@ -904,10 +908,16 @@ sub new { untie $self->{PARM_HASH}; return undef; } - $self->{PARM_HASH} = \%parmhash; - $self->{HASH_TIED} = 1; + + # Now copy the hashes for speed (?) + my %realnav; my %realparm; + foreach (%navmaphash) { $realnav{$_} = $navmaphash{$_}; } + foreach (%parmhash) { $realparm{$_} = $navmaphash{$_}; } + $self->{NAV_HASH} = \%realnav; + $self->{PARM_HASH} = \%realparm; bless($self); + $self->untieHashes(); return $self; } @@ -1021,13 +1031,20 @@ sub init { $self->{PARM_CACHE} = {}; } +# Internal function: Takes a key to look up in the nav hash and implements internal +# memory caching of that key. +sub navhash { + my $self = shift; my $key = shift; + return $self->{NAV_HASH}->{$key}; +} + # Checks to see if coursemap is defined, matching test in old lonnavmaps sub courseMapDefined { my $self = shift; my $uri = &Apache::lonnet::clutter($ENV{'request.course.uri'}); - my $firstres = $self->{NAV_HASH}->{"map_start_$uri"}; - my $lastres = $self->{NAV_HASH}->{"map_finish_$uri"}; + my $firstres = $self->navhash("map_start_$uri"); + my $lastres = $self->navhash("map_finish_$uri"); return $firstres && $lastres; } @@ -1107,8 +1124,8 @@ sub getById { sub firstResource { my $self = shift; - my $firstResource = $self->{NAV_HASH}->{'map_start_' . - &Apache::lonnet::clutter($ENV{'request.course.uri'})}; + my $firstResource = $self->navhash('map_start_' . + &Apache::lonnet::clutter($ENV{'request.course.uri'})); return $self->getById($firstResource); } @@ -1120,8 +1137,8 @@ sub firstResource { sub finishResource { my $self = shift; - my $firstResource = $self->{NAV_HASH}->{'map_finish_' . - &Apache::lonnet::clutter($ENV{'request.course.uri'})}; + my $firstResource = $self->navhash('map_finish_' . + &Apache::lonnet::clutter($ENV{'request.course.uri'})); return $self->getById($firstResource); } @@ -1248,7 +1265,7 @@ getIterator behaves as follows: =over 4 -=item * B(firstResource, finishResource, filterHash, condition): All parameters are optional. firstResource is a resource reference corresponding to where the iterator should start. It defaults to navmap->firstResource() for the corresponding nav map. finishResource corresponds to where you want the iterator to end, defaulting to navmap->finishResource(). filterHash is a hash used as a set containing strings representing the resource IDs, defaulting to empty. Condition is a 1 or 0 that sets what to do with the filter hash: If a 0, then only resource that exist IN the filterHash will be recursed on. If it is a 1, only resources NOT in the filterHash will be recursed on. Defaults to 0. +=item * B(firstResource, finishResource, filterHash, condition, forceTop): All parameters are optional. firstResource is a resource reference corresponding to where the iterator should start. It defaults to navmap->firstResource() for the corresponding nav map. finishResource corresponds to where you want the iterator to end, defaulting to navmap->finishResource(). filterHash is a hash used as a set containing strings representing the resource IDs, defaulting to empty. Condition is a 1 or 0 that sets what to do with the filter hash: If a 0, then only resource that exist IN the filterHash will be recursed on. If it is a 1, only resources NOT in the filterHash will be recursed on. Defaults to 0. forceTop is a boolean value. If it is false (default), the iterator will only return the first level of map that is not just a single, 'redirecting' map. If true, the iterator will return all information, starting with the top-level map, regardless of content. Thus, by default, only top-level resources will be shown. Change the condition to a 1 without changing the hash, and all resources will be shown. Changing the condition to 1 and including some values in the hash will allow you to selectively suppress parts of the navmap, while leaving it on 0 and adding things to the hash will allow you to selectively add parts of the nav map. See the handler code for examples. @@ -1268,6 +1285,8 @@ The iterator will return either a refere The tokens are retreivable via methods on the iterator object, i.e., $iterator->END_MAP. +Maps can contain empty resources. The iterator will automatically skip over such resources, but will still treat the structure correctly. Thus, a complicated map with several branches, but consisting entirely of empty resources except for one beginning or ending resource, will cause a lot of BRANCH_STARTs and BRANCH_ENDs, but only one resource will be returned. + =back =cut @@ -1318,6 +1337,9 @@ sub new { if (!defined($self->{ALREADY_SEEN})) { $self->{ALREADY_SEEN} = {} }; $self->{CONDITION} = shift; + # Do we want to automatically follow "redirection" maps? + $self->{FORCE_TOP} = shift; + # Now, we need to pre-process the map, by walking forward and backward # over the parts of the map we're going to look at. @@ -1333,6 +1355,11 @@ sub new { my $maxDepth = 0; # tracks max depth + # If there is only one resource in this map, and it's a map, we + # want to remember that, so the user can ask for the first map + # that isn't just a redirector. + my $resource; my $resourceCount = 0; + # **1** foreach my $pass (@iterations) { @@ -1356,16 +1383,18 @@ sub new { if ($curRes == $iterator->END_MAP()) { $depth--; } if (ref($curRes)) { + # If there's only one resource, this will save it + if($direction == FORWARD) { $resource = $curRes; $resourceCount++; } my $resultingVal = $curRes->{DATA}->{$valName}; my $nextResources = $curRes->$nextResourceMethod(); - my $resourceCount = scalar(@{$nextResources}); + my $nextCount = scalar(@{$nextResources}); - if ($resourceCount == 1) { # **3** + if ($nextCount == 1) { # **3** my $current = $nextResources->[0]->{DATA}->{$valName} || 999999999; $nextResources->[0]->{DATA}->{$valName} = min($resultingVal, $current); } - if ($resourceCount > 1) { # **4** + if ($nextCount > 1) { # **4** foreach my $res (@{$nextResources}) { my $current = $res->{DATA}->{$valName} || 999999999; $res->{DATA}->{$valName} = min($current, $resultingVal + 1); @@ -1385,6 +1414,18 @@ sub new { } } + # Check: Was this only one resource, a map? + if ($resourceCount == 1 && $resource->is_map() && !$self->{FORCE_TOP}) { + my $firstResource = $resource->map_start(); + my $finishResource = $resource->map_finish(); + return + Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource, + $finishResource, $self->{FILTER}, + $self->{ALREADY_SEEN}, + $self->{CONDITION}, 0); + + } + # Set up some bookkeeping information. $self->{CURRENT_DEPTH} = 0; $self->{MAX_DEPTH} = $maxDepth; @@ -1534,6 +1575,11 @@ sub next { $self->{ALREADY_SEEN}, $self->{CONDITION}); } + # If this is a blank resource, don't actually return it. + if (!$self->{HERE}->src()) { + return $self->next(); + } + return $self->{HERE}; } @@ -1717,6 +1763,11 @@ sub next { $self->{CONDITION}, $self->{DIRECTION}); } + # If this is a blank resource, ignore it. + if (!$self->{HERE}->src()) { + return $self->next(); + } + return $self->{HERE}; } @@ -1780,7 +1831,7 @@ sub navHash { my $self = shift; my $param = shift; my $id = shift; - return $self->{NAV_MAP}->{NAV_HASH}->{$param . ($id?$self->{ID}:"")}; + return $self->{NAV_MAP}->navhash($param . ($id?$self->{ID}:"")); } =pod @@ -2459,7 +2510,11 @@ sub getNext { # Don't remember it if the student doesn't have browse priviledges # future note: this may properly belong in the client of the resource - my $browsePriv = &Apache::lonnet::allowed('bre', $self->src); + my $browsePriv = $self->{BROWSE_PRIV}; + if (!defined($browsePriv)) { + $browsePriv = &Apache::lonnet::allowed('bre', $self->src); + $self->{BROWSE_PRIV} = $browsePriv; + } if (!($browsePriv ne '2' && $browsePriv ne 'F')) { push @branches, $next; } @@ -2478,7 +2533,11 @@ sub getPrevious { # Don't remember it if the student doesn't have browse priviledges # future note: this may properly belong in the client of the resource - my $browsePriv = &Apache::lonnet::allowed('bre', $self->src); + my $browsePriv = $self->{BROWSE_PRIV}; + if (!defined($browsePriv)) { + $browsePriv = &Apache::lonnet::allowed('bre', $self->src); + $self->{BROWSE_PRIV} = $browsePriv; + } if (!($browsePriv ne '2' && $browsePriv ne 'F')) { push @branches, $prev; }