--- loncom/interface/lonnavmaps.pm 2006/03/13 17:29:50 1.368 +++ loncom/interface/lonnavmaps.pm 2008/01/12 05:04:35 1.409 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.368 2006/03/13 17:29:50 raeburn Exp $ +# $Id: lonnavmaps.pm,v 1.409 2008/01/12 05:04:35 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -31,15 +31,14 @@ 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; use POSIX qw (floor strftime); -use Data::Dumper; # for debugging, not always use Time::HiRes qw( gettimeofday tv_interval ); +use LONCAPA; +use DateTime(); # symbolic constants sub SYMB { return 1; } @@ -86,37 +85,9 @@ my %colormap = $resObj->PARTIALLY_CORRECT => '#006600' ); # And a special case in the nav map; what to do when the assignment -# is not yet done and due in less then 24 hours +# is not yet done and due in less than 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 $html=&Apache::lonxml::xmlbegin(); - $r->print(<<"ENDSUBM"); - $html - - - - - -ENDSUBM - 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; - my $html=&Apache::lonxml::xmlbegin(); - $r->print("$html\n"); - $r->print("".&mt('Navigate Course Contents').""); -# ------------------------------------------------------------ Get query string - &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().';'. - $more_unload.'"'; - $r->print(&Apache::lonmenu::registerurl(1)); - } else { - $addentries=' onUnload="'.$more_unload.'"'; - } - - # Header - $r->print(''. - &Apache::loncommon::bodytag('Navigate Course Contents','', - $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; - } - - # 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"); - } - - my $jumpToFirstHomework = 0; - # Check to see if the student is jumping to next open, do-able problem - if ($ENV{QUERY_STRING} =~ /^jumpToFirstHomework/) { - $jumpToFirstHomework = 1; - # Find the next homework problem that they can do. - my $iterator = $navmap->getIterator(undef, undef, undef, 1); - my $curRes; - my $foundDoableProblem = 0; - my $problemRes; - - while (($curRes = $iterator->next()) && !$foundDoableProblem) { - if (ref($curRes) && $curRes->is_problem()) { - my $status = $curRes->status(); - if ($curRes->completable()) { - $problemRes = $curRes; - $foundDoableProblem = 1; - - # 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(); - } - } - } - - # 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 Me My First Homework 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 Homework")."

"); - $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 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, - '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(""); - $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 @@ -472,15 +148,15 @@ sub getLinkForResource { if (defined($res)) { my $anchor; if ($res->is_page()) { - foreach (@$stack) { if (defined($_)) { $anchor = $_; } } - $anchor=&Apache::lonnet::escape($anchor->shown_symb()); + foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; } } + $anchor=&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()); + $anchor=&escape($src->shown_symb()); return ($url,$res->shown_symb(),$anchor); } } @@ -490,11 +166,14 @@ 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()); + if ($res) { + return ($res->link(),$res->shown_symb()); + } + return; } # Convenience function: This separates the logic of how to create @@ -508,6 +187,10 @@ sub getDescription { my $part = shift; my $status = $res->status($part); + my $open = $res->opendate($part); + my $due = $res->duedate($part); + my $answer = $res->answerdate($part); + if ($status == $res->NETWORK_FAILURE) { return &mt("Having technical difficulties; please check status later"); } @@ -515,20 +198,28 @@ sub getDescription { return &mt("Not currently assigned."); } if ($status == $res->OPEN_LATER) { - return "Open " . timeToHumanString($res->opendate($part),'start'); + return "Open " .timeToHumanString($open,'start'); } if ($status == $res->OPEN) { - if ($res->duedate($part)) { - return &mt("Due")." " .timeToHumanString($res->duedate($part),'end'); + if ($due) { + if ($res->is_practice()) { + return &mt("Closes ")." " .timeToHumanString($due,'start'); + } else { + return &mt("Due")." " .timeToHumanString($due,'end'); + } } else { return &mt("Open, no due date"); } } if ($status == $res->PAST_DUE_ANSWER_LATER) { - return &mt("Answer open")." " . timeToHumanString($res->answerdate($part),'start'); + return &mt("Answer open")." " .timeToHumanString($answer,'start'); } if ($status == $res->PAST_DUE_NO_ANSWER) { - return &mt("Was due")." " . timeToHumanString($res->duedate($part),'end'); + if ($res->is_practice()) { + return &mt("Closed")." " . timeToHumanString($due,'start'); + } else { + return &mt("Was due")." " . timeToHumanString($due,'end'); + } } if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT) && $res->handgrade($part) ne 'yes') { @@ -550,8 +241,8 @@ sub getDescription { $triesString = "$triesString"; } } - if ($res->duedate($part)) { - return &mt("Due")." " . timeToHumanString($res->duedate($part),'end') . + if ($due) { + return &mt("Due")." " . timeToHumanString($due,'end') . " $triesString"; } else { return &mt("No due date")." $triesString"; @@ -562,7 +253,7 @@ sub getDescription { } } -# Convenience function, so others can use it: Is the problem due in less then +# Convenience function, so others can use it: Is the problem due in less than # 24 hours, and still can be done? sub dueInLessThan24Hours { @@ -577,7 +268,7 @@ sub dueInLessThan24Hours { } # Convenience function, so others can use it: Is there only one try remaining for the -# part, with more then one try to begin with, not due yet and still can be done? +# part, with more than one try to begin with, not due yet and still can be done? sub lastTry { my $res = shift; my $part = shift; @@ -618,9 +309,6 @@ sub timeToHumanString { } my $now = time(); - my @time = localtime($time); - my @now = localtime($now); - # Positive = future my $delta = $time - $now; @@ -646,13 +334,13 @@ sub timeToHumanString { my $tense = $inPast ? " ago" : ""; my $prefix = $inPast ? "" : "in "; - # Less then a minute + # Less than a minute if ( $delta < $minute ) { if ($delta == 1) { return "${prefix}1 second$tense"; } return "$prefix$delta seconds$tense"; } - # Less then an hour + # Less than an hour if ( $delta < $hour ) { # If so, use minutes my $minutes = floor($delta / 60); @@ -660,7 +348,7 @@ sub timeToHumanString { return "$prefix$minutes minutes$tense"; } - # Is it less then 24 hours away? If so, + # Is it less than 24 hours away? If so, # display hours + minutes if ( $delta < $hour * 24) { my $hours = floor($delta / $hour); @@ -679,22 +367,25 @@ sub timeToHumanString { return "$prefix$hourString$minuteString$tense"; } + my $dt = DateTime->from_epoch(epoch => $time) + ->set_time_zone(&Apache::lonlocal::gettimezone()); + # If there's a caller supplied format, use it. - if($format ne '') { - my $timeStr = strftime($format, localtime($time)); - return $timeStr.&Apache::lonlocal::gettimezone(); + if ($format ne '') { + my $timeStr = $dt->strftime($format); + return $timeStr.' ('.$dt->time_zone_short_name().')'; } - # Less then 5 days away, display day of the week and + # Less than 5 days away, display day of the week and # HH:MM if ( $delta < $day * 5 ) { - my $timeStr = strftime("%A, %b %e at %I:%M %P", localtime($time)); + my $timeStr = $dt->strftime("%A, %b %e at %I:%M %P (%Z)"); $timeStr =~ s/12:00 am/00:00/; $timeStr =~ s/12:00 pm/noon/; return ($inPast ? "last " : "this ") . - $timeStr.&Apache::lonlocal::gettimezone(); + $timeStr; } my $conjunction='on'; @@ -704,19 +395,22 @@ sub timeToHumanString { $conjunction='by'; } # Is it this year? - if ( $time[5] == $now[5]) { + my $dt_now = DateTime->from_epoch(epoch => $now) + ->set_time_zone(&Apache::lonlocal::gettimezone()); + if ( $dt->year() == $dt_now->year()) { # Return on Month Day, HH:MM meridian - my $timeStr = strftime("$conjunction %A, %b %e at %I:%M %P", localtime($time)); + my $timeStr = $dt->strftime("$conjunction %A, %b %e at %I:%M %P (%Z)"); $timeStr =~ s/12:00 am/00:00/; $timeStr =~ s/12:00 pm/noon/; - return $timeStr.&Apache::lonlocal::gettimezone(); + return $timeStr; } # Not this year, so show the year - my $timeStr = strftime("$conjunction %A, %b %e %Y at %I:%M %P", localtime($time)); + my $timeStr = + $dt->strftime("$conjunction %A, %b %e %Y at %I:%M %P (%Z)"); $timeStr =~ s/12:00 am/00:00/; $timeStr =~ s/12:00 pm/noon/; - return $timeStr.&Apache::lonlocal::gettimezone(); + return $timeStr; } } @@ -751,7 +445,7 @@ to compute due to the amount of data tha processed. Apache::lonnavmaps provides an object model for manipulating this -information in a higher-level fashion then directly manipulating +information in a higher-level fashion than directly manipulating the hash. It also provides access to several auxilary functions that aren't necessarily stored in the Big Hash, but are a per- resource sort of value, like whether there is any feedback on @@ -797,7 +491,7 @@ Apache::lonnavmaps::render({}). =head2 Overview of Columns The renderer will build an HTML table for the navmap and return -it. The table is consists of several columns, and a row for each +it. The table consists of several columns, and a row for each resource (or possibly each part). You tell the renderer how many columns to create and what to place in each column, optionally using one or more of the prepared columns, and the renderer will assemble @@ -928,6 +622,11 @@ instruct the renderer to render only a p the source of the map you want to process, like '/res/103/jerf/navmap.course.sequence'. +=item * B: default: false + +If you need to include the top level map (meaning the course) in the +rendered output set this to true + =item * B: default: constructs one from %env A reference to a navmap, used only if an iterator is not passed in. If @@ -1095,7 +794,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'}; } @@ -1120,16 +825,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=' . - &Apache::lonnet::escape($params->{'here'}) . - '&jump=' . - &Apache::lonnet::escape($resource->symb()) . - "&folderManip=1\">"; + $linkopen .= "&condition=" . $it->{CONDITION} . '&hereType=' + . $params->{'hereType'} . '&here=' . + &escape($params->{'here'}) . + '&jump=' . + &escape($resource->symb()) . + "&folderManip=1\">"; } else { # Don't allow users to manipulate folder @@ -1151,7 +856,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--; } @@ -1180,7 +885,7 @@ sub render_resource { !$params->{'condensed'}) { my $displaypart=$resource->part_display($part); $partLabel = " (".&mt('Part: [_1]', $displaypart).")"; - if ($link!~/\#/) { $link.='#'.&Apache::lonnet::escape($part); } + if ($link!~/\#/) { $link.='#'.&escape($part); } $title = ""; } @@ -1215,17 +920,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').''; } } @@ -1234,13 +939,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').''; } } @@ -1250,7 +955,7 @@ sub render_communication_status { $discussionHTML = $feedbackHTML = $errorHTML = ''; } - return "$discussionHTML$feedbackHTML$errorHTML "; + return "$discussionHTML$feedbackHTML$errorHTML "; } sub render_quick_status { @@ -1275,7 +980,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"; } @@ -1287,12 +992,12 @@ 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"; my $color; - if ($resource->is_problem()) { + if ($resource->is_problem() || $resource->is_practice()) { $color = $colormap{$resource->status}; if (dueInLessThan24Hours($resource, $part) || @@ -1302,14 +1007,17 @@ sub render_long_status { } if ($resource->kind() eq "res" && - $resource->is_problem() && + ($resource->is_problem() || $resource->is_practice()) && !$firstDisplayed) { if ($color) {$result .= ""; } $result .= getDescription($resource, $part); if ($color) {$result .= ""; } } - if ($resource->is_map() && advancedUser() && $resource->randompick()) { - $result .= '(randomly select ' . $resource->randompick() .')'; + if ($resource->is_map() && &advancedUser() && $resource->randompick()) { + $result .= &mt('(randomly select [_1])', $resource->randompick()); + } + if ($resource->is_map() && &advancedUser() && $resource->randomorder()) { + $result .= &mt('(randomly ordered)'); } # Debugging code @@ -1344,7 +1052,6 @@ my %statusStrings = ); my @statuses = ($resObj->CORRECT, $resObj->ATTEMPTED, $resObj->INCORRECT, $resObj->OPEN, $resObj->CLOSED, $resObj->ERROR); -use Data::Dumper; sub render_parts_summary_status { my ($resource, $part, $params) = @_; if (!$resource->is_problem() && !$resource->contains_problem) { return ''; } @@ -1443,9 +1150,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"; } } @@ -1473,7 +1180,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').'
'; } } @@ -1551,7 +1258,7 @@ sub render { $args->{'iterator'} = $it = $navmap->getIterator($firstResource, $finishResource, $filterHash, $condition); } else { - $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition); + $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition,undef,$args->{'include_top_level_map'}); } } @@ -1588,7 +1295,6 @@ sub render { # Print key? if ($printKey) { $result .= ''; - my $date=localtime; $result.=''; my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc"); if ($navmap->{LAST_CHECK}) { @@ -1611,13 +1317,13 @@ sub render { if ($printCloseAll && !$args->{'resource_no_folder_link'}) { my ($link,$text); if ($condition) { - $link='"navmaps?condition=0&filter=&'.$queryString. - '&here='.&Apache::lonnet::escape($here).'"'; - $text='Close All Folders'; + $link='"navmaps?condition=0&filter=&'.$queryString. + '&here='.&escape($here).'"'; + $text='Close all folders'; } else { - $link='"navmaps?condition=1&filter=&'.$queryString. - '&here='.&Apache::lonnet::escape($here).'"'; - $text='Open All Folders'; + $link='"navmaps?condition=1&filter=&'.$queryString. + '&here='.&escape($here).'"'; + $text='Open all folders'; } if ($args->{'caller'} eq 'navmapsdisplay') { &add_linkitem($args->{'linkitems'},'changefolder', @@ -1662,7 +1368,7 @@ END if ($args->{'caller'} eq 'navmapsdisplay') { $result .= '
Key:  
'; + &Apache::loncommon::help_open_menu('Navigation Screen','Navigation_Screen',undef,'RAT').''; if ($env{'environment.remotenavmap'} ne 'on') { $result .= ''; } else { @@ -1927,7 +1633,7 @@ END my $srcHasQuestion = $src =~ /\?/; $args->{"resourceLink"} = $src. ($srcHasQuestion?'&':'?') . - 'symb=' . &Apache::lonnet::escape($symb).$anchor; + 'symb=' . &escape($symb).$anchor; } # Now, we've decided what parts to show. Loop through them and # show them. @@ -2119,6 +1825,7 @@ See iterator documentation below. use strict; use GDBM_File; use Apache::lonnet; +use LONCAPA; sub new { # magic invocation to create a class instance @@ -2218,10 +1925,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=(); @@ -2231,22 +1938,36 @@ sub generate_email_discuss_status { foreach my $msgid (@keys) { if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) { - my $plain= - &Apache::lonnet::unescape(&Apache::lonnet::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); + &Apache::lonenc::check_decrypt(\$symb); + if (($fromcid ne '') && ($fromcid ne $cid)) { + next; + } + 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; @@ -2351,58 +2072,64 @@ 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(); - - my $ressymb = $self->wrap_symb($symb); - my $version = $self->{DISCUSSION_DATA}{'version:'.$ressymb}; + my $ressymb = $self->wrap_symb($symb); + # keys used to store bulletinboard postings use 'unwrapped' symb. + my $discsymb = &escape($self->unwrap_symb($ressymb)); + my $version = $self->{DISCUSSION_DATA}{'version:'.$discsymb}; if (!$version) { return; } 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:'.$ressymb}; + my $vkeys=$self->{DISCUSSION_DATA}{$id.':keys:'.$discsymb}; my @keys=split(/:/,$vkeys); if (grep(/^hidden$/ ,@keys)) { if (!$hiddenflag) { - $hidden = $self->{DISCUSSION_DATA}{$id.':'.$ressymb.':hidden'}; + $hidden = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':hidden'}; $hiddenflag = 1; } } elsif (grep(/^deleted$/,@keys)) { if (!$deletedflag) { - $deleted = $self->{DISCUSSION_DATA}{$id.':'.$ressymb.':deleted'}; + $deleted = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':deleted'}; $deletedflag = 1; } } else { - if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./) - && $prevread < $self->{DISCUSSION_DATA}{$id.':'.$ressymb.':timestamp'}) { - $unreadcount++; - $subjects{$unreadcount}= - $id.': '.$self->{DISCUSSION_DATA}{$id.':'.$ressymb.':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 { my $self = shift; my $symb = shift; - if ($symb =~ m-___(adm/\w+/\w+/)(\d+)(/bulletinboard)$-) { + if ($symb =~ m-___(adm/[^/]+/[^/]+/)(\d+)(/bulletinboard)$-) { unless ($symb =~ m|adm/wrapper/adm|) { $symb = 'bulletin___'.$2.'___adm/wrapper/'.$1.$2.$3; } @@ -2410,6 +2137,16 @@ sub wrap_symb { return $symb; } +sub unwrap_symb { + my $self = shift; + my $ressymb = shift; + my $discsymb = $ressymb; + if ($ressymb =~ m-^(bulletin___\d+___)adm/wrapper/(adm/[^/]+/[^/]+/\d+/bulletinboard)$-) { + $discsymb = $1.$2; + } + return $discsymb; +} + # Private method: Does the given resource (as a symb string) have # current feedback? Returns the string in the feedback hash, which # will be false if it does not exist. @@ -2417,23 +2154,48 @@ sub wrap_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 @@ -2459,7 +2221,7 @@ the given map. This is one of the proper # The strategy here is to cache the resource objects, and only construct them # as we use them. The real point is to prevent reading any more from the tied -# hash then we have to, which should hopefully alleviate speed problems. +# hash than we have to, which should hopefully alleviate speed problems. sub getById { my $self = shift; @@ -2533,16 +2295,23 @@ sub finishResource { # the actual lookup; parmval caches the results. sub parmval { my $self = shift; - my ($what,$symb)=@_; + my ($what,$symb,$recurse)=@_; my $hashkey = $what."|||".$symb; if (defined($self->{PARM_CACHE}->{$hashkey})) { - return $self->{PARM_CACHE}->{$hashkey}; + if (wantarray) { + return @{$self->{PARM_CACHE}->{$hashkey}}; + } else { + return $self->{PARM_CACHE}->{$hashkey}->[0]; + } } - my $result = $self->parmval_real($what, $symb); + my $result = $self->parmval_real($what, $symb, $recurse); $self->{PARM_CACHE}->{$hashkey} = $result; - return $result; + if (wantarray) { + return @{$result}; + } + return $result->[0]; } sub parmval_real { @@ -2563,11 +2332,11 @@ sub parmval_real { my $uname=$env{'user.name'}; my $udom=$env{'user.domain'}; - unless ($symb) { return ''; } + unless ($symb) { return ['']; } my $result=''; my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb); - + $mapname = &Apache::lonnet::deversion($mapname); # ----------------------------------------------------- Cascading lookup scheme my $rwhat=$what; $what=~s/^parameter\_//; @@ -2595,48 +2364,49 @@ sub parmval_real { # ---------------------------------------------------------- first, check user if ($uname and defined($useropt)) { - if (defined($$useropt{$courselevelr})) { return $$useropt{$courselevelr}; } - if (defined($$useropt{$courselevelm})) { return $$useropt{$courselevelm}; } - if (defined($$useropt{$courselevel})) { return $$useropt{$courselevel}; } + if (defined($$useropt{$courselevelr})) { return [$$useropt{$courselevelr},'resource']; } + if (defined($$useropt{$courselevelm})) { return [$$useropt{$courselevelm},'map']; } + if (defined($$useropt{$courselevel})) { return [$$useropt{$courselevel},'course']; } } # ------------------------------------------------------- second, check course if ($cgroup ne '' and defined($courseopt)) { - if (defined($$courseopt{$grplevelr})) { return $$courseopt{$grplevelr}; } - if (defined($$courseopt{$grplevelm})) { return $$courseopt{$grplevelm}; } - if (defined($$courseopt{$grplevel})) { return $$courseopt{$grplevel}; } + if (defined($$courseopt{$grplevelr})) { return [$$courseopt{$grplevelr},'resource']; } + if (defined($$courseopt{$grplevelm})) { return [$$courseopt{$grplevelm},'map']; } + if (defined($$courseopt{$grplevel})) { return [$$courseopt{$grplevel},'course']; } } if ($csec and defined($courseopt)) { - if (defined($$courseopt{$seclevelr})) { return $$courseopt{$seclevelr}; } - if (defined($$courseopt{$seclevelm})) { return $$courseopt{$seclevelm}; } - if (defined($$courseopt{$seclevel})) { return $$courseopt{$seclevel}; } + if (defined($$courseopt{$seclevelr})) { return [$$courseopt{$seclevelr},'resource']; } + if (defined($$courseopt{$seclevelm})) { return [$$courseopt{$seclevelm},'map']; } + if (defined($$courseopt{$seclevel})) { return [$$courseopt{$seclevel},'course']; } } if (defined($courseopt)) { - if (defined($$courseopt{$courselevelr})) { return $$courseopt{$courselevelr}; } + if (defined($$courseopt{$courselevelr})) { return [$$courseopt{$courselevelr},'resource']; } } # ----------------------------------------------------- third, check map parms my $thisparm=$$parmhash{$symbparm}; - if (defined($thisparm)) { return $thisparm; } + if (defined($thisparm)) { return [$thisparm,'map']; } # ----------------------------------------------------- fourth , check default my $meta_rwhat=$rwhat; $meta_rwhat=~s/\./_/g; my $default=&Apache::lonnet::metadata($fn,$meta_rwhat); - if (defined($default)) { return $default} + if (defined($default)) { return [$default,'resource']} $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat); - if (defined($default)) { return $default} - + if (defined($default)) { return [$default,'resource']} # --------------------------------------------------- fifth, check more course if (defined($courseopt)) { - if (defined($$courseopt{$courselevelm})) { return $$courseopt{$courselevelm}; } - if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; } + if (defined($$courseopt{$courselevelm})) { return [$$courseopt{$courselevelm},'map']; } + if (defined($$courseopt{$courselevel})) { + my $ret = [$$courseopt{$courselevel},'course']; + return $ret; + } } - # --------------------------------------------------- sixth , cascade up parts my ($space,@qualifier)=split(/\./,$rwhat); @@ -2646,13 +2416,13 @@ sub parmval_real { my $id=pop(@parts); my $part=join('_',@parts); if ($part eq '') { $part='0'; } - my $partgeneral=$self->parmval($part.".$qualifier",$symb,1); - if (defined($partgeneral)) { return $partgeneral; } + my @partgeneral=$self->parmval($part.".$qualifier",$symb,1); + if (defined($partgeneral[0])) { return \@partgeneral; } } - if ($recurse) { return undef; } - my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$what); - if (defined($pack_def)) { return $pack_def; } - return ''; + if ($recurse) { return []; } + my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$rwhat); + if (defined($pack_def)) { return [$pack_def,'resource']; } + return ['']; } =pod @@ -2660,7 +2430,7 @@ sub parmval_real { =item * B(url,multiple): Retrieves a resource object by URL of the resource, unless the optional -multiple parameter is included in wahich caes an array of resource +multiple parameter is included in which case an array of resource objects is returned. If passed a resource object, it will simply return it, so it is safe to use this method in code like "$res = $navmap->getResourceByUrl($res)" @@ -2695,7 +2465,7 @@ all matching resources. =item * B(map, filterFunc, recursive, showall): -Convience method for +Convenience method for scalar(retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0 @@ -2773,6 +2543,10 @@ sub retrieveResources { my @resources = (); + if (&$filterFunc($map)) { + push(@resources, $map); + } + # Run down the iterator and collect the resources. my $curRes; @@ -2782,7 +2556,7 @@ sub retrieveResources { next; } - push @resources, $curRes; + push(@resources, $curRes); if ($bailout) { return @resources; @@ -2929,7 +2703,7 @@ be the tokens described above. Also note there is some old code floating around that trys to track the depth of the iterator to see when it's done; do not copy that -code. It is difficult to get right and harder to understand then +code. It is difficult to get right and harder to understand than this. They should be migrated to this new style. =cut @@ -3113,6 +2887,10 @@ sub next { $self->{HAVE_RETURNED_0} = 1; return $self->{NAV_MAP}->getById('0.0'); } + if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0_BEGIN_MAP}) { + $self->{HAVE_RETURNED_0_BEGIN_MAP} = 1; + return $self->BEGIN_MAP(); + } if ($self->{RECURSIVE_ITERATOR_FLAG}) { # grab the next from the recursive iterator @@ -3214,7 +2992,7 @@ sub next { } # Is this the end of a branch? If so, all of the resources examined above - # led to lower levels then the one we are currently at, so we push a END_BRANCH + # led to lower levels than the one we are currently at, so we push a END_BRANCH # marker onto the stack so we don't forget. # Example: For the usual A(BC)(DE)F case, when the iterator goes down the # BC branch and gets to C, it will see F as the only next resource, but it's @@ -3423,9 +3201,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; } } @@ -3550,7 +3328,7 @@ X X All resources also have Bs, which uniquely identify a resource in a course. Many internal LON-CAPA functions expect a symb. A symb carries along with it the URL of the resource, and the map it appears -in. Symbs are much larger then resource IDs. +in. Symbs are much larger than resource IDs. =cut @@ -3626,8 +3404,13 @@ false. =item * B: -Returns true for a map if the randompick feature is being used on the -map. (?) +Returns the number of randomly picked items for a map if the randompick +feature is being used on the map. + +=item * B: + +Returns true for a map if the randomorder feature is being used on the +map. =item * B: @@ -3657,7 +3440,11 @@ sub kind { my $self=shift; return $self- sub randomout { my $self=shift; return $self->navHash("randomout_", 1); } sub randompick { my $self = shift; - return $self->{NAV_MAP}->{PARM_HASH}->{$self->symb().'.0.randompick'}; + return $self->parmval('randompick'); +} +sub randomorder { + my $self = shift; + return ($self->parmval('randomorder') =~ /^yes$/i); } sub link { my $self=shift; @@ -3735,6 +3522,7 @@ sub compTitle { } return $title; } + =pod B @@ -3775,6 +3563,16 @@ sub retrieveResources { return $self->{NAV_MAP}->retrieveResources(@_); } +sub is_exam { + my ($self,$part) = @_; + if ($self->parmval('type',$part) eq 'exam') { + return 1; + } + if ($self->src() =~ /\.(exam)$/) { + return 1; + } + return 0; +} sub is_html { my $self=shift; my $src = $self->src(); @@ -3811,9 +3609,17 @@ sub contains_problem { } return 0; } +sub map_contains_problem { + my $self=shift; + if ($self->is_map()) { + my $has_problem= + $self->hasResource($self,sub { $_[0]->is_problem() },1); + return $has_problem; + } + return 0; +} sub is_sequence { my $self=shift; - my $src = $self->src(); return $self->navHash("is_map_", 1) && $self->navHash("map_type_" . $self->map_pc()) eq 'sequence'; } @@ -3888,7 +3694,7 @@ Returns a string with the type of the ma sub map_finish { my $self = shift; my $src = $self->src(); - $src = Apache::lonnet::clutter($src); + $src = &Apache::lonnet::clutter($src); my $res = $self->navHash("map_finish_$src", 0); $res = $self->{NAV_MAP}->getById($res); return $res; @@ -3901,7 +3707,7 @@ sub map_pc { sub map_start { my $self = shift; my $src = $self->src(); - $src = Apache::lonnet::clutter($src); + $src = &Apache::lonnet::clutter($src); my $res = $self->navHash("map_start_$src", 0); $res = $self->{NAV_MAP}->getById($res); return $res; @@ -3918,9 +3724,9 @@ sub map_type { # These functions will be responsible for returning the CORRECT # VALUE for the parameter, no matter what. So while they may look -# like direct calls to parmval, they can be more then that. +# like direct calls to parmval, they can be more than that. # So, for instance, the duedate function should use the "duedatetype" -# information, rather then the resource object user. +# information, rather than the resource object user. =pod @@ -4013,17 +3819,38 @@ sub awarded { if (!defined($part)) { $part = '0'; } return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.awarded'}; } +# this should work exactly like the copy in lonhomework.pm 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); } + my $date; + my @interval=$self->parmval("interval", $part); + my $due_date=$self->parmval("duedate", $part); + if ($interval[0] =~ /\d+/) { + my $first_access=&Apache::lonnet::get_first_access($interval[1], + $self->symb); + if (defined($first_access)) { + my $interval = $first_access+$interval[0]; + $date = (!$due_date || $interval < $due_date) ? $interval + : $due_date; + } else { + $date = $due_date; + } + } else { + $date = $due_date; } - return $self->parmval("duedate", $part); + return $date; } sub handgrade { (my $self, my $part) = @_; + my @response_ids = $self->responseIds($part); + if (@response_ids) { + foreach my $response_id (@response_ids) { + if (lc($self->parmval("handgrade",$part.'_'.$response_id)) + eq 'yes') { + return 'yes'; + } + } + } return $self->parmval("handgrade", $part); } sub maxtries { @@ -4063,10 +3890,11 @@ sub type { sub weight { my $self = shift; my $part = shift; if (!defined($part)) { $part = '0'; } - return &Apache::lonnet::EXT('resource.'.$part.'.weight', - $self->symb(), $env{'user.domain'}, - $env{'user.name'}, - $env{'request.course.sec'}); + my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight', + $self->symb(), $env{'user.domain'}, + $env{'user.name'}, + $env{'request.course.sec'}); + return $weight; } sub part_display { my $self= shift(); my $partID = shift(); @@ -4122,13 +3950,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: @@ -4137,8 +3967,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 = &Apache::lonnet::escape($_); + for my $url (split(/\,/, $res->getFeedback())) { + my $link = &escape($url); ... and use the link as appropriate. @@ -4155,23 +3985,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 @@ -4332,8 +4164,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})) { @@ -4358,8 +4190,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 @@ -4368,9 +4200,9 @@ sub extractParts { # So we have to use our knowlege of part names to figure out # where the part names begin and end, and even then, it is possible # to construct ambiguous situations. - foreach (split /,/, $metadata) { - if ($_ =~ /^([a-zA-Z]+)response_(.*)/ - || $_ =~ /^(Task)_(.*)/) { + foreach my $data (split /,/, $metadata) { + if ($data =~ /^([a-zA-Z]+)response_(.*)/ + || $data =~ /^(Task)_(.*)/) { my $responseType = $1; my $partStuff = $2; my $partIdSoFar = ''; @@ -4382,8 +4214,15 @@ sub extractParts { if ($parts{$partIdSoFar}) { my @otherChunks = @partChunks[$i+1..$#partChunks]; my $responseId = join('_', @otherChunks); - push @{$responseIdHash{$partIdSoFar}}, $responseId; - push @{$responseTypeHash{$partIdSoFar}}, $responseType; + if ($self->is_task()) { + push(@{$responseIdHash{$partIdSoFar}}, + $partIdSoFar); + } else { + push(@{$responseIdHash{$partIdSoFar}}, + $responseId); + } + push(@{$responseTypeHash{$partIdSoFar}}, + $responseType); } } } @@ -4432,13 +4271,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 ... } @@ -4729,7 +4568,11 @@ sub status { #if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; } if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; } - my $suppressFeedback = $self->problemstatus($part) eq 'no'; + my $suppressFeedback = 0; + if (($self->problemstatus($part) eq 'no') || + ($self->problemstatus($part) eq 'no_feedback_ever')) { + $suppressFeedback = 1; + } # If there's an answer date and we're past it, don't # suppress the feedback; student should know if ($self->duedate($part) && $self->duedate($part) < time() &&
'. - &Apache::loncommon::help_open_menu('','Navigation Screen','Navigation_Screen','',undef,'RAT').'