--- loncom/interface/lonnavmaps.pm 2004/03/08 19:41:28 1.252 +++ loncom/interface/lonnavmaps.pm 2004/08/27 20:34:13 1.280 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.252 2004/03/08 19:41:28 matthew Exp $ +# $Id: lonnavmaps.pm,v 1.280 2004/08/27 20:34:13 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -83,6 +83,73 @@ my %colormap = # is not yet done and due in less then 24 hours my $hurryUpColor = "#FF0000"; +sub launch_win { + my ($mode,$script)=@_; + my $result; + if ($script ne 'no') { + $result.=''; + } + if ($mode eq 'link') { + $result.='' + .&mt("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 nav_control_js { + my $nav=($ENV{'environment.remotenavmap'} eq 'on'); + return (< + +ENDUPDATE +} + sub handler { my $r = shift; real_handler($r); @@ -111,6 +178,46 @@ sub real_handler { &Apache::loncommon::no_cache($r); $r->send_http_header; + 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(); @@ -123,22 +230,39 @@ 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']); # ----------------------------------------------------- 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'})); - $r->print(''); - + $addentries,$body_only,'', + $ENV{'form.register'})); + $r->print(''. + &Apache::loncommon::help_open_menu('','Navigation Screen','Navigation_Screen','',undef,'RAT')); + $r->rflush(); # Check that it's defined @@ -172,6 +296,27 @@ 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')); + } + if ($ENV{'environment.remotenavmap'} eq 'on') { +# $r->print("" . + $r->print("" . + &mt("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') { @@ -217,29 +362,45 @@ 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(); }; + $r->print("" . + &mt("Show Everything")."    "); $r->print("

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

"); $ENV{'form.filter'} = ''; $ENV{'form.condition'} = 1; $resource_no_folder_link = 1; } else { - $r->print("" . + $r->print("" . &mt("Show Only Uncompleted Homework")."    "); } + my %selected=($ENV{'form.sort'} => 'selected=on'); + my $sort_html=("
+ + + + + +
"); # renderer call - my $renderArgs = { 'cols' => [0,1,2,3], + my $renderArgs = { 'cols' => [0,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, + 'sort_html'=> $sort_html, 'r' => $r}; my $render = render($renderArgs); $navmap->untieHashes(); @@ -512,7 +673,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,10 +1025,7 @@ sub render_resource { my $filter = $it->{FILTER}; my $title = $resource->compTitle(); - if ($src =~ /^\/uploaded\//) { - $nonLinkedText=$title; - $title = ''; - } + my $partLabel = ""; my $newBranchText = ""; @@ -961,9 +1119,7 @@ sub render_resource { if ($resource->is_problem() && $part ne '0' && !$params->{'condensed'}) { - my $displaypart=&Apache::lonnet::EXT('resource.'.$part.'.display', - $resource->symb()); - unless ($displaypart) { $displaypart=$part; } + my $displaypart=$resource->part_display($part); $partLabel = " (Part: $displaypart)"; $link.='#'.&Apache::lonnet::escape($part); $title = ""; @@ -973,9 +1129,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"; } @@ -1011,8 +1170,11 @@ 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 .= ' ' . '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, @@ -1179,6 +1359,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 @@ -1287,7 +1468,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? @@ -1350,9 +1531,46 @@ sub render { "&here=" . Apache::lonnet::escape($here) . "\">".&mt('Open All Folders').""; } - $result .= "

\n"; - } + $result .= "\n"; + } + # Check for any unread discussions in all resources. + if (!$args->{'resource_no_folder_link'}) { + 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/:$//; + my %lt = &Apache::lonlocal::texthash( + 'mapr' => 'Mark all posts read', + ); + $result .= (<$lt{'mapr'} (Help: NavMaps_MarkPostsLink) +
+ + +
+END + } else { + $result .= '
'; + } + } + + if ($args->{'sort_html'}) { $result.=$args->{'sort_html'}; } + + $result .= "
\n"; if ($r) { $r->print($result); $r->rflush(); @@ -1424,7 +1642,43 @@ 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 {lc($a->compTitle) cmp lc($b->compTitle)} @resources; + } elsif ($args->{'sort'} eq 'duedate') { + @resources=$navmap->retrieveResources(undef, + sub { shift->is_problem(); }); + @resources= sort + { + if ($a->duedate ne $b->duedate) { + return $a->duedate cmp $b->duedate; + } else { + lc($a->compTitle) cmp lc($b->compTitle) + } + } @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() ) { @@ -1538,10 +1792,15 @@ 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 $src; + if ($args->{'sort'}) { + $src = $curRes->src(); # FIXME this is wrong for .pages + } else { + my $stack = $it->getStack(); + $src=getLinkForResource($stack); + } my $anchor=''; - if ($src=~s/(\#.*$)//) { + if ($src=~s/(\#.*)$//) { $anchor=$1; } my $srcHasQuestion = $src =~ /\?/; @@ -1549,7 +1808,7 @@ sub render { ($srcHasQuestion?'&':'?') . 'symb=' . &Apache::lonnet::escape($curRes->symb()). $anchor; - + # Now, display each column. foreach my $col (@$cols) { my $colHTML = ''; @@ -1795,6 +2054,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'}; @@ -1807,6 +2067,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:'. @@ -1840,6 +2109,7 @@ sub generate_email_discuss_status { $self->{ERROR_MSG} = \%error; # what is this? JB $self->{DISCUSSION_TIME} = \%discussiontime; $self->{EMAIL_STATUS} = \%emailstatus; + $self->{LAST_READ} = \%lastreadtime; $self->{EMAIL_DISCUSS_GENERATED} = 1; } @@ -1908,8 +2178,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 @@ -1980,9 +2263,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 { @@ -2165,7 +2453,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 @@ -2176,6 +2464,7 @@ in the filter function. =cut + sub getResourceByUrl { my $self = shift; my $resUrl = shift; @@ -2511,7 +2800,7 @@ 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 @@ -2544,7 +2833,7 @@ sub new { sub next { my $self = shift; - + my $closeAllPages=shift; if ($self->{FINISHED}) { return END_ITERATOR(); } @@ -2558,7 +2847,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}++; } @@ -2672,7 +2961,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(); @@ -2690,7 +2979,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}; @@ -3159,6 +3448,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; @@ -3175,7 +3473,15 @@ sub is_page { sub is_problem { my $self=shift; my $src = $self->src(); - return ($src =~ /\.(problem|exam|quiz|assess|survey|form|library)$/); + 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; @@ -3183,6 +3489,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 { @@ -3359,6 +3682,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 { @@ -3402,7 +3730,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 @@ -3659,6 +3996,20 @@ sub extractParts { } } } + my $resorder = &Apache::lonnet::metadata($self->src(),'responseorder'); + if ($resorder) { + my @resorder=split(/,/,$resorder); + foreach my $part (keys(%responseIdHash)) { + my %resids = map { ($_,1) } @{ $responseIdHash{$part} }; + my @neworder; + foreach my $possibleid (@resorder) { + if (exists($resids{$possibleid})) { + push(@neworder,$possibleid); + } + } + $responseIdHash{$part}=\@neworder; + } + } $self->{RESPONSE_IDS} = \%responseIdHash; $self->{RESPONSE_TYPES} = \%responseTypeHash; } @@ -4002,7 +4353,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) { 500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.