version 1.101, 2002/11/08 20:58:13
|
version 1.116, 2002/11/26 16:25:36
|
Line 37
|
Line 37
|
# 3/1/1,6/1,17/1,29/1,30/1,2/8,9/21,9/24,9/25 Gerd Kortemeyer |
# 3/1/1,6/1,17/1,29/1,30/1,2/8,9/21,9/24,9/25 Gerd Kortemeyer |
# YEAR=2002 |
# YEAR=2002 |
# 1/1 Gerd Kortemeyer |
# 1/1 Gerd Kortemeyer |
# |
# Oct-Nov Jeremy Bowers |
|
|
package Apache::lonnavmaps; |
package Apache::lonnavmaps; |
|
|
use strict; |
use strict; |
use Apache::Constants qw(:common :http); |
use Apache::Constants qw(:common :http); |
use Apache::lonnet(); |
|
use Apache::loncommon(); |
use Apache::loncommon(); |
use GDBM_File; |
|
use POSIX qw (floor strftime); |
use POSIX qw (floor strftime); |
|
|
sub handler { |
sub handler { |
Line 73 sub handler {
|
Line 71 sub handler {
|
&Apache::loncommon::no_cache($r); |
&Apache::loncommon::no_cache($r); |
$r->send_http_header; |
$r->send_http_header; |
|
|
# Create the nav map the nav map |
# Create the nav map |
my $navmap = Apache::lonnavmaps::navmap->new( |
my $navmap = Apache::lonnavmaps::navmap->new( |
$ENV{"request.course.fn"}.".db", |
$ENV{"request.course.fn"}.".db", |
$ENV{"request.course.fn"}."_parms.db", 1, 1); |
$ENV{"request.course.fn"}."_parms.db", 1, 1); |
Line 85 sub handler {
|
Line 83 sub handler {
|
return HTTP_NOT_ACCEPTABLE; |
return HTTP_NOT_ACCEPTABLE; |
} |
} |
|
|
|
$r->print("<html><head>\n"); |
|
$r->print("<title>Navigate Course Contents</title>"); |
|
|
# Header |
# Header |
$r->print(&Apache::loncommon::bodytag('Navigate Course Map','', |
$r->print(&Apache::loncommon::bodytag('Navigate Course Contents','', |
'')); |
'')); |
$r->print('<script>window.focus();</script>'); |
$r->print('<script>window.focus();</script>'); |
|
|
Line 115 sub handler {
|
Line 116 sub handler {
|
$condition = 1; |
$condition = 1; |
} |
} |
|
|
|
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"; |
|
|
if ($condition) { |
if ($condition) { |
$r->print('<a href="navmaps?condition=0&filter=">Close All Folders</a>'); |
$r->print("<a href=\"navmaps?condition=0&filter=&$queryAdd\">Close All Folders</a>"); |
} else { |
} else { |
$r->print('<a href="navmaps?condition=1&filter=">Open All Folders</a>'); |
$r->print("<a href=\"navmaps?condition=1&filter=&$queryAdd\">Open All Folders</a>"); |
} |
} |
|
|
$r->print('<br> '); |
$r->print('<br> '); |
Line 145 sub handler {
|
Line 158 sub handler {
|
my %colormap = |
my %colormap = |
( $res->NETWORK_FAILURE => '', |
( $res->NETWORK_FAILURE => '', |
$res->CORRECT => '', |
$res->CORRECT => '', |
$res->EXCUSED => '#BBBBFF', |
$res->EXCUSED => '#3333FF', |
$res->PAST_DUE_ANSWER_LATER => '', |
$res->PAST_DUE_ANSWER_LATER => '', |
$res->PAST_DUE_NO_ANSWER => '', |
$res->PAST_DUE_NO_ANSWER => '', |
$res->ANSWER_OPEN => '#CCFFCC', |
$res->ANSWER_OPEN => '#006600', |
$res->OPEN_LATER => '', |
$res->OPEN_LATER => '', |
$res->TRIES_LEFT => '', |
$res->TRIES_LEFT => '', |
$res->INCORRECT => '', |
$res->INCORRECT => '', |
Line 158 sub handler {
|
Line 171 sub handler {
|
# is not yet done and due in less then 24 hours |
# is not yet done and due in less then 24 hours |
my $hurryUpColor = "#FF0000"; |
my $hurryUpColor = "#FF0000"; |
|
|
|
# Keep these mappings in sync with lonquickgrades, which uses the colors |
|
# instead of the icons. |
my %statusIconMap = |
my %statusIconMap = |
( $res->NETWORK_FAILURE => '', |
( $res->NETWORK_FAILURE => '', |
$res->NOTHING_SET => '', |
$res->NOTHING_SET => '', |
Line 193 sub handler {
|
Line 208 sub handler {
|
# Is this a new-style course? If so, we want to suppress showing the top-level |
# 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. |
# maps in their own folders, in favor of "inlining" them. |
my $topResource = $navmap->getById("0.0"); |
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 |
# Begin the HTML table |
# four cols: resource + indent, chat+feedback, icon, text string |
# four cols: resource + indent, chat+feedback, icon, text string |
Line 228 sub handler {
|
Line 230 sub handler {
|
$mapIterator->next(); # discard the first BEGIN_MAP |
$mapIterator->next(); # discard the first BEGIN_MAP |
my $curRes = $mapIterator->next(); |
my $curRes = $mapIterator->next(); |
my $counter = 0; |
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->BEGIN_MAP()) { $depth++; } |
if ($curRes == $mapIterator->END_MAP()) { $depth--; } |
if ($curRes == $mapIterator->END_MAP()) { $depth--; } |
|
|
Line 258 sub handler {
|
Line 262 sub handler {
|
$ENV{'form.alreadyHere'} = 1; |
$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(); |
$curRes = $mapIterator->next(); |
} |
} |
|
|
Line 287 sub handler {
|
Line 280 sub handler {
|
$condition); |
$condition); |
$mapIterator->next(); |
$mapIterator->next(); |
$curRes = $mapIterator->next(); |
$curRes = $mapIterator->next(); |
my $deltadepth = 0; |
|
$depth = 1; |
$depth = 1; |
|
|
my @backgroundColors = ("#FFFFFF", "#F6F6F6"); |
my @backgroundColors = ("#FFFFFF", "#F6F6F6"); |
Line 301 sub handler {
|
Line 293 sub handler {
|
} |
} |
|
|
while ($depth > 0) { |
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() || |
if ($curRes == $mapIterator->BEGIN_MAP() || |
$curRes == $mapIterator->BEGIN_BRANCH()) { |
$curRes == $mapIterator->BEGIN_BRANCH()) { |
$indentLevel++; |
$indentLevel++; |
Line 326 sub handler {
|
Line 309 sub handler {
|
|
|
if (ref($curRes)) { $counter++; } |
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 |
# Is this resource being ignored because it is in a random-out |
# map and it was not selected? |
# map and it was not selected? |
if (ref($curRes) && !advancedUser() && $curRes->randomout()) { |
if (ref($curRes) && !advancedUser() && $curRes->randomout()) { |
Line 336 sub handler {
|
Line 316 sub handler {
|
next; # if yes, then just ignore this resource |
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 |
# Step one: Decide which parts to show |
my @parts = @{$curRes->parts()}; |
my @parts = @{$curRes->parts()}; |
Line 423 sub handler {
|
Line 413 sub handler {
|
# For each part we intend to display... |
# For each part we intend to display... |
foreach my $part (@parts) { |
foreach my $part (@parts) { |
|
|
my $deltalevel = 0; # for inserting the branch icon |
|
my $nonLinkedText = ""; # unlinked stuff after title |
my $nonLinkedText = ""; # unlinked stuff after title |
|
|
my $stack = $mapIterator->getStack(); |
my $stack = $mapIterator->getStack(); |
Line 437 sub handler {
|
Line 426 sub handler {
|
'symb='.&Apache::lonnet::escape($curRes->symb()). |
'symb='.&Apache::lonnet::escape($curRes->symb()). |
'"'; |
'"'; |
|
|
my $title = $curRes->title(); |
my $title = $curRes->compTitle(); |
if (!$title) { |
|
$title = $curRes->src(); |
|
$title = substr ($title, rindex($title, "/") + 1); |
|
} |
|
my $partLabel = ""; |
my $partLabel = ""; |
my $newBranchText = ""; |
my $newBranchText = ""; |
|
|
Line 449 sub handler {
|
Line 434 sub handler {
|
if ($isNewBranch) { |
if ($isNewBranch) { |
$newBranchText = "<img src=\"/adm/lonIcons/branch.gif\" border=\"0\">"; |
$newBranchText = "<img src=\"/adm/lonIcons/branch.gif\" border=\"0\">"; |
$isNewBranch = 0; |
$isNewBranch = 0; |
$deltalevel = 1; # reserves space for the branch icon |
|
} |
} |
|
|
# links to open and close the folders |
# links to open and close the folders |
Line 485 sub handler {
|
Line 469 sub handler {
|
my $colorizer = ""; |
my $colorizer = ""; |
my $color; |
my $color; |
if ($curRes->is_problem()) { |
if ($curRes->is_problem()) { |
my $status = $curRes->status($part); |
$color = $colormap{$curRes->status}; |
$color = $colormap{$status}; |
|
|
|
# Special case in the navmaps: If in less then |
if (dueInLessThen24Hours($curRes, $part) || |
# 24 hours, give it a bit of urgency |
lastTry($curRes, $part)) { |
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()) { |
|
$color = $hurryUpColor; |
$color = $hurryUpColor; |
} |
} |
|
|
if ($color ne "") { |
if ($color ne "") { |
$colorizer = "bgcolor=\"$color\""; |
$colorizer = "bgcolor=\"$color\""; |
} |
} |
Line 519 sub handler {
|
Line 489 sub handler {
|
my $backgroundColor = $backgroundColors[$rowNum % scalar(@backgroundColors)]; |
my $backgroundColor = $backgroundColors[$rowNum % scalar(@backgroundColors)]; |
|
|
# FIRST COL: The resource indentation, branch icon, name, and anchor |
# FIRST COL: The resource indentation, branch icon, name, and anchor |
$r->print(" <tr bgcolor=\"$backgroundColor\"><td align=\"left\" valign=\"center\" width=\"60%\">\n"); |
$r->print(" <tr bgcolor=\"$backgroundColor\"><td align=\"left\" valign=\"center\">\n"); |
|
|
# Print the anchor if necessary |
# Print the anchor if necessary |
if ($counter == $currentUrlIndex - $currentUrlDelta) { |
if ($counter == $currentUrlIndex - $currentUrlDelta) { |
Line 527 sub handler {
|
Line 497 sub handler {
|
} |
} |
|
|
# print indentation |
# print indentation |
for (my $i = 0; $i < $indentLevel - $deltalevel + $deltadepth; $i++) { |
for (my $i = 0; $i < $indentLevel - $deltalevel; $i++) { |
$r->print($indentString); |
$r->print($indentString); |
} |
} |
|
|
Line 558 sub handler {
|
Line 528 sub handler {
|
'<font size="-1">Host down</font>')); |
'<font size="-1">Host down</font>')); |
} |
} |
|
|
|
$r->print("</td>\n"); |
|
|
# SECOND COL: Is there text, feedback, errors?? |
# SECOND COL: Is there text, feedback, errors?? |
my $discussionHTML = ""; my $feedbackHTML = ""; |
my $discussionHTML = ""; my $feedbackHTML = ""; |
|
|
Line 616 sub handler {
|
Line 588 sub handler {
|
} |
} |
|
|
$r->print(" </td></tr>\n"); |
$r->print(" </td></tr>\n"); |
|
|
|
if (!($counter % 20)) { $r->rflush(); } |
|
if ($counter == 2) { $r->rflush(); } |
} |
} |
} |
} |
$curRes = $mapIterator->next(); |
$curRes = $mapIterator->next(); |
Line 733 sub getDescription {
|
Line 708 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. |
# This puts a human-readable name on the ENV variable. |
sub advancedUser { |
sub advancedUser { |
return $ENV{'user.adv'}; |
return $ENV{'user.adv'}; |
Line 898 sub new {
|
Line 900 sub new {
|
&GDBM_READER(), 0640))) { |
&GDBM_READER(), 0640))) { |
return undef; |
return undef; |
} |
} |
$self->{NAV_HASH} = \%navmaphash; |
|
|
|
my %parmhash; |
my %parmhash; |
if (!(tie(%parmhash, 'GDBM_File', $self->{PARM_HASH_FILE}, |
if (!(tie(%parmhash, 'GDBM_File', $self->{PARM_HASH_FILE}, |
Line 907 sub new {
|
Line 908 sub new {
|
untie $self->{PARM_HASH}; |
untie $self->{PARM_HASH}; |
return undef; |
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); |
bless($self); |
|
$self->untieHashes(); |
|
|
return $self; |
return $self; |
} |
} |
Line 1024 sub init {
|
Line 1031 sub init {
|
$self->{PARM_CACHE} = {}; |
$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 |
# Checks to see if coursemap is defined, matching test in old lonnavmaps |
sub courseMapDefined { |
sub courseMapDefined { |
my $self = shift; |
my $self = shift; |
my $uri = &Apache::lonnet::clutter($ENV{'request.course.uri'}); |
my $uri = &Apache::lonnet::clutter($ENV{'request.course.uri'}); |
|
|
my $firstres = $self->{NAV_HASH}->{"map_start_$uri"}; |
my $firstres = $self->navhash("map_start_$uri"); |
my $lastres = $self->{NAV_HASH}->{"map_finish_$uri"}; |
my $lastres = $self->navhash("map_finish_$uri"); |
return $firstres && $lastres; |
return $firstres && $lastres; |
} |
} |
|
|
Line 1110 sub getById {
|
Line 1124 sub getById {
|
|
|
sub firstResource { |
sub firstResource { |
my $self = shift; |
my $self = shift; |
my $firstResource = $self->{NAV_HASH}->{'map_start_' . |
my $firstResource = $self->navhash('map_start_' . |
&Apache::lonnet::clutter($ENV{'request.course.uri'})}; |
&Apache::lonnet::clutter($ENV{'request.course.uri'})); |
return $self->getById($firstResource); |
return $self->getById($firstResource); |
} |
} |
|
|
Line 1123 sub firstResource {
|
Line 1137 sub firstResource {
|
|
|
sub finishResource { |
sub finishResource { |
my $self = shift; |
my $self = shift; |
my $firstResource = $self->{NAV_HASH}->{'map_finish_' . |
my $firstResource = $self->navhash('map_finish_' . |
&Apache::lonnet::clutter($ENV{'request.course.uri'})}; |
&Apache::lonnet::clutter($ENV{'request.course.uri'})); |
return $self->getById($firstResource); |
return $self->getById($firstResource); |
} |
} |
|
|
Line 1251 getIterator behaves as follows:
|
Line 1265 getIterator behaves as follows:
|
|
|
=over 4 |
=over 4 |
|
|
=item * B<getIterator>(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<getIterator>(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. |
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. |
|
|
Line 1271 The iterator will return either a refere
|
Line 1285 The iterator will return either a refere
|
|
|
The tokens are retreivable via methods on the iterator object, i.e., $iterator->END_MAP. |
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 |
=back |
|
|
=cut |
=cut |
Line 1289 sub min {
|
Line 1305 sub min {
|
if ($a < $b) { return $a; } else { return $b; } |
if ($a < $b) { return $a; } else { return $b; } |
} |
} |
|
|
|
# In the CVS repository, documentation of this algorithm is included |
|
# in /doc/lonnavdocs, as a PDF and .tex source. Markers like **1** |
|
# will reference the same location in the text as the part of the |
|
# algorithm is running through. |
|
|
sub new { |
sub new { |
# magic invocation to create a class instance |
# magic invocation to create a class instance |
my $proto = shift; |
my $proto = shift; |
Line 1316 sub new {
|
Line 1337 sub new {
|
if (!defined($self->{ALREADY_SEEN})) { $self->{ALREADY_SEEN} = {} }; |
if (!defined($self->{ALREADY_SEEN})) { $self->{ALREADY_SEEN} = {} }; |
$self->{CONDITION} = shift; |
$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 |
# 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. |
# over the parts of the map we're going to look at. |
|
|
Line 1331 sub new {
|
Line 1355 sub new {
|
|
|
my $maxDepth = 0; # tracks max depth |
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) { |
foreach my $pass (@iterations) { |
my $direction = $pass->[0]; |
my $direction = $pass->[0]; |
my $valName = $pass->[1]; |
my $valName = $pass->[1]; |
Line 1352 sub new {
|
Line 1383 sub new {
|
if ($curRes == $iterator->END_MAP()) { $depth--; } |
if ($curRes == $iterator->END_MAP()) { $depth--; } |
|
|
if (ref($curRes)) { |
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 $resultingVal = $curRes->{DATA}->{$valName}; |
my $nextResources = $curRes->$nextResourceMethod(); |
my $nextResources = $curRes->$nextResourceMethod(); |
my $resourceCount = scalar(@{$nextResources}); |
my $nextCount = scalar(@{$nextResources}); |
|
|
if ($resourceCount == 1) { |
if ($nextCount == 1) { # **3** |
my $current = $nextResources->[0]->{DATA}->{$valName} || 999999999; |
my $current = $nextResources->[0]->{DATA}->{$valName} || 999999999; |
$nextResources->[0]->{DATA}->{$valName} = min($resultingVal, $current); |
$nextResources->[0]->{DATA}->{$valName} = min($resultingVal, $current); |
} |
} |
|
|
if ($resourceCount > 1) { |
if ($nextCount > 1) { # **4** |
foreach my $res (@{$nextResources}) { |
foreach my $res (@{$nextResources}) { |
my $current = $res->{DATA}->{$valName} || 999999999; |
my $current = $res->{DATA}->{$valName} || 999999999; |
$res->{DATA}->{$valName} = min($current, $resultingVal + 1); |
$res->{DATA}->{$valName} = min($current, $resultingVal + 1); |
Line 1369 sub new {
|
Line 1402 sub new {
|
} |
} |
} |
} |
|
|
# Assign the final val |
# Assign the final val (**2**) |
if (ref($curRes) && $direction == BACKWARD()) { |
if (ref($curRes) && $direction == BACKWARD()) { |
my $finalDepth = min($curRes->{DATA}->{TOP_DOWN_VAL}, |
my $finalDepth = min($curRes->{DATA}->{TOP_DOWN_VAL}, |
$curRes->{DATA}->{BOT_UP_VAL}); |
$curRes->{DATA}->{BOT_UP_VAL}); |
Line 1381 sub new {
|
Line 1414 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. |
# Set up some bookkeeping information. |
$self->{CURRENT_DEPTH} = 0; |
$self->{CURRENT_DEPTH} = 0; |
$self->{MAX_DEPTH} = $maxDepth; |
$self->{MAX_DEPTH} = $maxDepth; |
Line 1391 sub new {
|
Line 1436 sub new {
|
push @{$self->{STACK}}, []; |
push @{$self->{STACK}}, []; |
} |
} |
|
|
# Prime the recursion w/ the first resource |
# Prime the recursion w/ the first resource **5** |
push @{$self->{STACK}->[0]}, $self->{FIRST_RESOURCE}; |
push @{$self->{STACK}->[0]}, $self->{FIRST_RESOURCE}; |
$self->{ALREADY_SEEN}->{$self->{FIRST_RESOURCE}->{ID}} = 1; |
$self->{ALREADY_SEEN}->{$self->{FIRST_RESOURCE}->{ID}} = 1; |
|
|
Line 1440 sub next {
|
Line 1485 sub next {
|
my $newDepth; |
my $newDepth; |
my $here; |
my $here; |
while ( $i >= 0 && !$found ) { |
while ( $i >= 0 && !$found ) { |
if ( scalar(@{$self->{STACK}->[$i]}) > 0 ) { |
if ( scalar(@{$self->{STACK}->[$i]}) > 0 ) { # **6** |
$here = $self->{HERE} = shift @{$self->{STACK}->[$i]}; |
$here = pop @{$self->{STACK}->[$i]}; # **7** |
$found = 1; |
$found = 1; |
$newDepth = $i; |
$newDepth = $i; |
} |
} |
Line 1459 sub next {
|
Line 1504 sub next {
|
} |
} |
} |
} |
|
|
|
# If this is not a resource, it must be an END_BRANCH marker we want |
|
# to return directly. |
|
if (!ref($here)) { # **8** |
|
if ($here == END_BRANCH()) { # paranoia, in case of later extension |
|
$self->{CURRENT_DEPTH}--; |
|
return $here; |
|
} |
|
} |
|
|
|
# Otherwise, it is a resource and it's safe to store in $self->{HERE} |
|
$self->{HERE} = $here; |
|
|
# Get to the right level |
# Get to the right level |
if ( $self->{CURRENT_DEPTH} > $newDepth ) { |
if ( $self->{CURRENT_DEPTH} > $newDepth ) { |
push @{$self->{STACK}->[$newDepth]}, $here; |
push @{$self->{STACK}->[$newDepth]}, $here; |
Line 1478 sub next {
|
Line 1535 sub next {
|
# So we need to look at all the resources we can get to from here, |
# So we need to look at all the resources we can get to from here, |
# categorize them if we haven't seen them, remember if we have a new |
# categorize them if we haven't seen them, remember if we have a new |
my $nextUnfiltered = $here->getNext(); |
my $nextUnfiltered = $here->getNext(); |
|
my $maxDepthAdded = -1; |
|
|
for (@$nextUnfiltered) { |
for (@$nextUnfiltered) { |
if (!defined($self->{ALREADY_SEEN}->{$_->{ID}})) { |
if (!defined($self->{ALREADY_SEEN}->{$_->{ID}})) { |
push @{$self->{STACK}->[$_->{DATA}->{DISPLAY_DEPTH}]}, $_; |
my $depth = $_->{DATA}->{DISPLAY_DEPTH}; |
|
push @{$self->{STACK}->[$depth]}, $_; |
$self->{ALREADY_SEEN}->{$_->{ID}} = 1; |
$self->{ALREADY_SEEN}->{$_->{ID}} = 1; |
|
if ($maxDepthAdded < $depth) { $maxDepthAdded = $depth; } |
} |
} |
} |
} |
|
|
|
# Is this the end of a branch? If so, all of the resources examined above |
|
# led to lower levels then the one we are currently at, so we push a END_BRANCH |
|
# marker onto the stack so we don't forget. |
|
# Example: For the usual A(BC)(DE)F case, when the iterator goes down the |
|
# 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 && $here != $self->{FINISH_RESOURCE}) { # **9** |
|
push @{$self->{STACK}->[$self->{CURRENT_DEPTH}]}, END_BRANCH(); |
|
} |
|
|
# That ends the main iterator logic. Now, do we want to recurse |
# That ends the main iterator logic. Now, do we want to recurse |
# down this map (if this resource is a map)? |
# down this map (if this resource is a map)? |
if ($self->{HERE}->is_map() && |
if ($self->{HERE}->is_map() && |
Line 1500 sub next {
|
Line 1575 sub next {
|
$self->{ALREADY_SEEN}, $self->{CONDITION}); |
$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}; |
return $self->{HERE}; |
|
|
} |
} |
Line 1683 sub next {
|
Line 1763 sub next {
|
$self->{CONDITION}, $self->{DIRECTION}); |
$self->{CONDITION}, $self->{DIRECTION}); |
} |
} |
|
|
|
# If this is a blank resource, ignore it. |
|
if (!$self->{HERE}->src()) { |
|
return $self->next(); |
|
} |
|
|
return $self->{HERE}; |
return $self->{HERE}; |
} |
} |
|
|
Line 1746 sub navHash {
|
Line 1831 sub navHash {
|
my $self = shift; |
my $self = shift; |
my $param = shift; |
my $param = shift; |
my $id = shift; |
my $id = shift; |
return $self->{NAV_MAP}->{NAV_HASH}->{$param . ($id?$self->{ID}:"")}; |
return $self->{NAV_MAP}->navhash($param . ($id?$self->{ID}:"")); |
} |
} |
|
|
=pod |
=pod |
Line 1757 These are methods that help you retrieve
|
Line 1842 These are methods that help you retrieve
|
|
|
=over 4 |
=over 4 |
|
|
|
=item * B<compTitle>: Returns a "composite title", that is equal to $res->title() if the resource has a title, and is otherwise the last part of the URL (e.g., "problem.problem"). |
|
|
=item * B<ext>: Returns true if the resource is external. |
=item * B<ext>: Returns true if the resource is external. |
|
|
=item * B<goesto>: Returns the "goesto" value from the compiled nav map. (It is likely you want to use B<getNext> instead.) |
=item * B<goesto>: Returns the "goesto" value from the compiled nav map. (It is likely you want to use B<getNext> instead.) |
Line 1806 sub symb {
|
Line 1893 sub symb {
|
} |
} |
sub title { my $self=shift; return $self->navHash("title_", 1); } |
sub title { my $self=shift; return $self->navHash("title_", 1); } |
sub to { my $self=shift; return $self->navHash("to_", 1); } |
sub to { my $self=shift; return $self->navHash("to_", 1); } |
|
sub compTitle { |
|
my $self = shift; |
|
my $title = $self->title(); |
|
if (!$title) { |
|
$title = $self->src(); |
|
$title = substr($title, rindex($title, '/') + 1); |
|
} |
|
return $title; |
|
} |
=pod |
=pod |
|
|
B<Predicate Testing the Resource> |
B<Predicate Testing the Resource> |
Line 1966 sub answerdate {
|
Line 2061 sub answerdate {
|
} |
} |
return $self->parmval("answerdate", $part); |
return $self->parmval("answerdate", $part); |
} |
} |
|
sub awarded { my $self = shift; return $self->queryRestoreHash('awarded', shift); } |
sub duedate { |
sub duedate { |
(my $self, my $part) = @_; |
(my $self, my $part) = @_; |
return $self->parmval("duedate", $part); |
return $self->parmval("duedate", $part); |
Line 1990 sub tol {
|
Line 2086 sub tol {
|
(my $self, my $part) = @_; |
(my $self, my $part) = @_; |
return $self->parmval("tol", $part); |
return $self->parmval("tol", $part); |
} |
} |
sub tries { |
sub tries { |
my $self = shift; |
my $self = shift; |
my $part = shift; |
my $tries = $self->queryRestoreHash('tries', shift); |
$part = '0' if (!defined($part)); |
if (!defined($tries)) { return '0';} |
|
|
# Make sure return hash is loaded, should error check |
|
$self->getReturnHash(); |
|
|
|
my $tries = $self->{RETURN_HASH}->{'resource.'.$part.'.tries'}; |
|
if (!defined($tries)) {return '0';} |
|
return $tries; |
return $tries; |
} |
} |
sub type { |
sub type { |
(my $self, my $part) = @_; |
(my $self, my $part) = @_; |
return $self->parmval("type", $part); |
return $self->parmval("type", $part); |
} |
} |
sub weight { |
sub weight { |
(my $self, my $part) = @_; |
my $self = shift; my $part = shift; |
return $self->parmval("weight", $part); |
return $self->parmval("weight", $part); |
} |
} |
|
|
Line 2269 sub ATTEMPTED { return 16; }
|
Line 2359 sub ATTEMPTED { return 16; }
|
|
|
sub getCompletionStatus { |
sub getCompletionStatus { |
my $self = shift; |
my $self = shift; |
my $part = shift; |
|
$part = "0" if (!defined($part)); |
|
return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE}); |
return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE}); |
|
|
# Make sure return hash exists |
my $status = $self->queryRestoreHash('solved', shift); |
$self->getReturnHash(); |
|
|
|
my $status = $self->{RETURN_HASH}->{'resource.'.$part.'.solved'}; |
|
|
|
# Left as seperate if statements in case we ever do more with this |
# Left as seperate if statements in case we ever do more with this |
if ($status eq 'correct_by_student') {return $self->CORRECT;} |
if ($status eq 'correct_by_student') {return $self->CORRECT;} |
Line 2288 sub getCompletionStatus {
|
Line 2373 sub getCompletionStatus {
|
return $self->NOT_ATTEMPTED; |
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 |
=pod |
|
|
B<Composite Status> |
B<Composite Status> |
Line 2413 sub getNext {
|
Line 2510 sub getNext {
|
|
|
# Don't remember it if the student doesn't have browse priviledges |
# Don't remember it if the student doesn't have browse priviledges |
# future note: this may properly belong in the client of the resource |
# 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 ($browsePriv ne '2' && $browsePriv ne 'F') { |
if (!defined($browsePriv)) { |
|
$browsePriv = &Apache::lonnet::allowed('bre', $self->src); |
|
$self->{BROWSE_PRIV} = $browsePriv; |
|
} |
|
if (!($browsePriv ne '2' && $browsePriv ne 'F')) { |
push @branches, $next; |
push @branches, $next; |
} |
} |
} |
} |
Line 2432 sub getPrevious {
|
Line 2533 sub getPrevious {
|
|
|
# Don't remember it if the student doesn't have browse priviledges |
# Don't remember it if the student doesn't have browse priviledges |
# future note: this may properly belong in the client of the resource |
# 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 ($browsePriv ne '2' && $browsePriv ne 'F') { |
if (!defined($browsePriv)) { |
|
$browsePriv = &Apache::lonnet::allowed('bre', $self->src); |
|
$self->{BROWSE_PRIV} = $browsePriv; |
|
} |
|
if (!($browsePriv ne '2' && $browsePriv ne 'F')) { |
push @branches, $prev; |
push @branches, $prev; |
} |
} |
} |
} |