--- loncom/interface/lonnavmaps.pm 2006/05/30 12:46:09 1.384 +++ loncom/interface/lonnavmaps.pm 2006/12/24 22:13:19 1.395 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.384 2006/05/30 12:46:09 www Exp $ +# $Id: lonnavmaps.pm,v 1.395 2006/12/24 22:13:19 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -31,9 +31,7 @@ package Apache::lonnavmaps; use strict; use GDBM_File; -use Apache::Constants qw(:common :http); use Apache::loncommon(); -use Apache::lonmenu(); use Apache::lonenc(); use Apache::lonlocal; use Apache::lonnet; @@ -91,34 +89,6 @@ my %colormap = # is not yet done and due in less then 24 hours my $hurryUpColor = "#FF0000"; -sub launch_win { - my ($mode,$script,$toplinkitems,$firsttime)=@_; - 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(<header_only) { - if ($env{'browser.mathml'}) { - &Apache::loncommon::content_type($r,'text/xml'); - } else { - &Apache::loncommon::content_type($r,'text/html'); - } - $r->send_http_header; - return OK; - } - - # Send header, don't cache this page - if ($env{'browser.mathml'}) { - &Apache::loncommon::content_type($r,'text/xml'); - } else { - &Apache::loncommon::content_type($r,'text/html'); - } - &Apache::loncommon::no_cache($r); - - 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=(<send_http_header; - my $js =<<"ENDSUBM"; - -ENDSUBM - $r->print(&Apache::loncommon::start_page(undef,$js, - {'only_body' => 1, - 'bgcolor' => '#FFFFFF', - 'add_entries' => - {'onload' => - "submitthis()"}}). - &Apache::loncommon::end_page()); - - return OK; - } - if ($ENV{QUERY_STRING} =~ /^launchExternal/) { - &Apache::lonnet::put('environment',{'remotenavmap' => 'on'}); - &Apache::lonnet::appenv('environment.remotenavmap' => 'on'); - my $menu=&Apache::lonmenu::reopenmenu(); - my $navstatus=&Apache::lonmenu::get_nav_status(); - if ($menu) { - $r->print(< - swmenu=$menu - swmenu.clearTimeout(swmenu.menucltim); - $navstatus - -MENU - } - } - if ($ENV{QUERY_STRING} eq 'turningOffExternal') { - $env{'environment.remotenavmap'}='off'; - } - - # Create the nav map - my $navmap = Apache::lonnavmaps::navmap->new(); - - if (!defined($navmap)) { - my $requrl = $r->uri; - $env{'user.error.msg'} = "$requrl:bre:0:0:Course not initialized"; - return HTTP_NOT_ACCEPTABLE; - } - $r->send_http_header; - -# ------------------------------------------------------------ Get query string - &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register','sort','showOnlyHomework','postsymb']); - -# ----------------------------------------------------- Force menu registration - my $body_only=''; - my $js; - if ($env{'environment.remotenavmap'} eq 'on') { - $js=''; - $body_only=1; - } - - # Header - $r->print(&Apache::loncommon::start_page('Navigate Course Contents',$js, - {'only_body' => $body_only, - 'force_register' => - $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(''.&mt('Coursemap undefined.'). - '' . - &Apache::loncommon::end_page()); - return OK; - } - - # See if there's only one map in the top-level, if we don't - # already have a filter... if so, automatically display it - # (older code; should use retrieveResources) - if ($ENV{QUERY_STRING} !~ /filter/) { - my $iterator = $navmap->getIterator(undef, undef, undef, 0); - my $curRes; - my $sequenceCount = 0; - my $sequenceId; - while ($curRes = $iterator->next()) { - if (ref($curRes) && $curRes->is_sequence()) { - $sequenceCount++; - $sequenceId = $curRes->map_pc(); - } - } - - if ($sequenceCount == 1) { - # The automatic iterator creation in the render call - # will pick this up. We know the condition because - # the defined($env{'form.filter'}) also ensures this - # is a fresh call. - $env{'form.filter'} = "$sequenceId"; - } - } - - 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"); - } - - - # Check to see if the student is jumping to next open, do-able problem - if ($ENV{QUERY_STRING} =~ /^jumpToFirstHomework/) { - # Find the next homework problem that they can do. - my $iterator = $navmap->getIterator(undef, undef, undef, 1); - my $curRes; - my $foundDoableProblem = 0; - my $minimumduedate; - - while ($curRes = $iterator->next()) { - if (ref($curRes) && $curRes->is_problem()) { - my $status = $curRes->status(); - if ($curRes->completable()) { - my $thisduedate=$curRes->duedate(); - unless ($foundDoableProblem) { - $minimumduedate=$thisduedate; - } - - $foundDoableProblem = 1; - - if ($thisduedate<=$minimumduedate) { - # Pop open all previous maps - my $stack = $iterator->getStack(); - pop @$stack; # last resource in the stack is the problem - # itself, which we don't need in the map stack - my @mapPcs = map {$_->map_pc()} @$stack; - $env{'form.filter'} = join(',', @mapPcs); - - # Mark as both "here" and "jump" - $env{'form.postsymb'} = $curRes->symb(); - $minimumduedate=$thisduedate; - } - } - } - } - - # If we found no problems, print a note to that effect. - if (!$foundDoableProblem) { - $r->print("All homework assignments have been completed.

"); - } - } else { - &add_linkitem(\%toplinkitems,'firsthomework', - 'location.href="navmaps?jumpToFirstHomework"', - "Show my first due problem"); - } - - my $suppressEmptySequences = 0; - my $filterFunc = undef; - my $resource_no_folder_link = 0; - - # Display only due homework. - my $showOnlyHomework = 0; - 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 Problems")."

"); - $env{'form.filter'} = ''; - $env{'form.condition'} = 1; - $resource_no_folder_link = 1; - } else { - &add_linkitem(\%toplinkitems,'uncompleted', - 'location.href="navmaps?sort='.$env{'form.sort'}. - '&showOnlyHomework=1"', - "Show only uncompleted problems"); - } - - 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, - 'sort_html'=> $sort_html, - 'r' => $r, - 'caller' => 'navmapsdisplay', - 'linkitems' => \%toplinkitems}; - my $render = render($renderArgs); - - # If no resources were printed, print a reassuring message so the - # user knows there was no error. - if ($renderArgs->{'counter'} == 0) { - if ($showOnlyHomework) { - $r->print("

".&mt("All homework is currently completed").".

"); - } else { # both jumpToFirstHomework and normal use the same: course must be empty - $r->print("

This course is empty.

"); - } - } - #my $td=&tv_interval($t0); - #$r->print("
$td"); - - $r->print(&Apache::loncommon::end_page()); - $r->rflush(); - - return OK; -} - # Convenience functions: Returns a string that adds or subtracts # the second argument from the first hash, appropriate for the # query string that determines which folders to recurse on @@ -470,7 +149,7 @@ sub getLinkForResource { if (defined($res)) { my $anchor; if ($res->is_page()) { - foreach (@$stack) { if (defined($_)) { $anchor = $_; } } + foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; } } $anchor=&escape($anchor->shown_symb()); return ($res->link(),$res->shown_symb(),$anchor); } @@ -488,8 +167,8 @@ sub getLinkForResource { # (when we first recurse on a map, it puts an undefined resource # on the bottom because $self->{HERE} isn't defined yet, and we # want the src for the map anyhow) - foreach (@$stack) { - if (defined($_)) { $res = $_; } + foreach my $item (@$stack) { + if (defined($item)) { $res = $item; } } return ($res->link(),$res->shown_symb()); @@ -1093,7 +772,13 @@ sub render_resource { if ($resource->is_problem()) { if ($part eq '0' || $params->{'condensed'}) { - $icon =''.&mt('Problem').''; + $icon = ''.&mt('Task');
+	    } else {
+		$icon .= 'problem.gif'; } else { $icon = $params->{'indentString'}; } @@ -1118,16 +803,16 @@ sub render_resource { ($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" border='0' />"; $linkopen = "{'url'} . '?' . - $params->{'queryString'} . '&filter='; + $params->{'queryString'} . '&filter='; $linkopen .= ($nowOpen xor $it->{CONDITION}) ? addToFilter($filter, $mapId) : removeFromFilter($filter, $mapId); - $linkopen .= "&condition=" . $it->{CONDITION} . '&hereType=' - . $params->{'hereType'} . '&here=' . + $linkopen .= "&condition=" . $it->{CONDITION} . '&hereType=' + . $params->{'hereType'} . '&here=' . &escape($params->{'here'}) . - '&jump=' . + '&jump=' . &escape($resource->symb()) . - "&folderManip=1\">"; + "&folderManip=1\">"; } else { # Don't allow users to manipulate folder @@ -1149,7 +834,7 @@ sub render_resource { } # We're done preparing and finally ready to start the rendering - my $result = ""; + my $result = ""; my $indentLevel = $params->{'indentLevel'}; if ($newBranchText) { $indentLevel--; } @@ -1213,17 +898,17 @@ sub render_communication_status { my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc"); if ($resource->hasDiscussion()) { $discussionHTML = $linkopen . - '' . + ''.&mt('New Discussion').'' . $linkclose; } if ($resource->getFeedback()) { my $feedback = $resource->getFeedback(); - foreach (split(/\,/, $feedback)) { - if ($_) { + foreach my $msgid (split(/\,/, $feedback)) { + if ($msgid) { $feedbackHTML .= ' ' - . '' + . ''.&mt('New Email').''; } } @@ -1232,13 +917,13 @@ sub render_communication_status { if ($resource->getErrors()) { my $errors = $resource->getErrors(); my $errorcount = 0; - foreach (split(/,/, $errors)) { + foreach my $msgid (split(/,/, $errors)) { last if ($errorcount>=10); # Only output 10 bombs maximum - if ($_) { + if ($msgid) { $errorcount++; $errorHTML .= ' ' - . '' + . ''.&mt('New Error').''; } } @@ -1248,7 +933,7 @@ sub render_communication_status { $discussionHTML = $feedbackHTML = $errorHTML = ''; } - return "$discussionHTML$feedbackHTML$errorHTML "; + return "$discussionHTML$feedbackHTML$errorHTML "; } sub render_quick_status { @@ -1273,7 +958,7 @@ sub render_quick_status { if ($icon) { my $location= &Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon"); - $result .= "$linkopen$alt$linkclose\n"; + $result .= "$linkopen$alt$linkclose\n"; } else { $result .= " \n"; } @@ -1285,7 +970,7 @@ sub render_quick_status { } sub render_long_status { my ($resource, $part, $params) = @_; - my $result = "\n"; + my $result = "\n"; my $firstDisplayed = !$params->{'condensed'} && $params->{'multipart'} && $part eq "0"; @@ -1441,9 +1126,9 @@ sub render { # marker my $filterHash = {}; # Figure out what we're not displaying - foreach (split(/\,/, $env{"form.filter"})) { - if ($_) { - $filterHash->{$_} = "1"; + foreach my $item (split(/\,/, $env{"form.filter"})) { + if ($item) { + $filterHash->{$item} = "1"; } } @@ -1471,7 +1156,7 @@ sub render { $navmap = Apache::lonnavmaps::navmap->new(); if (!defined($navmap)) { # no londer in course - return ''.&mt('No course selected').'
+ return ''.&mt('No course selected').'
'.&mt('Select a course').'
'; } } @@ -1609,11 +1294,11 @@ sub render { if ($printCloseAll && !$args->{'resource_no_folder_link'}) { my ($link,$text); if ($condition) { - $link='"navmaps?condition=0&filter=&'.$queryString. + $link='"navmaps?condition=0&filter=&'.$queryString. '&here='.&escape($here).'"'; $text='Close all folders'; } else { - $link='"navmaps?condition=1&filter=&'.$queryString. + $link='"navmaps?condition=1&filter=&'.$queryString. '&here='.&escape($here).'"'; $text='Open all folders'; } @@ -1660,7 +1345,7 @@ END if ($args->{'caller'} eq 'navmapsdisplay') { $result .= ''; + &Apache::loncommon::help_open_menu('Navigation Screen','Navigation_Screen',undef,'RAT').''; if ($env{'environment.remotenavmap'} ne 'on') { $result .= ''; } else { @@ -2216,10 +1901,10 @@ sub generate_email_discuss_status { 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{$_}; + foreach my $key (keys %lastread) { + my $shortkey = $key; + $shortkey =~ s/_lastread$//; + $lastreadtime{$shortkey} = $lastread{$key}; } my %feedback=(); @@ -2229,22 +1914,32 @@ sub generate_email_discuss_status { foreach my $msgid (@keys) { if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) { - my $plain= - &LONCAPA::unescape(&LONCAPA::unescape($msgid)); - if ($plain=~/ \[([^\]]+)\]\:/) { - my $url=$1; - if ($plain=~/\:Error \[/) { - $error{$url}.=','.$msgid; - } else { - $feedback{$url}.=','.$msgid; - } - } + my ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$fromcid, + $symb,$error) = &Apache::lonmsg::unpackmsgid($msgid); + if (defined($symb)) { + if (defined($error) && $error == 1) { + $error{$symb}.=','.$msgid; + } else { + $feedback{$symb}.=','.$msgid; + } + } else { + my $plain= + &LONCAPA::unescape(&LONCAPA::unescape($msgid)); + if ($plain=~/ \[([^\]]+)\]\:/) { + my $url=$1; + if ($plain=~/\:Error \[/) { + $error{$url}.=','.$msgid; + } else { + $feedback{$url}.=','.$msgid; + } + } + } } } - #url's of resources that have feedbacks + #symbs of resources that have feedbacks (will be urls pre-2.3) $self->{FEEDBACK} = \%feedback; - #or errors + #or errors (will be urls pre 2.3) $self->{ERROR_MSG} = \%error; $self->{DISCUSSION_TIME} = \%discussiontime; $self->{EMAIL_STATUS} = \%emailstatus; @@ -2349,9 +2044,10 @@ sub last_post_time { return $self->{DISCUSSION_TIME}->{$ressymb}; } -sub unread_discussion { +sub discussion_info { my $self = shift; my $symb = shift; + my $filter = shift; $self->get_discussion_data(); @@ -2363,12 +2059,10 @@ sub unread_discussion { my $prevread = $self->{LAST_READ}{$ressymb}; - my $unreadcount = 0; + my $count = 0; my $hiddenflag = 0; my $deletedflag = 0; - my ($hidden,$deleted); - - my %subjects; + my ($hidden,$deleted,%info); for (my $id=$version; $id>0; $id--) { my $vkeys=$self->{DISCUSSION_DATA}{$id.':keys:'.$discsymb}; @@ -2384,18 +2078,24 @@ sub unread_discussion { $deletedflag = 1; } } else { - if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./) - && $prevread < $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'}) { - $unreadcount++; - $subjects{$unreadcount}= - $id.': '.$self->{DISCUSSION_DATA}{$id.':'.$discsymb.':subject'}; - } + if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./)) { + if ($filter eq 'unread') { + if ($prevread >= $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'}) { + next; + } + } + $count++; + $info{$count}{'subject'} = + $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':subject'}; + $info{$count}{'id'} = $id; + $info{$count}{'timestamp'} = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'}; + } } } if (wantarray) { - return ($unreadcount,\%subjects); + return ($count,%info); } - return $unreadcount + return $count; } sub wrap_symb { @@ -2426,23 +2126,48 @@ sub unwrap_symb { sub getFeedback { my $self = shift; my $symb = shift; + my $source = shift; $self->generate_email_discuss_status(); if (!defined($self->{FEEDBACK})) { return ""; } - return $self->{FEEDBACK}->{$symb}; + my $feedback; + if ($self->{FEEDBACK}->{$symb}) { + $feedback = $self->{FEEDBACK}->{$symb}; + if ($self->{FEEDBACK}->{$source}) { + $feedback .= ','.$self->{FEEDBACK}->{$source}; + } + } else { + if ($self->{FEEDBACK}->{$source}) { + $feedback = $self->{FEEDBACK}->{$source}; + } + } + return $feedback; } # Private method: Get the errors for that resource (by source). sub getErrors { my $self = shift; + my $symb = shift; my $src = shift; $self->generate_email_discuss_status(); if (!defined($self->{ERROR_MSG})) { return ""; } - return $self->{ERROR_MSG}->{$src}; + + my $errors; + if ($self->{ERROR_MSG}->{$symb}) { + $errors = $self->{ERROR_MSG}->{$symb}; + if ($self->{ERROR_MSG}->{$src}) { + $errors .= ','.$self->{ERROR_MSG}->{$src}; + } + } else { + if ($self->{ERROR_MSG}->{$src}) { + $errors = $self->{ERROR_MSG}->{$src}; + } + } + return $errors; } =pod @@ -3432,9 +3157,9 @@ sub next { # filter the next possibilities to remove things we've # already seen. - foreach (@$nextUnfiltered) { - if (!defined($self->{ALREADY_SEEN}->{$_->{ID}})) { - push @$next, $_; + foreach my $item (@$nextUnfiltered) { + if (!defined($self->{ALREADY_SEEN}->{$item->{ID}})) { + push @$next, $item; } } @@ -4037,7 +3762,7 @@ sub duedate { my $date; my $interval=$self->parmval("interval", $part); my $due_date=$self->parmval("duedate", $part); - if (defined($interval)) { + if ($interval =~ /\d+/) { my $first_access=&Apache::lonnet::get_first_access('map',$self->symb); if (defined($first_access)) { $interval = $first_access+$interval; @@ -4150,13 +3875,15 @@ data was not extracted when the nav map Returns a false value if there hasn't been discussion otherwise returns unix timestamp of last time a discussion posting (or edit) was made. -=item * B: +=item * B: -returns in scalar context the count of the number of unread discussion -postings +optional argument is a filter (currently can be 'unread'); +returns in scalar context the count of the number of discussion postings. returns in list context both the count of postings and a hash ref -containing the subjects of all unread postings +containing information about the postings (subject, id, timestamp) in a hash. + +Default is to return counts for all postings. However if called with a second argument set to 'unread', will return information about only unread postings. =item * B: @@ -4165,8 +3892,8 @@ for the resource, or the null string if email data was not extracted when the nav map was constructed. Usually used like this: - for (split(/\,/, $res->getFeedback())) { - my $link = &escape($_); + for my $url (split(/\,/, $res->getFeedback())) { + my $link = &escape($url); ... and use the link as appropriate. @@ -4183,23 +3910,25 @@ sub last_post_time { return $self->{NAV_MAP}->last_post_time($self->symb()); } -sub unread_discussion { - my $self = shift; - return $self->{NAV_MAP}->unread_discussion($self->symb()); +sub discussion_info { + my ($self,$filter) = @_; + return $self->{NAV_MAP}->discussion_info($self->symb(),$filter); } sub getFeedback { my $self = shift; my $source = $self->src(); + my $symb = $self->symb(); if ($source =~ /^\/res\//) { $source = substr $source, 5; } - return $self->{NAV_MAP}->getFeedback($source); + return $self->{NAV_MAP}->getFeedback($symb,$source); } sub getErrors { my $self = shift; my $source = $self->src(); + my $symb = $self->symb(); if ($source =~ /^\/res\//) { $source = substr $source, 5; } - return $self->{NAV_MAP}->getErrors($source); + return $self->{NAV_MAP}->getErrors($symb,$source); } =pod @@ -4360,8 +4089,8 @@ sub extractParts { $self->{PART_TYPE} = {}; return; } - foreach (split(/\,/,$metadata)) { - if ($_ =~ /^(?:part|Task)_(.*)$/) { + foreach my $entry (split(/\,/,$metadata)) { + if ($entry =~ /^(?:part|Task)_(.*)$/) { my $part = $1; # This floods the logs if it blows up if (defined($parts{$part})) { @@ -4386,8 +4115,8 @@ sub extractParts { # Init the responseIdHash - foreach (@{$self->{PARTS}}) { - $responseIdHash{$_} = []; + foreach my $part (@{$self->{PARTS}}) { + $responseIdHash{$part} = []; } # Now, the unfortunate thing about this is that parts, part name, and @@ -4467,13 +4196,13 @@ the completion information. Idiomatic usage of these two methods would probably look something like - foreach ($resource->parts()) { - my $dateStatus = $resource->getDateStatus($_); - my $completionStatus = $resource->getCompletionStatus($_); + foreach my $part ($resource->parts()) { + my $dateStatus = $resource->getDateStatus($part); + my $completionStatus = $resource->getCompletionStatus($part); or - my $status = $resource->status($_); + my $status = $resource->status($part); ... use it here ... }
'. - &Apache::loncommon::help_open_menu('','Navigation Screen','Navigation_Screen','',undef,'RAT').'