--- loncom/interface/lonnavmaps.pm 2003/12/01 14:38:25 1.242 +++ loncom/interface/lonnavmaps.pm 2005/02/05 06:52:50 1.315 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.242 2003/12/01 14:38:25 matthew Exp $ +# $Id: lonnavmaps.pm,v 1.315 2005/02/05 06:52:50 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -25,20 +25,7 @@ # # http://www.lon-capa.org/ # -# (Page Handler -# -# (TeX Content Handler -# -# 05/29/00,05/30 Gerd Kortemeyer) -# 08/30,08/31,09/06,09/14,09/15,09/16,09/19,09/20,09/21,09/23, -# 10/02,10/10,10/14,10/16,10/18,10/19,10/31,11/6,11/14,11/16 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 -# 1/1 Gerd Kortemeyer -# Oct-Nov Jeremy Bowers -# YEAR=2003 -# Jeremy Bowers ... lots of days +### package Apache::lonnavmaps; @@ -46,9 +33,11 @@ use strict; use Apache::Constants qw(:common :http); use Apache::loncommon(); use Apache::lonmenu(); +use Apache::lonenc(); use Apache::lonlocal; +use Apache::lonnet; use POSIX qw (floor strftime); -use Data::Dumper; # for debugging, not always used +use Data::Dumper; # for debugging, not always # symbolic constants sub SYMB { return 1; } @@ -96,6 +85,56 @@ my %colormap = # is not yet done and due in less then 24 hours my $hurryUpColor = "#FF0000"; +sub launch_win { + my ($mode,$script,$toplinkitems)=@_; + my $result; + if ($script ne 'no') { + $result.=''; + } + if ($mode eq 'link') { + &add_linkitem($toplinkitems,'launchnav','launch_navmapwin()', + "Launch navigation window"); + } + return $result; +} + +sub close { + if ($ENV{'environment.remotenavmap'} ne 'on') { return ''; } + return(< +window.status='Accessing Nav Control'; +menu=window.open("/adm/rat/empty.html","loncapanav", + "height=600,width=400,scrollbars=1"); +window.status='Closing Nav Control'; +menu.close(); +window.status='Done.'; + +ENDCLOSE +} + +sub update { + if ($ENV{'environment.remotenavmap'} ne 'on') { return ''; } + if (!$ENV{'request.course.id'}) { return ''; } + if ($ENV{'REQUEST_URI'}=~m|^/adm/navmaps|) { return ''; } + return(< + +ENDUPDATE +} + sub handler { my $r = shift; real_handler($r); @@ -124,6 +163,48 @@ sub real_handler { &Apache::loncommon::no_cache($r); $r->send_http_header; + my %toplinkitems=(); + &add_linkitem(\%toplinkitems,'blank','',"Select Action"); + if ($ENV{QUERY_STRING} eq 'collapseExternal') { + &Apache::lonnet::put('environment',{'remotenavmap' => 'off'}); + &Apache::lonnet::appenv('environment.remotenavmap' => 'off'); + my $menu=&Apache::lonmenu::reopenmenu(); + my $navstatus=&Apache::lonmenu::get_nav_status(); + if ($menu) { + $menu=(<print(<<"ENDSUBM"); + + + + + + +ENDSUBM + return; + } + if ($ENV{QUERY_STRING} eq 'launchExternal') { + &Apache::lonnet::put('environment',{'remotenavmap' => 'on'}); + &Apache::lonnet::appenv('environment.remotenavmap' => 'on'); + } + # Create the nav map my $navmap = Apache::lonnavmaps::navmap->new(); @@ -136,26 +217,43 @@ sub real_handler { $r->print("\n"); $r->print("".&mt('Navigate Course Contents').""); # ------------------------------------------------------------ Get query string - &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register']); + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register','sort','showOnlyHomework','postsymb']); # ----------------------------------------------------- Force menu registration my $addentries=''; + my $more_unload; + my $body_only=''; + if ($ENV{'environment.remotenavmap'} eq 'on') { + $r->print(''); +# FIXME need to be smarter to only catch window close events +# $more_unload="collapse()" + $body_only=1; + } if ($ENV{'form.register'}) { - $addentries=' onLoad="'.&Apache::lonmenu::loadevents(). - '" onUnload="'.&Apache::lonmenu::unloadevents().'"'; - $r->print(&Apache::lonmenu::registerurl(1)); + $addentries=' onLoad="'.&Apache::lonmenu::loadevents(). + '" onUnload="'.&Apache::lonmenu::unloadevents().';'. + $more_unload.'"'; + $r->print(&Apache::lonmenu::registerurl(1)); + } else { + $addentries=' onUnload="'.$more_unload.'"'; } # Header $r->print(''. &Apache::loncommon::bodytag('Navigate Course Contents','', - $addentries,'','',$ENV{'form.register'})); + $addentries,$body_only,'', + $ENV{'form.register'})); $r->print(''); - + $r->rflush(); # Check that it's defined if (!($navmap->courseMapDefined())) { + $r->print(&Apache::loncommon::help_open_menu('','Navigation Screen','Navigation_Screen','',undef,'RAT')); $r->print('Coursemap undefined.' . ''); return OK; @@ -185,9 +283,28 @@ sub real_handler { } } + if ($ENV{QUERY_STRING} eq 'launchExternal') { + $r->print(' +
+
'); + $r->print(' + '); + } + + if ($ENV{'environment.remotenavmap'} ne 'on') { + $r->print(&launch_win('link','yes',\%toplinkitems)); + } + if ($ENV{'environment.remotenavmap'} eq 'on') { + &add_linkitem(\%toplinkitems,'closenav','collapse()', + "Close navigation window"); + } + my $jumpToFirstHomework = 0; # Check to see if the student is jumping to next open, do-able problem - if ($ENV{QUERY_STRING} eq 'jumpToFirstHomework') { + if ($ENV{QUERY_STRING} =~ /^jumpToFirstHomework/) { $jumpToFirstHomework = 1; # Find the next homework problem that they can do. my $iterator = $navmap->getIterator(undef, undef, undef, 1); @@ -220,8 +337,9 @@ sub real_handler { $r->print("All homework assignments have been completed.

"); } } else { - $r->print("" . - &mt("Go To My First Homework Problem")."    "); + &add_linkitem(\%toplinkitems,'firsthomework', + 'location.href="navmaps?jumpToFirstHomework"', + "Show Me My First Homework Problem"); } my $suppressEmptySequences = 0; @@ -230,32 +348,53 @@ sub real_handler { # Display only due homework. my $showOnlyHomework = 0; - if ($ENV{QUERY_STRING} eq 'showOnlyHomework') { + if ($ENV{'form.showOnlyHomework'} eq "1") { $showOnlyHomework = 1; $suppressEmptySequences = 1; $filterFunc = sub { my $res = shift; return $res->completable() || $res->is_map(); }; + &add_linkitem(\%toplinkitems,'everything', + 'location.href="navmaps?sort='.$ENV{'form.sort'}.'"', + "Show Everything"); $r->print("

".&mt("Uncompleted Homework")."

"); $ENV{'form.filter'} = ''; $ENV{'form.condition'} = 1; $resource_no_folder_link = 1; } else { - $r->print("" . - &mt("Show Only Uncompleted Homework")."    "); - } - + &add_linkitem(\%toplinkitems,'uncompleted', + 'location.href="navmaps?sort='.$ENV{'form.sort'}. + '&showOnlyHomework=1"', + "Show Only Uncompleted Homework"); + } + + my %selected=($ENV{'form.sort'} => 'selected=on'); + my $sort_html=("
+ + + + + +
"); # renderer call my $renderArgs = { 'cols' => [0,1,2,3], + 'sort' => $ENV{'form.sort'}, 'url' => '/adm/navmaps', 'navmap' => $navmap, 'suppressNavmap' => 1, 'suppressEmptySequences' => $suppressEmptySequences, 'filterFunc' => $filterFunc, 'resource_no_folder_link' => $resource_no_folder_link, - 'r' => $r}; + 'sort_html'=> $sort_html, + 'r' => $r, + 'caller' => 'navmapsdisplay', + 'linkitems' => \%toplinkitems}; my $render = render($renderArgs); - $navmap->untieHashes(); # If no resources were printed, print a reassuring message so the # user knows there was no error. @@ -296,15 +435,26 @@ sub removeFromFilter { # Convenience function: Given a stack returned from getStack on the iterator, # return the correct src() value. -# Later, this should add an anchor when we start putting anchors in pages. sub getLinkForResource { my $stack = shift; my $res; # Check to see if there are any pages in the stack foreach $res (@$stack) { - if (defined($res) && $res->is_page()) { - return $res->src(); + if (defined($res)) { + my $anchor; + if ($res->is_page()) { + foreach (@$stack) { if (defined($_)) { $anchor = $_; } } + $anchor=&Apache::lonnet::escape($anchor->shown_symb()); + return ($res->link(),$res->shown_symb(),$anchor); + } + # in case folder was skipped over as "only sequence" + my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb()); + if ($map=~/\.page$/) { + my $url=&Apache::lonnet::clutter($map); + $anchor=&Apache::lonnet::escape($src->shown_symb()); + return ($url,$res->shown_symb(),$anchor); + } } } @@ -316,12 +466,12 @@ sub getLinkForResource { if (defined($_)) { $res = $_; } } - return $res->src(); + return ($res->link(),$res->shown_symb()); } -# Convenience function: This seperates the logic of how to create +# Convenience function: This separates the logic of how to create # the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned", -# etc.) into a seperate function. It takes a resource object as the +# etc.) into a separate function. It takes a resource object as the # first parameter, and the part number of the resource as the second. # It's basically a big switch statement on the status of the resource. @@ -371,7 +521,7 @@ sub getDescription { $triesString = "$triesString"; } } - if ($res->duedate()) { + if ($res->duedate($part)) { return &mt("Due")." " . timeToHumanString($res->duedate($part)) . " $triesString"; } else { @@ -386,15 +536,15 @@ 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 { +sub dueInLessThan24Hours { my $res = shift; my $part = shift; my $status = $res->status($part); return ($status == $res->OPEN() || $status == $res->TRIES_LEFT()) && - $res->duedate() && $res->duedate() < time()+(24*60*60) && - $res->duedate() > time(); + $res->duedate($part) && $res->duedate($part) < time()+(24*60*60) && + $res->duedate($part) > time(); } # Convenience function, so others can use it: Is there only one try remaining for the @@ -406,8 +556,8 @@ sub lastTry { my $tries = $res->tries($part); my $maxtries = $res->maxtries($part); return $tries && $maxtries && $maxtries > 1 && - $maxtries - $tries == 1 && $res->duedate() && - $res->duedate() > time(); + $maxtries - $tries == 1 && $res->duedate($part) && + $res->duedate($part) > time(); } # This puts a human-readable name on the ENV variable. @@ -517,7 +667,7 @@ sub timeToHumanString { } # Not this year, so show the year - my $timeStr = strftime("on %A, %b %e %G at %I:%M %P", localtime($time)); + my $timeStr = strftime("on %A, %b %e %Y at %I:%M %P", localtime($time)); $timeStr =~ s/12:00 am/00:00/; $timeStr =~ s/12:00 pm/noon/; return $timeStr; @@ -864,43 +1014,48 @@ sub render_resource { my $nonLinkedText = ''; # stuff after resource title not in link my $link = $params->{"resourceLink"}; + + # The URL part is not escaped at this point, but the symb is... + # The stuff to the left of the ? must have ' replaced by \' since + # it will be quoted with ' in the href. + + my ($left,$right) = split(/\?/, $link); + $left =~ s/'/\\'/g; + $link = $left.'?'.$right; + my $src = $resource->src(); my $it = $params->{"iterator"}; my $filter = $it->{FILTER}; my $title = $resource->compTitle(); - if ($src =~ /^\/uploaded\//) { - $nonLinkedText=$title; - $title = ''; - } + my $partLabel = ""; my $newBranchText = ""; - + my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons"); # If this is a new branch, label it so if ($params->{'isNewBranch'}) { - $newBranchText = ""; + $newBranchText = ""; } # links to open and close the folder + + my $linkopen = ""; + + my $linkclose = ""; # Default icon: unknown page - my $icon = ""; + my $icon = ""; if ($resource->is_problem()) { if ($part eq '0' || $params->{'condensed'}) { - $icon = ''; + $icon =''; } else { $icon = $params->{'indentString'}; } } else { - my $curfext= (split (/\./,$resource->src))[-1]; - my $embstyle = &Apache::loncommon::fileembstyle($curfext); - # The unless conditional that follows is a bit of overkill - if (!(!defined($embstyle) || $embstyle eq 'unk' || $embstyle eq 'hdn')) { - $icon = ""; - } + $icon = ""; } # Display the correct map icon to open or shut map @@ -915,7 +1070,7 @@ sub render_resource { if (!$params->{'resource_no_folder_link'}) { $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif'; - $icon = ""; + $icon = ""; $linkopen = "{'queryString'} . '&filter='; @@ -928,11 +1083,12 @@ sub render_resource { '&jump=' . &Apache::lonnet::escape($resource->symb()) . "&folderManip=1'>"; + } else { # Don't allow users to manipulate folder $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.nomanip.gif'; - $icon = ""; + $icon = ""; $linkopen = ""; $linkclose = ""; @@ -955,6 +1111,7 @@ sub render_resource { } # Decide what to display + $result .= "$newBranchText$linkopen$icon$linkclose"; my $curMarkerBegin = ''; @@ -970,7 +1127,9 @@ sub render_resource { if ($resource->is_problem() && $part ne '0' && !$params->{'condensed'}) { - $partLabel = " (Part $part)"; + my $displaypart=$resource->part_display($part); + $partLabel = " (Part: $displaypart)"; + if ($link!~/\#/) { $link.='#'.&Apache::lonnet::escape($part); } $title = ""; } @@ -978,9 +1137,12 @@ sub render_resource { $nonLinkedText .= ' (' . $resource->countParts() . ' parts)'; } - if (!$params->{'resource_nolink'} && $src !~ /^\/uploaded\// && - !$resource->is_sequence()) { - $result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText"; + my $target; + if ($ENV{'environment.remotenavmap'} eq 'on') { + $target=' target="loncapaclient" '; + } + if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) { + $result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText"; } else { $result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText"; } @@ -995,10 +1157,10 @@ sub render_communication_status { my $link = $params->{"resourceLink"}; my $linkopen = ""; my $linkclose = ""; - + my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc"); if ($resource->hasDiscussion()) { $discussionHTML = $linkopen . - '' . + '' . $linkclose; } @@ -1008,7 +1170,7 @@ sub render_communication_status { if ($_) { $feedbackHTML .= ' ' - . ''; } } @@ -1016,11 +1178,14 @@ sub render_communication_status { if ($resource->getErrors()) { my $errors = $resource->getErrors(); + my $errorcount = 0; foreach (split(/,/, $errors)) { + last if ($errorcount>=10); # Only output 10 bombs maximum if ($_) { + $errorcount++; $errorHTML .= ' ' - . ''; } } @@ -1049,7 +1214,9 @@ sub render_quick_status { my $icon = $statusIconMap{$resource->simpleStatus($part)}; my $alt = $iconAltTags{$icon}; if ($icon) { - $result .= "$linkopen$alt$linkclose\n"; + my $location= + &Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon"); + $result .= "$linkopen$alt$linkclose\n"; } else { $result .= " \n"; } @@ -1069,7 +1236,7 @@ sub render_long_status { if ($resource->is_problem()) { $color = $colormap{$resource->status}; - if (dueInLessThen24Hours($resource, $part) || + if (dueInLessThan24Hours($resource, $part) || lastTry($resource, $part)) { $color = $hurryUpColor; } @@ -1121,35 +1288,53 @@ my @statuses = ($resObj->CORRECT, $resOb use Data::Dumper; sub render_parts_summary_status { my ($resource, $part, $params) = @_; - if (!$resource->is_problem()) { return ''; } + if (!$resource->is_problem() && !$resource->contains_problem) { return ''; } if ($params->{showParts}) { return ''; } my $td = "\n"; my $endtd = "\n"; + my @probs; - # If there is a single part, just show the simple status - if ($resource->singlepart()) { - my $status = $resource->simpleStatus('0'); - return $td . "" - . $statusStrings{$status} . "" . $endtd; - } - - # Now we can be sure the $part doesn't really matter. - my $statusCount = $resource->simpleStatusCount(); - my @counts; - foreach my $status(@statuses) { - # decouple display order from the simpleStatusCount order - my $slot = Apache::lonnavmaps::resource::statusToSlot($status); - if ($statusCount->[$slot]) { - push @counts, "" . $statusCount->[$slot] . ' ' + if ($resource->contains_problem) { + @probs=$resource->retrieveResources($resource,sub { $_[0]->is_problem() },1,0); + } else { + @probs=($resource); + } + my $return; + my %overallstatus; + my $totalParts; + foreach my $resource (@probs) { + # If there is a single part, just show the simple status + if ($resource->singlepart()) { + my $status = $resource->simpleStatus(${$resource->parts}[0]); + $overallstatus{$status}++; + $totalParts++; + next; + } + # Now we can be sure the $part doesn't really matter. + my $statusCount = $resource->simpleStatusCount(); + my @counts; + foreach my $status (@statuses) { + # decouple display order from the simpleStatusCount order + my $slot = Apache::lonnavmaps::resource::statusToSlot($status); + if ($statusCount->[$slot]) { + $overallstatus{$status}+=$statusCount->[$slot]; + $totalParts+=$statusCount->[$slot]; + } + } + } + $return.= $td . $totalParts . ' parts: '; + foreach my $status (@statuses) { + if ($overallstatus{$status}) { + $return.="" . $overallstatus{$status} . ' ' . $statusStrings{$status} . ""; } } - - return $td . $resource->countParts() . ' parts: ' . join (', ', @counts) . $endtd; + $return.= $endtd; + return $return; } my @preparedColumns = (\&render_resource, \&render_communication_status, @@ -1162,11 +1347,17 @@ sub setDefault { return $val; } +sub cmp_title { + my ($atitle,$btitle) = (lc($_[0]->compTitle),lc($_[1]->compTitle)); + $atitle=~s/^\s*//; + $btitle=~s/^\s*//; + return $atitle cmp $btitle; +} + sub render { my $args = shift; &Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING}); my $result = ''; - # Configure the renderer. my $cols = $args->{'cols'}; if (!defined($cols)) { @@ -1184,6 +1375,7 @@ sub render { my $jump = $args->{'jump'}; my $here = $args->{'here'}; my $suppressNavmap = setDefault($args->{'suppressNavmap'}, 0); + my $closeAllPages = setDefault($args->{'closeAllPages'}, 0); my $currentJumpDelta = 2; # change this to change how many resources are displayed # before the current resource when using #current @@ -1228,7 +1420,7 @@ sub render { # Determine where the "here" marker is and where the screen jumps to. if ($ENV{'form.postsymb'}) { - $here = $jump = $ENV{'form.postsymb'}; + $here = $jump = &Apache::lonnet::symbclean($ENV{'form.postsymb'}); } elsif ($ENV{'form.postdata'}) { # couldn't find a symb, is there a URL? my $currenturl = $ENV{'form.postdata'}; @@ -1246,7 +1438,7 @@ sub render { # 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 (($curRes = $mapIterator->next()) && !$found) { + while ($here && ($curRes = $mapIterator->next()) && !$found) { if (ref($curRes) && $curRes->symb() eq $here) { my $mapStack = $mapIterator->getStack(); @@ -1292,7 +1484,7 @@ sub render { $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition); } } - + # (re-)Locate the jump point, if any # Note this does not take filtering or hidden into account... need # to be fixed? @@ -1322,23 +1514,24 @@ sub render { my $printKey = $args->{'printKey'}; my $printCloseAll = $args->{'printCloseAll'}; if (!defined($printCloseAll)) { $printCloseAll = 1; } - + # Print key? if ($printKey) { $result .= ''; my $date=localtime; $result.=''; + my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc"); if ($navmap->{LAST_CHECK}) { $result .= - ' '.&mt('New discussion since').' '. + ' '.&mt('New discussion since').' '. strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})). ''; } else { $result .= ''; } @@ -1346,18 +1539,87 @@ sub render { } if ($printCloseAll && !$args->{'resource_no_folder_link'}) { + my ($link,$text); if ($condition) { - $result.="".&mt('Close All Folders').""; + $link='"navmaps?condition=0&filter=&'.$queryString. + '&here='.&Apache::lonnet::escape($here).'"'; + $text='Close All Folders'; } else { - $result.="".&mt('Open All Folders').""; + $link='"navmaps?condition=1&filter=&'.$queryString. + '&here='.&Apache::lonnet::escape($here).'"'; + $text='Open All Folders'; + } + if ($args->{'caller'} eq 'navmapsdisplay') { + &add_linkitem($args->{'linkitems'},'changefolder', + 'location.href='.$link,$text); + } else { + $result.=''.&mt($text).''; + } + $result .= "\n"; + } + + # Check for any unread discussions in all resources. + if ($args->{'caller'} eq 'navmapsdisplay') { + &add_linkitem($args->{'linkitems'},'clearbubbles', + 'document.clearbubbles.submit()', + 'Mark all posts read'); + my $time=time; + $result .= (< + + +END + if ($args->{'sort'} eq 'discussion') { + my $totdisc = 0; + my $haveDisc = ''; + my @allres=$navmap->retrieveResources(); + foreach my $resource (@allres) { + if ($resource->hasDiscussion()) { + my $ressymb; + if ($resource->symb() =~ m-(___adm/\w+/\w+)/(\d+)/bulletinboard$-) { + $ressymb = 'bulletin___'.$2.$1.'/'.$2.'/bulletinboard'; + } else { + $ressymb = $resource->symb(); + } + $haveDisc .= $ressymb.':'; + $totdisc ++; + } + } + if ($totdisc > 0) { + $haveDisc =~ s/:$//; + $result .= (< + +END + } + } + $result.=''; + } + + if ($args->{'caller'} eq 'navmapsdisplay') { + $result .= '
Key:    '. - ' '.&mt('New message (click to open)').'

'. + ' '.&mt('New message (click to open)').'

'. '

  '. - ' '.&mt('Discussions').''. - '   '.&mt('New message (click to open)'). + ' '.&mt('Discussions').''. + '   '.&mt('New message (click to open)'). '
'; + if ($ENV{'environment.remotenavmap'} ne 'on') { + $result .= ''; + } else { + $result .= ''; } - $result .= "

\n"; - } + $result.=&show_linkitems($args->{'linkitems'}); + if ($args->{'sort_html'}) { + if ($ENV{'environment.remotenavmap'} ne 'on') { + $result.=''. + ''; + } else { + $result.=''; + } + } + $result .= '
'. + &Apache::loncommon::help_open_menu('','Navigation Screen','Navigation_Screen','',undef,'RAT').' 
   '.$args->{'sort_html'}.'

'. + $args->{'sort_html'}.'
'; + } elsif ($args->{'sort_html'}) { + $result.=$args->{'sort_html'}; + } + $result .= "
\n"; if ($r) { $r->print($result); $r->rflush(); @@ -1379,7 +1641,9 @@ sub render { $args->{'indentLevel'} = 0; $args->{'isNewBranch'} = 0; $args->{'condensed'} = 0; - $args->{'indentString'} = setDefault($args->{'indentString'}, ""); + my $location= + &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace1.gif"); + $args->{'indentString'} = setDefault($args->{'indentString'}, ""); $args->{'displayedHereMarker'} = 0; # If we're suppressing empty sequences, look for them here. Use DFS for speed, @@ -1429,7 +1693,60 @@ sub render { $args->{'here'} = $here; $args->{'indentLevel'} = -1; # first BEGIN_MAP takes this to 0 - while ($curRes = $it->next()) { + my @resources; + my $code='';# sub { !(shift->is_map();) }; + if ($args->{'sort'} eq 'title') { + my $oldFilterFunc = $filterFunc; + my $filterFunc= + sub { + my ($res)=@_; + if ($res->is_map()) { return 0;} + return &$oldFilterFunc($res); + }; + @resources=$navmap->retrieveResources(undef,$filterFunc); + @resources= sort { &cmp_title($a,$b) } @resources; + } elsif ($args->{'sort'} eq 'duedate') { + my $oldFilterFunc = $filterFunc; + my $filterFunc= + sub { + my ($res)=@_; + if (!$res->is_problem()) { return 0;} + return &$oldFilterFunc($res); + }; + @resources=$navmap->retrieveResources(undef,$filterFunc); + @resources= sort { + if ($a->duedate ne $b->duedate) { + return $a->duedate cmp $b->duedate; + } + my $value=&cmp_title($a,$b); + return $value; + } @resources; + } elsif ($args->{'sort'} eq 'discussion') { + my $oldFilterFunc = $filterFunc; + my $filterFunc= + sub { + my ($res)=@_; + if (!$res->hasDiscussion() && + !$res->getFeedback() && + !$res->getErrors()) { return 0;} + return &$oldFilterFunc($res); + }; + @resources=$navmap->retrieveResources(undef,$filterFunc); + @resources= sort { &cmp_title($a,$b) } @resources; + } else { + #unknow sort mechanism or default + undef($args->{'sort'}); + } + + + while (1) { + if ($args->{'sort'}) { + $curRes = shift(@resources); + } else { + $curRes = $it->next($closeAllPages); + } + if (!$curRes) { last; } + # Maintain indentation level. if ($curRes == $it->BEGIN_MAP() || $curRes == $it->BEGIN_BRANCH() ) { @@ -1532,7 +1849,26 @@ sub render { # Add part 0 so we display it correctly. unshift @parts, '0'; } - + + { + my ($src,$symb,$anchor,$stack); + if ($args->{'sort'}) { + my $it = $navmap->getIterator(undef, undef, undef, 1); + while ( my $res=$it->next()) { + if (ref($res) && + $res->symb() eq $curRes->symb()) { last; } + } + $stack=$it->getStack(); + } else { + $stack=$it->getStack(); + } + ($src,$symb,$anchor)=getLinkForResource($stack); + if (defined($anchor)) { $anchor='#'.$anchor; } + my $srcHasQuestion = $src =~ /\?/; + $args->{"resourceLink"} = $src. + ($srcHasQuestion?'&':'?') . + 'symb=' . &Apache::lonnet::escape($symb).$anchor; + } # Now, we've decided what parts to show. Loop through them and # show them. foreach my $part (@parts) { @@ -1543,14 +1879,7 @@ sub render { # Set up some data about the parts that the cols might want my $filter = $it->{FILTER}; - my $stack = $it->getStack(); - my $src = getLinkForResource($stack); - - my $srcHasQuestion = $src =~ /\?/; - $args->{"resourceLink"} = $src. - ($srcHasQuestion?'&':'?') . - 'symb=' . &Apache::lonnet::escape($curRes->symb()); - + # Now, display each column. foreach my $col (@$cols) { my $colHTML = ''; @@ -1600,7 +1929,12 @@ sub render { # it's quite likely this might fix other browsers, too, and # certainly won't hurt anything. if ($displayedJumpMarker) { - $result .= "\n"; + $result .= " +"; } $result .= ""; @@ -1611,8 +1945,46 @@ sub render { $r->rflush(); } - if ($mustCloseNavMap) { $navmap->untieHashes(); } + return $result; +} + +sub add_linkitem { + my ($linkitems,$name,$cmd,$text)=@_; + $$linkitems{$name}{'cmd'}=$cmd; + $$linkitems{$name}{'text'}=&mt($text); +} +sub show_linkitems { + my ($linkitems)=@_; + my @linkorder = ("blank","launchnav","closenav","firsthomework", + "everything","uncompleted","changefolder","clearbubbles"); + + my $result .= (< + +
+   +
'."\n"; + return $result; } @@ -1673,10 +2045,6 @@ successful, or B if not. =back -When you are done with the $navmap object, you I call -$navmap->untieHashes(), or you'll prevent the current user from using that -course until the web server is restarted. (!) - =head2 Methods =over 4 @@ -1791,6 +2159,7 @@ sub generate_course_user_opt { sub generate_email_discuss_status { my $self = shift; + my $symb = shift; if ($self->{EMAIL_DISCUSS_GENERATED}) { return; } my $cid=$ENV{'request.course.id'}; @@ -1803,6 +2172,15 @@ sub generate_email_discuss_status { $courseLeaveTime : $logoutTime); my %discussiontime = &Apache::lonnet::dump('discussiontimes', $cdom, $cnum); + my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss', + $ENV{'user.domain'},$ENV{'user.name'},'lastread'); + my %lastreadtime = (); + foreach (keys %lastread) { + my $key = $_; + $key =~ s/_lastread$//; + $lastreadtime{$key} = $lastread{$_}; + } + my %feedback=(); my %error=(); my $keys = &Apache::lonnet::reply('keys:'. @@ -1812,17 +2190,11 @@ sub generate_email_discuss_status { foreach my $msgid (split(/\&/, $keys)) { $msgid=&Apache::lonnet::unescape($msgid); - my $plain=&Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid)); - if ($plain=~/(Error|Feedback) \[([^\]]+)\]/) { - my ($what,$url)=($1,$2); - my %status= - &Apache::lonnet::get('email_status',[$msgid]); - if ($status{$msgid}=~/^error\:/) { - $status{$msgid}=''; - } - - if (($status{$msgid} eq 'new') || - (!$status{$msgid})) { + if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) { + my $plain= + &Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid)); + if ($plain=~/(Error|Feedback) \[([^\]]+)\]/) { + my ($what,$url)=($1,$2); if ($what eq 'Error') { $error{$url}.=','.$msgid; } else { @@ -1832,10 +2204,13 @@ sub generate_email_discuss_status { } } + #url's of resources that have feedbacks $self->{FEEDBACK} = \%feedback; - $self->{ERROR_MSG} = \%error; # what is this? JB + #or errors + $self->{ERROR_MSG} = \%error; $self->{DISCUSSION_TIME} = \%discussiontime; $self->{EMAIL_STATUS} = \%emailstatus; + $self->{LAST_READ} = \%lastreadtime; $self->{EMAIL_DISCUSS_GENERATED} = 1; } @@ -1882,17 +2257,11 @@ sub courseMapDefined { sub getIterator { my $self = shift; my $iterator = Apache::lonnavmaps::iterator->new($self, shift, shift, - shift, undef, shift); + shift, undef, shift, + shift, shift); return $iterator; } -# unties the hash when done -sub untieHashes { - my $self = shift; - untie %{$self->{NAV_HASH}}; - untie %{$self->{PARM_HASH}}; -} - # Private method: Does the given resource (as a symb string) have # current discussion? Returns 0 if chat/mail data not extracted. sub hasDiscussion { @@ -1904,8 +2273,21 @@ sub hasDiscussion { if (!defined($self->{DISCUSSION_TIME})) { return 0; } #return defined($self->{DISCUSSION_TIME}->{$symb}); - return $self->{DISCUSSION_TIME}->{$symb} > - $self->{LAST_CHECK}; + +# backward compatibility (bulletin boards used to be 'wrapped') + my $ressymb = $symb; + if ($ressymb =~ m|adm/(\w+)/(\w+)/(\d+)/bulletinboard$|) { + unless ($ressymb =~ m|adm/wrapper/adm|) { + $ressymb = 'bulletin___'.$3.'___adm/wrapper/adm/'.$1.'/'.$2.'/'.$3.'/bulletinboard'; + } + } + + if ( defined ( $self->{LAST_READ}->{$ressymb} ) ) { + return $self->{DISCUSSION_TIME}->{$ressymb} > $self->{LAST_READ}->{$ressymb}; + } else { +# return $self->{DISCUSSION_TIME}->{$ressymb} > $self->{LAST_CHECK}; # v.1.1 behavior + return $self->{DISCUSSION_TIME}->{$ressymb} > 0; # in 1.2 will display speech bubble icons for all items with posts until marked as read (even if read in v 1.1). + } } # Private method: Does the given resource (as a symb string) have @@ -1976,9 +2358,14 @@ sub getById { sub getBySymb { my $self = shift; my $symb = shift; + my ($mapUrl, $id, $filename) = &Apache::lonnet::decode_symb($symb); my $map = $self->getResourceByUrl($mapUrl); - return $self->getById($map->map_pc() . '.' . $id); + my $returnvalue = undef; + if (ref($map)) { + $returnvalue = $self->getById($map->map_pc() .'.'.$id); + } + return $returnvalue; } sub getByMapPc { @@ -2091,8 +2478,6 @@ sub parmval_real { if (defined($courseopt)) { if (defined($$courseopt{$courselevelr})) { return $$courseopt{$courselevelr}; } - if (defined($$courseopt{$courselevelm})) { return $$courseopt{$courselevelm}; } - if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; } } # ----------------------------------------------------- third, check map parms @@ -2102,10 +2487,20 @@ sub parmval_real { # ----------------------------------------------------- fourth , check default - my $default=&Apache::lonnet::metadata($fn,$rwhat.'.default'); + my $meta_rwhat=$rwhat; + $meta_rwhat=~s/\./_/g; + my $default=&Apache::lonnet::metadata($fn,$meta_rwhat); if (defined($default)) { return $default} + $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat); + if (defined($default)) { return $default} + +# --------------------------------------------------- fifth, check more course + if (defined($courseopt)) { + if (defined($$courseopt{$courselevelm})) { return $$courseopt{$courselevelm}; } + if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; } + } -# --------------------------------------------------- fifth , cascade up parts +# --------------------------------------------------- sixth , cascade up parts my ($space,@qualifier)=split(/\./,$rwhat); my $qualifier=join('.',@qualifier); @@ -2134,7 +2529,7 @@ you're not sure if $res is already an ob resource appears multiple times in the course, only the first instance will be returned. As a result, this is probably useful only for maps. -=item * B(map, filterFunc, recursive, bailout): +=item * B(map, filterFunc, recursive, bailout, showall): The map is a specification of a map to retreive the resources from, either as a url or as an object. The filterFunc is a reference to a @@ -2142,13 +2537,14 @@ function that takes a resource object as true if the resource should be included, or false if it should not be. If recursive is true, the map will be recursively examined, otherwise it will not be. If bailout is true, the function will return -as soon as it finds a resource, if false it will finish. By default, -the map is the top-level map of the course, filterFunc is a function -that always returns 1, recursive is true, bailout is false. The -resources will be returned in a list containing the resource objects -for the corresponding resources, with B in -the list; regardless of branching, recursion, etc., it will be a flat -list. +as soon as it finds a resource, if false it will finish. If showall is +true it will not hide maps that contain nothing but one other map. By +default, the map is the top-level map of the course, filterFunc is a +function that always returns 1, recursive is true, bailout is false, +showall is false. The resources will be returned in a list containing +the resource objects for the corresponding resources, with B in the list; regardless of branching, +recursion, etc., it will be a flat list. Thus, this is suitable for cases where you don't want the structure, just a list of all resources. It is also suitable for finding out how @@ -2157,7 +2553,7 @@ want to know is if I resources matc parameter will allow you to avoid potentially expensive enumeration of all matching resources. -=item * B(map, filterFunc, recursive): +=item * B(map, filterFunc, recursive): Convience method for @@ -2168,6 +2564,7 @@ in the filter function. =cut + sub getResourceByUrl { my $self = shift; my $resUrl = shift; @@ -2194,7 +2591,7 @@ sub retrieveResources { if (!defined($recursive)) { $recursive = 1; } my $bailout = shift; if (!defined($bailout)) { $bailout = 0; } - + my $showall = shift; # Create the necessary iterator. if (!ref($map)) { # assume it's a url of a map. $map = $self->getResourceByUrl($map); @@ -2213,7 +2610,7 @@ sub retrieveResources { # Get an iterator. my $it = $self->getIterator($map->map_start(), $map->map_finish(), - undef, $recursive); + undef, $recursive, $showall); my @resources = (); @@ -2250,7 +2647,7 @@ sub hasResource { 1; package Apache::lonnavmaps::iterator; - +use WeakRef; =pod =back @@ -2390,7 +2787,7 @@ sub new { my $class = ref($proto) || $proto; my $self = {}; - $self->{NAV_MAP} = shift; + weaken($self->{NAV_MAP} = shift); return undef unless ($self->{NAV_MAP}); # Handle the parameters @@ -2503,14 +2900,15 @@ sub new { } # Check: Was this only one resource, a map? - if ($resourceCount == 1 && $resource->is_map() && !$self->{FORCE_TOP}) { + if ($resourceCount == 1 && $resource->is_sequence() && !$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); + $self->{CONDITION}, + $self->{FORCE_TOP}); } @@ -2536,7 +2934,7 @@ sub new { sub next { my $self = shift; - + my $closeAllPages=shift; if ($self->{FINISHED}) { return END_ITERATOR(); } @@ -2550,7 +2948,7 @@ sub next { if ($self->{RECURSIVE_ITERATOR_FLAG}) { # grab the next from the recursive iterator - my $next = $self->{RECURSIVE_ITERATOR}->next(); + my $next = $self->{RECURSIVE_ITERATOR}->next($closeAllPages); # is it a begin or end map? If so, update the depth if ($next == BEGIN_MAP() ) { $self->{RECURSIVE_DEPTH}++; } @@ -2664,7 +3062,7 @@ sub next { # That ends the main iterator logic. Now, do we want to recurse # down this map (if this resource is a map)? - if ($self->{HERE}->is_map() && + if ( ($self->{HERE}->is_sequence() || (!$closeAllPages && $self->{HERE}->is_page())) && (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) { $self->{RECURSIVE_ITERATOR_FLAG} = 1; my $firstResource = $self->{HERE}->map_start(); @@ -2673,7 +3071,9 @@ sub next { $self->{RECURSIVE_ITERATOR} = Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource, $finishResource, $self->{FILTER}, - $self->{ALREADY_SEEN}, $self->{CONDITION}); + $self->{ALREADY_SEEN}, + $self->{CONDITION}, + $self->{FORCE_TOP}); } # If this is a blank resource, don't actually return it. @@ -2682,7 +3082,7 @@ sub next { my $browsePriv = $self->{HERE}->browsePriv(); if (!$self->{HERE}->src() || (!($browsePriv eq 'F') && !($browsePriv eq '2')) ) { - return $self->next(); + return $self->next($closeAllPages); } return $self->{HERE}; @@ -2726,7 +3126,7 @@ sub populateStack { 1; package Apache::lonnavmaps::DFSiterator; - +use WeakRef; # Not documented in the perldoc: This is a simple iterator that just walks # through the nav map and presents the resources in a depth-first search # fashion, ignorant of conditionals, randomized resources, etc. It presents @@ -2734,7 +3134,7 @@ package Apache::lonnavmaps::DFSiterator; # useful for pre-processing of some kind, and is in fact used by the main # iterator that way, but that's about it. # One could imagine merging this into the init routine of the main iterator, -# but this might as well be left seperate, since it is possible some other +# but this might as well be left separate, 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. @@ -2754,7 +3154,7 @@ sub new { my $class = ref($proto) || $proto; my $self = {}; - $self->{NAV_MAP} = shift; + weaken($self->{NAV_MAP} = shift); return undef unless ($self->{NAV_MAP}); $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource(); @@ -2908,7 +3308,7 @@ sub populateStack { 1; package Apache::lonnavmaps::resource; - +use WeakRef; use Apache::lonnet; =pod @@ -2990,7 +3390,7 @@ sub new { my $class = ref($proto) || $proto; my $self = {}; - $self->{NAV_MAP} = shift; + weaken($self->{NAV_MAP} = shift); $self->{ID} = shift; # Store this new resource in the parent nav map's cache. @@ -3078,6 +3478,7 @@ Returns the title of the resource. # These info functions can be used directly, as they don't return # resource information. sub comesfrom { my $self=shift; return $self->navHash("comesfrom_", 1); } +sub encrypted { my $self=shift; return $self->navHash("encrypted_", 1); } sub ext { my $self=shift; return $self->navHash("ext_", 1) eq 'true:'; } sub from { my $self=shift; return $self->navHash("from_", 1); } # considered private and undocumented @@ -3089,10 +3490,20 @@ sub randompick { return $self->{NAV_MAP}->{PARM_HASH}->{$self->symb . '.0.parameter_randompick'}; } +sub link { + my $self=shift; + if ($self->encrypted()) { return &Apache::lonenc::encrypted($self->src); } + return $self->src; +} sub src { my $self=shift; return $self->navHash("src_", 1); } +sub shown_symb { + my $self=shift; + if ($self->encrypted()) {return &Apache::lonenc::encrypted($self->symb());} + return $self->symb(); +} sub symb { my $self=shift; (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/; @@ -3111,6 +3522,16 @@ sub title { return $self->navHash("title_", 1); } # considered private and undocumented sub to { my $self=shift; return $self->navHash("to_", 1); } +sub condition { + my $self=shift; + my $undercond=$self->navHash("undercond_", 1); + if (!defined($undercond)) { return 1; }; + my $condid=$self->navHash("condid_$undercond"); + if (!defined($condid)) { return 1; }; + my $condition=&Apache::lonnet::directcondval($condid); + return $condition; +} + sub compTitle { my $self = shift; my $title = $self->title(); @@ -3151,6 +3572,15 @@ Returns true if the resource is a sequen =cut +sub hasResource { + my $self = shift; + return $self->{NAV_MAP}->hasResource(@_); +} + +sub retrieveResources { + my $self = shift; + return $self->{NAV_MAP}->retrieveResources(@_); +} sub is_html { my $self=shift; @@ -3167,7 +3597,15 @@ sub is_page { sub is_problem { my $self=shift; my $src = $self->src(); - return ($src =~ /problem$/); + return ($src =~ /\.(problem|exam|quiz|assess|survey|form|library)$/) +} +sub contains_problem { + my $self=shift; + if ($self->is_page()) { + my $hasProblem=$self->hasResource($self,sub { $_[0]->is_problem() },1); + return $hasProblem; + } + return 0; } sub is_sequence { my $self=shift; @@ -3175,6 +3613,23 @@ sub is_sequence { return $self->navHash("is_map_", 1) && $self->navHash("map_type_" . $self->map_pc()) eq 'sequence'; } +sub is_survey { + my $self = shift(); + my $part = shift(); + if ($self->parmval('type',$part) eq 'survey') { + return 1; + } + if ($self->src() =~ /\.(survey)$/) { + return 1; + } + return 0; +} + +sub is_empty_sequence { + my $self=shift; + my $src = $self->src(); + return !$self->is_page() && $self->navHash("is_map_", 1) && !$self->navHash("map_type_" . $self->map_pc()); +} # Private method: Shells out to the parmval in the nav map, handler parts. sub parmval { @@ -3351,6 +3806,11 @@ sub awarded { } sub duedate { (my $self, my $part) = @_; + my $interval=$self->parmval("interval", $part); + if ($interval) { + my $first_access=&Apache::lonnet::get_first_access('map',$self->symb); + if ($first_access) { return ($first_access+$interval); } + } return $self->parmval("duedate", $part); } sub maxtries { @@ -3394,7 +3854,16 @@ sub weight { $self->symb(), $ENV{'user.domain'}, $ENV{'user.name'}, $ENV{'request.course.sec'}); - +} +sub part_display { + my $self= shift(); my $partID = shift(); + if (! defined($partID)) { $partID = '0'; } + my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display', + $self->symb); + if (! defined($display) || $display eq '') { + $display = $partID; + } + return $display; } # Multiple things need this @@ -3484,6 +3953,16 @@ Returns the number of parts of the probl for single part problems, returns 1. For multipart, it returns the number of parts in the problem, not including psuedo-part 0. +=item * B(): + +Returns the total number of responses in the problem a student can answer. + +=item * B(): + +Returns a hash whose keys are the response types. The values are the number +of times each response type is used. This is for the I problem, not +just a single part. + =item * B(): Returns true if the problem is multipart, false otherwise. Use this instead @@ -3530,6 +4009,26 @@ sub countParts { return scalar(@{$parts}); # + $delta; } +sub countResponses { + my $self = shift; + my $count; + foreach my $part (@{$self->parts()}) { + $count+= scalar($self->responseIds($part)); + } + return $count; +} + +sub responseTypes { + my $self = shift; + my %responses; + foreach my $part ($self->parts()) { + foreach my $responsetype ($self->responseType($part)) { + $responses{$responsetype}++ if (defined($responsetype)); + } + } + return %responses; +} + sub multipart { my $self = shift; return $self->countParts() > 1; @@ -3617,6 +4116,7 @@ sub extractParts { } + # These hashes probably do not need names that end with "Hash".... my %responseIdHash; my %responseTypeHash; @@ -3651,6 +4151,30 @@ sub extractParts { } } } + my $resorder = &Apache::lonnet::metadata($self->src(),'responseorder'); + # + # Reorder the arrays in the %responseIdHash and %responseTypeHash + if ($resorder) { + my @resorder=split(/,/,$resorder); + foreach my $part (keys(%responseIdHash)) { + my $i=0; + my %resids = map { ($_,$i++) } @{ $responseIdHash{$part} }; + my @neworder; + foreach my $possibleid (@resorder) { + if (exists($resids{$possibleid})) { + push(@neworder,$resids{$possibleid}); + } + } + my @ids; + my @type; + foreach my $element (@neworder) { + push (@ids,$responseIdHash{$part}->[$element]); + push (@type,$responseTypeHash{$part}->[$element]); + } + $responseIdHash{$part}=\@ids; + $responseTypeHash{$part}=\@type; + } + } $self->{RESPONSE_IDS} = \%responseIdHash; $self->{RESPONSE_TYPES} = \%responseTypeHash; } @@ -3842,8 +4366,9 @@ sub getCompletionStatus { my $status = $self->queryRestoreHash('solved', shift); - # Left as seperate if statements in case we ever do more with this + # Left as separate if statements in case we ever do more with this if ($status eq 'correct_by_student') {return $self->CORRECT;} + if ($status eq 'correct_by_scantron') {return $self->CORRECT;} if ($status eq 'correct_by_override') {return $self->CORRECT_BY_OVERRIDE; } if ($status eq 'incorrect_attempted') {return $self->INCORRECT; } if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; } @@ -3994,7 +4519,7 @@ sub status { if ($dateStatus == PAST_DUE_ANSWER_LATER || $dateStatus == PAST_DUE_NO_ANSWER ) { - return $dateStatus; + return $suppressFeedback ? ANSWER_SUBMITTED : $dateStatus; } if ($dateStatus == ANSWER_OPEN) { @@ -4180,6 +4705,7 @@ sub getNext { my $to = $self->to(); foreach my $branch ( split(/,/, $to) ) { my $choice = $self->{NAV_MAP}->getById($branch); + if (!$choice->condition()) { next; } my $next = $choice->goesto(); $next = $self->{NAV_MAP}->getById($next); @@ -4208,7 +4734,8 @@ sub browsePriv { return $self->{BROWSE_PRIV}; } - $self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre', $self->src()); + $self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre',$self->src(), + $self->symb()); } =pod