--- loncom/interface/lonnavmaps.pm 2002/10/03 19:10:28 1.62 +++ loncom/interface/lonnavmaps.pm 2002/10/07 19:00:38 1.67 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.62 2002/10/03 19:10:28 bowersj2 Exp $ +# $Id: lonnavmaps.pm,v 1.67 2002/10/07 19:00:38 bowersj2 Exp $ # # Copyright Michigan State University Board of Trustees # @@ -819,9 +819,6 @@ sub new_handle { &Apache::loncommon::no_cache($r); $r->send_http_header; - # Header - $r->print(&Apache::loncommon::bodytag('Navigate Course Map','', - '')); # Initialize the nav map my $navmap = Apache::lonnavmaps::navmap->new( $ENV{"request.course.fn"}.".db", @@ -834,6 +831,28 @@ sub new_handle { return HTTP_NOT_ACCEPTABLE; } + # Header + $r->print(&Apache::loncommon::bodytag('Navigate Course Map','', + '')); + $r->print(''); + my $desc=$ENV{'course.'.$ENV{'request.course.id'}.'.description'}; + if (defined($desc)) { $r->print("

$desc

\n"); } + my $date=localtime; + $r->print("

$date

\n"); + $r->rflush(); + if ($navmap->{LAST_CHECK}) { + $r->print(' New discussion since '. + timeToHumanString($navmap->{LAST_CHECK}). + '
New message (click to open)

'); + } else { + $r->print(' Discussions'. + '
New message (click to open)

'); + } + #if (($currenturl=~/^\/res/) && + # ($currenturl!~/^\/res\/adm/)) { + # $r->print('Current Location

'); + #} + # Check that it's defined if (!($navmap->courseMapDefined())) { $r->print('Coursemap undefined.' . @@ -863,22 +882,22 @@ sub new_handle { my $hurryUpColor = "#FFCCCC"; my %statusIconMap = - ( $res->NETWORK_FAILURE => '', - $res->NOTHING_SET => '', - $res->CORRECT => 'navmap.correct.gif', - $res->EXCUSED => 'navmap.correct.gif', - $res->PAST_DUE_NO_ANSWER => 'navmap.wrong.gif', - $res->PAST_DUE_ANSWER_LATER => 'navmap.wrong.gif', - $res->ANSWER_OPEN => 'navmap.wrong.gif', - $res->OPEN_LATER => '', - $res->TRIES_LEFT => 'navmap.open.gif', - $res->INCORRECT => 'navmap.wrong.gif', - $res->OPEN => 'navmap.open.gif' ); + ( $res->NETWORK_FAILURE => '', + $res->NOTHING_SET => '', + $res->CORRECT => 'navmap.correct.gif', + $res->EXCUSED => 'navmap.correct.gif', + $res->PAST_DUE_NO_ANSWER => 'navmap.wrong.gif', + $res->PAST_DUE_ANSWER_LATER => 'navmap.wrong.gif', + $res->ANSWER_OPEN => 'navmap.wrong.gif', + $res->OPEN_LATER => '', + $res->TRIES_LEFT => 'navmap.open.gif', + $res->INCORRECT => 'navmap.wrong.gif', + $res->OPEN => 'navmap.open.gif' ); my %condenseStatuses = - ( $res->NETWORK_FAILURE => 1, - $res->NOTHING_SET => 1, - $res->CORRECT => 1 ); + ( $res->NETWORK_FAILURE => 1, + $res->NOTHING_SET => 1, + $res->CORRECT => 1 ); my %filterHash; # Figure out what we're not displaying @@ -888,11 +907,23 @@ sub new_handle { } } + my $currenturl = $ENV{'form.postdata'}; + $currenturl=~s/^http\:\/\///; + $currenturl=~s/^[^\/]+//; + my $queryAdd = "postdata=" . &Apache::lonnet::escape($currenturl); + + $r->print('Show All Resources

'); + # Begin the HTML table # four cols: resource + indent, chat+feedback, icon, text string $r->print('' ."\n"); - my $mapIterator = $navmap->getIterator(undef, undef, \%filterHash); + my $condition = 0; + if ($ENV{'form.condition'}) { + $condition = 1; + } + + my $mapIterator = $navmap->getIterator(undef, undef, \%filterHash, $condition); my $curRes = $mapIterator->next(); undef $res; # so we don't accidentally use it later my $indentLevel = -1; @@ -917,65 +948,66 @@ sub new_handle { if (ref($curRes) && $curRes->src()) { - # Step one: Decide which parts to show - my @parts = @{$curRes->parts()}; - my $multipart = scalar(@parts) > 1; - my $condensed = 0; - + # Step one: Decide which parts to show + my @parts = @{$curRes->parts()}; + my $multipart = scalar(@parts) > 1; + my $condensed = 0; + if ($curRes->is_problem()) { - # Is it multipart? + # Is it multipart? - if ($multipart) { - # If it's multipart, see if part 0 is "open" - # if it is, display all parts, if it isn't, - # just display first - if (!$curRes->opendate("0")) { - @parts = ("0"); # just display the zero-th part - } else { - # Otherwise, only display part 0 if we want to - # attach feedback or email information to it - if ($curRes->hasDiscussion() || $curRes->getFeedback()) { - shift @parts; - } else { - # If there's discussion or feedback, that counts - # as a difference, so display the parts. - - # Now, we decide whether to condense the - # parts due to similarity - my $status = $curRes->status($parts[1]); - my $due = $curRes->duedate($parts[1]); - my $open = $curRes->opendate($parts[1]); - my $statusAllSame = 1; - my $dueAllSame = 1; - my $openAllSame = 1; - for (my $i = 2; $i < scalar(@parts); $i++) { - if ($curRes->status($parts[$i]) != $status){ - $statusAllSame = 0; - } - if ($curRes->duedate($parts[$i]) != $due ) { - $dueAllSame = 0; - } - if ($curRes->opendate($parts[$i]) != $open) { - $openAllSame = 0; - } - } - - # $allSame is true if all the statuses were - # the same. Now, if they are all the same and - # match one of the statuses to condense, or they - # are all open with the same due date, or they are - # all OPEN_LATER with the same open date, display the - # status of the first non-zero part (to get the 'correct' - # status right, since 0 is never 'correct' or 'open'). - if (($statusAllSame && defined($condenseStatuses{$status})) || - ($dueAllSame && $status == $curRes->OPEN && $statusAllSame)|| - ($openAllSame && $status == $curRes->OPEN_LATER && $statusAllSame) ){ - @parts = ($parts[1]); - $condensed = 1; - } - } - } - } + if ($multipart) { + # If it's multipart, see if part 0 is "open" + # if it is, display all parts, if it isn't, + # just display first + if (!$curRes->opendate("0")) { + @parts = ("0"); # just display the zero-th part + $condensed = 1; + } else { + # Otherwise, only display part 0 if we want to + # attach feedback or email information to it + if ($curRes->hasDiscussion() || $curRes->getFeedback()) { + shift @parts; + } else { + # If there's discussion or feedback, that counts + # as a difference, so display the parts. + + # Now, we decide whether to condense the + # parts due to similarity + my $status = $curRes->status($parts[1]); + my $due = $curRes->duedate($parts[1]); + my $open = $curRes->opendate($parts[1]); + my $statusAllSame = 1; + my $dueAllSame = 1; + my $openAllSame = 1; + for (my $i = 2; $i < scalar(@parts); $i++) { + if ($curRes->status($parts[$i]) != $status){ + $statusAllSame = 0; + } + if ($curRes->duedate($parts[$i]) != $due ) { + $dueAllSame = 0; + } + if ($curRes->opendate($parts[$i]) != $open) { + $openAllSame = 0; + } + } + + # $allSame is true if all the statuses were + # the same. Now, if they are all the same and + # match one of the statuses to condense, or they + # are all open with the same due date, or they are + # all OPEN_LATER with the same open date, display the + # status of the first non-zero part (to get the 'correct' + # status right, since 0 is never 'correct' or 'open'). + if (($statusAllSame && defined($condenseStatuses{$status})) || + ($dueAllSame && $status == $curRes->OPEN && $statusAllSame)|| + ($openAllSame && $status == $curRes->OPEN_LATER && $statusAllSame) ){ + @parts = ($parts[1]); + $condensed = 1; + } + } + } + } } else { @parts[0] = "0"; # this is to get past foreach loop below @@ -983,10 +1015,17 @@ sub new_handle { # with only one part without loss } - # Step two: Show the parts + # Display one part, in event of network error. + # If this is a single part, we can at least show the correct + # status, but if it's multipart, we're lost. + if ($curRes->{RESOURCE_ERROR}) { + @parts = ("0"); + } + + # Step two: Show the parts foreach my $part (@parts) { - my $deltalevel = 0; # for inserting the branch icon + my $deltalevel = 0; # for inserting the branch icon # For each thing we're displaying... @@ -1007,7 +1046,7 @@ sub new_handle { if ($isNewBranch) { $newBranchText = ""; $isNewBranch = 0; - $deltalevel = 1; + $deltalevel = 1; } # links to open and close the folders @@ -1016,11 +1055,11 @@ sub new_handle { my $icon = "\"\""; if ($curRes->is_problem()) { - if ($part eq "0" || $condensed) { - $icon = ''; - } else { - $icon = $indentString; - } + if ($part eq "0" || $condensed) { + $icon = ''; + } else { + $icon = $indentString; + } } # Display the correct icon, link to open or shut map @@ -1028,13 +1067,13 @@ sub new_handle { my $mapId = $curRes->map_pc(); my $nowOpen = !defined($filterHash{$mapId}); $icon = $nowOpen ? - "folder_opened.gif" : "folder_closed.gif"; - $icon = "\"\""; + "folder_closed.gif" : "folder_opened.gif"; + $icon = "\"\""; $linkopen = ""; + $linkopen .= "&condition=$condition&$queryAdd\">"; $linkclose = ""; } @@ -1048,8 +1087,8 @@ sub new_handle { } } - # FIRST ROW: The resource indentation, branch icon, and name - $r->print(" "); - - my $discussionHTML = ""; my $feedbackHTML = ""; - - # SECOND ROW: Is there text or feedback? - if ($curRes->hasDiscussion()) { - $discussionHTML = $linkopen . - '' . - $linkclose; - } - - if ($curRes->getFeedback()) { - my $feedback = $curRes->getFeedback(); - foreach (split(/\,/, $feedback)) { - if ($_) { - $feedbackHTML .= ' ' - . ''; - } - } - } - - $r->print(""); - - # Is this the first displayed part of a multi-part problem - # that has not been condensed, so we should suppress these two - # columns? - my $firstDisplayed = !$condensed && $multipart && $part eq "0"; - - # THIRD ROW: Problem status icon - if ($curRes->is_problem() && - !$firstDisplayed) { - my $icon = $statusIconMap{$curRes->status($part)}; - if ($icon) { - $r->print("\n"); - } else { - $r->print("\n"); - } - } else { # not problem, no icon - $r->print("\n"); - } + $partLabel = " (Part $part)"; + $title = ""; + } + if ($multipart && $condensed) { + $nonLinkedText = ' (' . $curRes->countParts() . ' parts)'; + } + + $r->print(" $title$partLabel $nonLinkedText"); + + if ($curRes->{RESOURCE_ERROR}) { + $r->print(&Apache::loncommon::help_open_topic ("Navmap_Host_Down", + 'Host down')); + } + + my $discussionHTML = ""; my $feedbackHTML = ""; - # FOURTH ROW: Text description - $r->print(""); + + # Is this the first displayed part of a multi-part problem + # that has not been condensed, so we should suppress these two + # columns? + my $firstDisplayed = !$condensed && $multipart && $part eq "0"; + + # THIRD ROW: Problem status icon + if ($curRes->is_problem() && + !$firstDisplayed) { + my $icon = $statusIconMap{$curRes->status($part)}; + if ($icon) { + $r->print("\n"); + } else { + $r->print("\n"); + } + } else { # not problem, no icon + $r->print("\n"); + } + + # FOURTH ROW: Text description + $r->print("\n"); + $r->print("\n"); } } $curRes = $mapIterator->next(); @@ -1193,11 +1242,11 @@ sub getDescription { return "Opens: " . timeToHumanString($res->opendate($part)); } if ($status == $res->OPEN) { - if ($res->duedate()) { - return "Due: $status " . timeToHumanString($res->duedate($part)); - } else { - return "Open, no due date"; - } + if ($res->duedate()) { + return "Due: $status " . timeToHumanString($res->duedate($part)); + } else { + return "Open, no due date"; + } } if ($status == $res->PAST_DUE_ANSWER_LATER) { return "Answer opens: " . timeToHumanString($res->answerdate($part)); @@ -1209,18 +1258,18 @@ sub getDescription { return "Answer available"; } if ($status == $res->EXCUSED) { - return "Excused by instructor"; + return "Excused by instructor"; } if ($status == $res->TRIES_LEFT) { - my $tries = $res->tries(); - my $maxtries = $res->maxtries(); - my $triesString = "($tries of $maxtries tries used)"; - if ($res->duedate()) { - return "Due: $status " . timeToHumanString($res->duedate($part)) . - " $triesString"; - } else { - return "No due date $triesString"; - } + my $tries = $res->tries(); + my $maxtries = $res->maxtries(); + my $triesString = "($tries of $maxtries tries used)"; + if ($res->duedate()) { + return "Due: $status " . timeToHumanString($res->duedate($part)) . + " $triesString"; + } else { + return "No due date $triesString"; + } } } @@ -1231,9 +1280,9 @@ sub timeToHumanString { my ($time) = @_; # zero, '0' and blank are bad times if ($time) { - return localtime($time); + return localtime($time); } else { - return 'Never'; + return 'Never'; } } @@ -1301,7 +1350,7 @@ sub new { if (!(tie(%parmhash, 'GDBM_File', $self->{PARM_HASH_FILE}, &GDBM_READER(), 0640))) { - untie $self->{PARM_HASH}; + untie $self->{PARM_HASH}; return undef; } $self->{PARM_HASH} = \%parmhash; @@ -1428,7 +1477,7 @@ sub courseMapDefined { sub getIterator { my $self = shift; my $iterator = Apache::lonnavmaps::iterator->new($self, shift, shift, - shift, shift); + shift, undef, shift); return $iterator; } @@ -1454,7 +1503,7 @@ sub hasDiscussion { if (!defined($self->{DISCUSSION_TIME})) { return 0; } return $self->{DISCUSSION_TIME}->{$symb} > - $self->{LAST_CHECK}; + $self->{LAST_CHECK}; } # Private function: Does the given resource (as a symb string) have @@ -1637,7 +1686,7 @@ getIterator behaves as follows: =over 4 -=item B(nav_map, firstResource, finishResource, filterHash): 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(). It is your responsibility to ensure that the iterator will actually get there. 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 1, then only resource that exist IN the filterHash will be recursed on. If it is a 0, only resources NOT in the filterHash will be recursed on. Defaults to 0. +=item B(nav_map, 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(). It is your responsibility to ensure that the iterator will actually get there. 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, which is to say, do not recurse unless explicitly asked to. Thus, by default, all resources will be shown. Change the condition to a 1 without changing the hash, and only the top level of the map will be shown. Changing the condition to 1 and including some values in the hash will allow you to selectively examine parts of the navmap, while leaving it on 0 and adding things to the hash will allow you to selectively ignore parts of the nav map. See the handler code for examples. @@ -1689,6 +1738,7 @@ sub new { # A hash, used as a set, of resource already seen $self->{ALREADY_SEEN} = shift; if (!defined($self->{ALREADY_SEEN})) { $self->{ALREADY_SEEN} = {} }; + $self->{CONDITION} = shift; # Flag: Have we started yet? If not, the first action is to return BEGIN_MAP. $self->{STARTED} = 0; @@ -1859,7 +1909,7 @@ sub next { # If this is a map and we want to recurse down it... (not filtered out) if ($self->{HERE}->is_map() && - !defined($self->{FILTER}->{$self->{HERE}->map_pc()})) { + (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) { $self->{RECURSIVE_ITERATOR_FLAG} = 1; my $firstResource = $self->{HERE}->map_start(); my $finishResource = $self->{HERE}->map_finish(); @@ -1869,9 +1919,8 @@ sub next { # isn't MAIN::new, __PACKAGE__::new or Apache::lonnavmaps::iterator->new $self->{RECURSIVE_ITERATOR} = Apache::lonnavmaps::iterator->new ($self->{NAV_MAP}, $firstResource, - $finishResource, $self->{FILTER}, $self->{ALREADY_SEEN}); - # prime the new iterator with the first resource - #push @{$self->{RECURSIVE_ITERATOR}->{BRANCH_STACK}}, $firstResource; + $finishResource, $self->{FILTER}, $self->{ALREADY_SEEN}, + $self->{CONDITION}); } return $self->{HERE}; @@ -1944,6 +1993,7 @@ sub new { # Store this new resource in the parent nav map's cache. $self->{NAV_MAP}->{RESOURCE_CACHE}->{$self->{ID}} = $self; + $self->{RESOURCE_ERROR} = 0; # A hash that can be used by two-pass algorithms to store data # about this resource in. Not used by the resource object @@ -1987,6 +2037,7 @@ sub goesto { my $self=shift; return $sel # "to" can return a comma seperated list for branches sub to { my $self=shift; return $self->navHash("to_", 1); } sub kind { my $self=shift; return $self->navHash("kind_", 1); } +sub ext { my $self=shift; return $self->navHash("ext_", 1) eq 'true:'; } sub src { my $self=shift; return $self->navHash("src_", 1); @@ -2236,6 +2287,8 @@ sub getFeedback { sub parts { my $self = shift; + if ($self->ext) { return ['0']; } + $self->extractParts(); return $self->{PARTS}; } @@ -2244,8 +2297,12 @@ sub countParts { my $self = shift; my $parts = $self->parts(); - - if (scalar(@{$parts}) == 1) { return 1;} + + if ($self->{RESOURCE_ERROR}) { + return 0; + } + + if (scalar(@{$parts}) < 2) { return 1;} return scalar(@{$parts}) - 1; } @@ -2255,16 +2312,24 @@ sub extractParts { my $self = shift; return if ($self->{PARTS}); + return if ($self->ext); $self->{PARTS} = []; # Retrieve part count - foreach (split(/\,/,&Apache::lonnet::metadata($self->src(), - 'allpossiblekeys'))) { + my $metadata = &Apache::lonnet::metadata($self->src(), 'allpossiblekeys'); + if (!$metadata) { + $self->{RESOURCE_ERROR} = 1; + $self->{PARTS} = []; + return; + } + + foreach (split(/\,/,$metadata)) { if ($_ =~ /^parameter\_(.*)\_opendate$/) { push @{$self->{PARTS}}, $1; } } + # Is this possible to do in one line? - Jeremy my @sortedParts = sort @{$self->{PARTS}}; @@ -2539,11 +2604,11 @@ sub getNext { $next = $self->{NAV_MAP}->getById($next); # Don't remember it if we've already seen it or if - # the student doesn't have browse priviledges - my $browsePriv = &Apache::lonnet::allowed('bre', $self->src); + # the student doesn't have browse priviledges + my $browsePriv = &Apache::lonnet::allowed('bre', $self->src); if (!defined($alreadySeenHash) || !defined($alreadySeenHash->{$next->{ID}}) || - ($browsePriv ne '2' && $browsePriv ne 'F')) { + ($browsePriv ne '2' && $browsePriv ne 'F')) { push @branches, $next; } }
\n"); + # FIRST COL: The resource indentation, branch icon, and name + $r->print("
\n"); # print indentation for (my $i = 0; $i < $indentLevel - $deltalevel; $i++) { @@ -1058,64 +1097,74 @@ sub new_handle { $r->print(" ${newBranchText}${linkopen}$icon${linkclose}\n"); + my $nonLinkedText = ""; + if ($curRes->is_problem() && $part != "0" && !$condensed) { - $partLabel = " (Part $part)"; - $title = ""; - } - - $r->print(" $title$partLabel$discussionHTML$feedbackHTML$linkopen$linkclose\n"); + # SECOND COL: Is there text or feedback? + if ($curRes->hasDiscussion()) { + $discussionHTML = $linkopen . + '' . + $linkclose; + } + + if ($curRes->getFeedback()) { + my $feedback = $curRes->getFeedback(); + foreach (split(/\,/, $feedback)) { + if ($_) { + $feedbackHTML .= ' ' + . ''; + } + } + } + + $r->print("$discussionHTML$feedbackHTML$linkopen$linkclose\n"); if ($curRes->kind() eq "res" && $curRes->is_problem() && - !$firstDisplayed) { + !$firstDisplayed) { $r->print (getDescription($curRes, $part)); } - $r->print("