--- loncom/interface/lonnavmaps.pm 2002/11/15 17:30:33 1.107 +++ loncom/interface/lonnavmaps.pm 2003/02/01 19:55:00 1.129.2.1 @@ -2,7 +2,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.107 2002/11/15 17:30:33 bowersj2 Exp $ +# $Id: lonnavmaps.pm,v 1.129.2.1 2003/02/01 19:55:00 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -48,6 +48,11 @@ use POSIX qw (floor strftime); sub handler { my $r = shift; + real_handler($r); +} + +sub real_handler { + my $r = shift; &Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING}); @@ -83,11 +88,19 @@ sub handler { return HTTP_NOT_ACCEPTABLE; } + $r->print("\n"); + $r->print("Navigate Course Contents"); + # Header - $r->print(&Apache::loncommon::bodytag('Navigate Course Map','', + $r->print(&Apache::loncommon::bodytag('Navigate Course Contents','', '')); $r->print(''); + $r->rflush(); + + # Now that we've displayed some stuff to the user, init the navmap + $navmap->init(); + $r->print(''); my $date=localtime; $r->print(''); @@ -113,18 +126,63 @@ sub handler { $condition = 1; } + # Determine where the "here" marker is and where the screen jumps to. + my $SYMB = 1; my $URL = 2; my $NOTHING = 3; # symbolic constants + my $hereType; # the type of marker, $SYMB, $URL, or $NOTHING + my $here; # the actual URL or SYMB for the here marker + my $jumpType; # The type of the thing we have a jump for, $SYMB or $URL + my $jump; # the SYMB/URL of the resource we need to jump to + + if ( $ENV{'form.alreadyHere'} ) { # we came from a user's manipulation of the nav page + # If this is a click on a folder or something, we want to preserve the "here" + # from the querystring, and get the new "jump" marker + $hereType = $ENV{'form.hereType'}; + $here = $ENV{'form.here'}; + $jumpType = $ENV{'form.jumpType'} || $NOTHING; + $jump = $ENV{'form.jump'}; + } else { # the user is visiting the nav map from the remote + # We're coming from the remote. We have either a url, a symb, or nothing, + # and we need to figure out what. + # Preference: Symb + + if ($ENV{'form.symb'}) { + $hereType = $jumpType = $SYMB; + $here = $jump = $ENV{'form.symb'}; + } elsif ($ENV{'form.postdata'}) { + # couldn't find a symb, is there a URL? + my $currenturl = $ENV{'form.postdata'}; + $currenturl=~s/^http\:\/\///; + $currenturl=~s/^[^\/]+//; + + $hereType = $jumpType = $URL; + $here = $jump = $currenturl; + } else { + # Nothing + $hereType = $jumpType = $NOTHING; + } + } + + + # alreadyHere allows us to only open the maps necessary to view + # the current location once, while at the same time remembering + # the current location. Without that check, the user would never + # be able to close those maps; the user would close it, and the + # currenturl scan would re-open it. + my $queryAdd = "&alreadyHere=1"; + if ($condition) { - $r->print('Close All Folders'); + $r->print("Close All Folders"); } else { - $r->print('Open All Folders'); + $r->print("Open All Folders"); } $r->print('
 '); $r->rflush(); - # Now that we've displayed some stuff to the user, init the navmap - $navmap->init(); - # Check that it's defined if (!($navmap->courseMapDefined())) { $r->print('Coursemap undefined.' . @@ -143,10 +201,10 @@ sub handler { my %colormap = ( $res->NETWORK_FAILURE => '', $res->CORRECT => '', - $res->EXCUSED => '#BBBBFF', + $res->EXCUSED => '#3333FF', $res->PAST_DUE_ANSWER_LATER => '', $res->PAST_DUE_NO_ANSWER => '', - $res->ANSWER_OPEN => '#CCFFCC', + $res->ANSWER_OPEN => '#006600', $res->OPEN_LATER => '', $res->TRIES_LEFT => '', $res->INCORRECT => '', @@ -156,6 +214,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 => '', @@ -191,19 +251,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$|; - - my $currenturl = $ENV{'form.postdata'}; - $currenturl=~s/^http\:\/\///; - $currenturl=~s/^[^\/]+//; - - # alreadyHere allows us to only open the maps necessary to view - # the current location once, while at the same time remembering - # the current location. Without that check, the user would never - # be able to close those maps; the user would close it, and the - # currenturl scan would re-open it. - my $queryAdd = "postdata=" . &Apache::lonnet::escape($currenturl) . - "&alreadyHere=1"; # Begin the HTML table # four cols: resource + indent, chat+feedback, icon, text string @@ -215,35 +262,31 @@ sub handler { # Here's a simple example of the iterator. # Preprocess the map: Look for current URL, force inlined maps to display - my $mapIterator = $navmap->getIterator(undef, undef, \%filterHash, 1); + my $mapIterator = $navmap->getIterator(undef, undef, undef, 1); my $found = 0; my $depth = 1; - my $currentUrlIndex = 0; # keeps track of when the current resource is found, + my $currentJumpIndex = 0; # keeps track of when the current resource is found, # so we can back up a few and put the anchor above the # current resource - my $currentUrlDelta = 5; # change this to change how many resources are displayed + my $currentJumpDelta = 2; # change this to change how many resources are displayed # before the current resource when using #current $mapIterator->next(); # discard the first BEGIN_MAP my $curRes = $mapIterator->next(); my $counter = 0; - - while ($depth > 0) { + my $foundJump = ($jumpType == $NOTHING); # look for jump point if we have one + my $looped = 0; + + # We only need to do this if we need to open the maps to show the + # current position. This will change the counter so we can't count + # for the jump marker with this loop. + while ($depth > 0 && !$ENV{'form.alreadyHere'}) { if ($curRes == $mapIterator->BEGIN_MAP()) { $depth++; } if ($curRes == $mapIterator->END_MAP()) { $depth--; } - if (ref($curRes)) { $counter++; } - - my $mapStack = $mapIterator->getStack(); - if ($currenturl && !$ENV{'form.alreadyHere'} && ref($curRes) && - $curRes->src() eq $currenturl) { - # If this is the correct resource, be sure to - # show it by making sure the containing maps - # are open. - - # This is why we have to use the main iterator instead of the - # potentially faster DFS: The count has to be the same, so - # the order has to be the same, which DFS won't give us. - $currentUrlIndex = $counter; + if (ref($curRes) && !$ENV{'form.alreadyHere'} && + ($hereType == $SYMB && $curRes->symb() eq $here) || + (ref($curRes) && $hereType == $URL && $curRes->src() eq $here)) { + my $mapStack = $mapIterator->getStack(); # Ensure the parent maps are open for my $map (@{$mapStack}) { @@ -255,16 +298,33 @@ 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; - } + $looped = 1; + + $curRes = $mapIterator->next(); + } + + $mapIterator = $navmap->getIterator(undef, undef, \%filterHash, 0); + $depth = 1; + $mapIterator->next(); + $curRes = $mapIterator->next(); + + while ($depth > 0 && !$foundJump) { + if ($curRes == $mapIterator->BEGIN_MAP()) { $depth++; } + if ($curRes == $mapIterator->END_MAP()) { $depth--; } + if (ref($curRes)) { $counter++; } + + if (ref($curRes) && + (($jumpType == $SYMB && $curRes->symb() eq $jump) || + ($jumpType == $URL && $curRes->src() eq $jump))) { + # If this is the correct resource, be sure to + # show it by making sure the containing maps + # are open. + + # This is why we have to use the main iterator instead of the + # potentially faster DFS: The count has to be the same, so + # the order has to be the same, which DFS won't give us. + $currentJumpIndex = $counter; + $foundJump = 1; } $curRes = $mapIterator->next(); @@ -278,6 +338,7 @@ sub handler { my $now = time(); my $in24Hours = $now + 24 * 60 * 60; my $displayedHereMarker = 0; + my $displayedJumpMarker = 0; # We know the first thing is a BEGIN_MAP (see "$self->{STARTED}" # code in iterator->next), so ignore the first one @@ -285,7 +346,6 @@ sub handler { $condition); $mapIterator->next(); $curRes = $mapIterator->next(); - my $deltadepth = 0; $depth = 1; my @backgroundColors = ("#FFFFFF", "#F6F6F6"); @@ -293,21 +353,7 @@ sub handler { $counter = 0; - # Print the 'current' anchor here if it would fall off the top - if ($currentUrlIndex - $currentUrlDelta < 0) { - $r->print(''); - } - 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++; @@ -324,9 +370,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()) { @@ -334,7 +377,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()}; @@ -421,7 +474,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(); @@ -436,6 +488,10 @@ sub handler { '"'; my $title = $curRes->compTitle(); + if ($src=~/^\/uploaded\//) { + $nonLinkedText=$title; + $title=''; + } my $partLabel = ""; my $newBranchText = ""; @@ -443,7 +499,6 @@ sub handler { if ($isNewBranch) { $newBranchText = ""; $isNewBranch = 0; - $deltalevel = 1; # reserves space for the branch icon } # links to open and close the folders @@ -471,7 +526,10 @@ sub handler { $linkopen .= ($nowOpen xor $condition) ? addToFilter(\%filterHash, $mapId) : removeFromFilter(\%filterHash, $mapId); - $linkopen .= "&condition=$condition&$queryAdd\">"; + $linkopen .= "&condition=$condition&$queryAdd" . + "&hereType=$hereType&here=" . + Apache::lonnet::escape($here) . "&jumpType=$SYMB&" . + "jump=" . Apache::lonnet::escape($curRes->symb()) ."\">"; $linkclose = ""; } @@ -479,27 +537,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\""; } @@ -513,27 +557,29 @@ sub handler { my $backgroundColor = $backgroundColors[$rowNum % scalar(@backgroundColors)]; # FIRST COL: The resource indentation, branch icon, name, and anchor - $r->print(" \n"); # SECOND COL: Is there text, feedback, errors?? - my $discussionHTML = ""; my $feedbackHTML = ""; + my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = ""; if ($curRes->hasDiscussion()) { $discussionHTML = $linkopen . @@ -574,7 +622,19 @@ sub handler { } } - $r->print(""); + if ($curRes->getErrors()) { + my $errors = $curRes->getErrors(); + foreach (split(/,/, $errors)) { + if ($_) { + $errorHTML .= ' ' + . ''; + } + } + } + + $r->print(""); # Is this the first displayed part of a multi-part problem # that has not been condensed, so we should suppress these two @@ -611,15 +671,25 @@ sub handler { } $r->print(" \n"); + + if (!($counter % 20)) { $r->rflush(); } + if ($counter == 2) { $r->rflush(); } } } $curRes = $mapIterator->next(); } - $r->print("
Key:  
\n"); + $r->print("
\n"); # Print the anchor if necessary - if ($counter == $currentUrlIndex - $currentUrlDelta) { - $r->print(''); + if ($counter == $currentJumpIndex - $currentJumpDelta ) { + $r->print(''); + $displayedJumpMarker = 1; } # print indentation - for (my $i = 0; $i < $indentLevel - $deltalevel + $deltadepth; $i++) { + for (my $i = 0; $i < $indentLevel - $deltalevel; $i++) { $r->print($indentString); } $r->print(" ${newBranchText}${linkopen}$icon${linkclose}\n"); - #$r->print($curRes->awarded($part)); my $curMarkerBegin = ""; my $curMarkerEnd = ""; # Is this the current resource? - if ($curRes->src() eq $currenturl && !$displayedHereMarker) { - $curMarkerBegin = '> '; + if (!$displayedHereMarker && + (($hereType == $SYMB && $curRes->symb eq $here) || + ($hereType == $URL && $curRes->src eq $here))) { + $curMarkerBegin = '> '; $curMarkerEnd = ' <'; $displayedHereMarker = 1; } @@ -548,13 +594,15 @@ sub handler { $r->print(" $curMarkerBegin$title$partLabel $curMarkerEnd $nonLinkedText"); - if ($curRes->{RESOURCE_ERROR}) { - $r->print(&Apache::loncommon::help_open_topic ("Navmap_Host_Down", - 'Host down')); - } + #if ($curRes->{RESOURCE_ERROR}) { + # $r->print(&Apache::loncommon::help_open_topic ("Navmap_Host_Down", + # 'Host down')); + # } + + $r->print("$discussionHTML$feedbackHTML $discussionHTML$feedbackHTML$errorHTML 
"); + $r->print(""); + + # Print out the part that jumps to #curloc if it exists + if ($displayedJumpMarker) { + $r->print(''); + } $navmap->untieHashes(); + $r->print(""); + return OK; } @@ -728,6 +798,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'}; @@ -893,7 +990,6 @@ sub new { &GDBM_READER(), 0640))) { return undef; } - $self->{NAV_HASH} = \%navmaphash; my %parmhash; if (!(tie(%parmhash, 'GDBM_File', $self->{PARM_HASH_FILE}, @@ -902,8 +998,10 @@ sub new { untie $self->{PARM_HASH}; return undef; } - $self->{PARM_HASH} = \%parmhash; + $self->{HASH_TIED} = 1; + $self->{NAV_HASH} = \%navmaphash; + $self->{PARM_HASH} = \%parmhash; bless($self); @@ -976,7 +1074,7 @@ sub init { my %emailstatus = &Apache::lonnet::dump('email_status'); my $logoutTime = $emailstatus{'logout'}; my $courseLeaveTime = $emailstatus{'logout_'.$ENV{'request.course.id'}}; - $self->{LAST_CHECK} = ($courseLeaveTime > $logoutTime ? + $self->{LAST_CHECK} = ($courseLeaveTime < $logoutTime ? $courseLeaveTime : $logoutTime); my %discussiontime = &Apache::lonnet::dump('discussiontimes', $cdom, $cnum); @@ -1019,13 +1117,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; } @@ -1050,18 +1155,19 @@ sub DESTROY { $self->untieHashes(); } -# Private function: Does the given resource (as a symb string) have +# Private method: Does the given resource (as a symb string) have # current discussion? Returns 0 if chat/mail data not extracted. sub hasDiscussion { my $self = shift; my $symb = shift; if (!defined($self->{DISCUSSION_TIME})) { return 0; } + #return defined($self->{DISCUSSION_TIME}->{$symb}); return $self->{DISCUSSION_TIME}->{$symb} > $self->{LAST_CHECK}; } -# Private function: Does the given resource (as a symb string) have +# Private method: Does the given resource (as a symb string) have # current feedback? Returns the string in the feedback hash, which # will be false if it does not exist. sub getFeedback { @@ -1073,6 +1179,15 @@ sub getFeedback { return $self->{FEEDBACK}->{$symb}; } +# Private method: Get the errors for that resource (by source). +sub getErrors { + my $self = shift; + my $src = shift; + + if (!defined($self->{ERROR_MSG})) { return ""; } + return $self->{ERROR_MSG}->{$src}; +} + =pod =item * B(id): Based on the ID of the resource (1.1, 3.2, etc.), get a resource object for that resource. This method, or other methods that use it (as in the resource object) is the only proper way to obtain a resource object. @@ -1105,8 +1220,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); } @@ -1118,8 +1233,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); } @@ -1246,7 +1361,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. @@ -1266,6 +1381,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 @@ -1316,6 +1433,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. @@ -1331,6 +1451,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) { @@ -1354,16 +1479,23 @@ sub new { if ($curRes == $iterator->END_MAP()) { $depth--; } if (ref($curRes)) { + # If there's only one resource, this will save it + # we have to filter empty resources from consideration here, + # or even "empty", redirecting maps have two (start & finish) + # or three (start, finish, plus redirector) + if($direction == FORWARD && $curRes->src()) { + $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); @@ -1383,6 +1515,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; @@ -1510,8 +1654,11 @@ sub next { # BC branch and gets to C, it will see F as the only next resource, but it's # one level lower. Thus, this is the end of the branch, since there are no # more resources added to this level or above. + # We don't do this if the examined resource is the finish resource, + # because the condition given above is true, but the "END_MAP" will + # take care of things and we should already be at depth 0. my $isEndOfBranch = $maxDepthAdded < $self->{CURRENT_DEPTH}; - if ($isEndOfBranch) { # **9** + if ($isEndOfBranch && $here != $self->{FINISH_RESOURCE}) { # **9** push @{$self->{STACK}->[$self->{CURRENT_DEPTH}]}, END_BRANCH(); } @@ -1529,6 +1676,13 @@ sub next { $self->{ALREADY_SEEN}, $self->{CONDITION}); } + # If this is a blank resource, don't actually return it. + # Should you ever find you need it, make sure to add an option to the code + # that you can use; other things depend on this behavior. + if (!$self->{HERE}->src() || !$self->{HERE}->browsePriv()) { + return $self->next(); + } + return $self->{HERE}; } @@ -1575,6 +1729,9 @@ package Apache::lonnavmaps::DFSiterator; # but this might as well be left seperate, since it is possible some other # use might be found for it. - Jeremy +# Unlike the main iterator, this DOES return all resources, even blank ones. +# The main iterator needs them to correctly preprocess the map. + sub BEGIN_MAP { return 1; } # begining of a new map sub END_MAP { return 2; } # end of the map sub FORWARD { return 1; } # go forward @@ -1775,7 +1932,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 @@ -2005,10 +2162,7 @@ sub answerdate { } return $self->parmval("answerdate", $part); } -sub awarded { - (my $self, my $part) = @_; - return $self->parmval("awarded", $part); -} +sub awarded { my $self = shift; return $self->queryRestoreHash('awarded', shift); } sub duedate { (my $self, my $part) = @_; return $self->parmval("duedate", $part); @@ -2033,24 +2187,18 @@ sub tol { (my $self, my $part) = @_; return $self->parmval("tol", $part); } -sub tries { - my $self = shift; - my $part = shift; - $part = '0' if (!defined($part)); - - # Make sure return hash is loaded, should error check - $self->getReturnHash(); - - my $tries = $self->{RETURN_HASH}->{'resource.'.$part.'.tries'}; - if (!defined($tries)) {return '0';} +sub tries { + my $self = shift; + my $tries = $self->queryRestoreHash('tries', shift); + if (!defined($tries)) { return '0';} return $tries; } sub type { (my $self, my $part) = @_; return $self->parmval("type", $part); } -sub weight { - (my $self, my $part) = @_; +sub weight { + my $self = shift; my $part = shift; return $self->parmval("weight", $part); } @@ -2105,7 +2253,16 @@ sub hasDiscussion { sub getFeedback { my $self = shift; - return $self->{NAV_MAP}->getFeedback($self->src()); + my $source = $self->src(); + if ($source =~ /^\/res\//) { $source = substr $source, 5; } + return $self->{NAV_MAP}->getFeedback($source); +} + +sub getErrors { + my $self = shift; + my $source = $self->src(); + if ($source =~ /^\/res\//) { $source = substr $source, 5; } + return $self->{NAV_MAP}->getErrors($source); } =pod @@ -2312,14 +2469,9 @@ sub ATTEMPTED { return 16; } sub getCompletionStatus { my $self = shift; - my $part = shift; - $part = "0" if (!defined($part)); return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE}); - # Make sure return hash exists - $self->getReturnHash(); - - my $status = $self->{RETURN_HASH}->{'resource.'.$part.'.solved'}; + my $status = $self->queryRestoreHash('solved', shift); # Left as seperate if statements in case we ever do more with this if ($status eq 'correct_by_student') {return $self->CORRECT;} @@ -2331,6 +2483,18 @@ sub getCompletionStatus { return $self->NOT_ATTEMPTED; } +sub queryRestoreHash { + my $self = shift; + my $hashentry = shift; + my $part = shift; + $part = "0" if (!defined($part)); + return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE}); + + $self->getReturnHash(); + + return $self->{RETURN_HASH}->{'resource.'.$part.'.'.$hashentry}; +} + =pod B @@ -2454,12 +2618,7 @@ sub getNext { my $next = $choice->goesto(); $next = $self->{NAV_MAP}->getById($next); - # 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); - if (!($browsePriv ne '2' && $browsePriv ne 'F')) { - push @branches, $next; - } + push @branches, $next; } return \@branches; } @@ -2473,16 +2632,20 @@ sub getPrevious { my $prev = $choice->comesfrom(); $prev = $self->{NAV_MAP}->getById($prev); - # 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); - if (!($browsePriv ne '2' && $browsePriv ne 'F')) { - push @branches, $prev; - } + push @branches, $prev; } return \@branches; } +sub browsePriv { + my $self = shift; + if (defined($self->{BROWSE_PRIV})) { + return $self->{BROWSE_PRIV}; + } + + $self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre', $self->src()); +} + =pod =back