--- loncom/interface/lonnavmaps.pm 2006/05/11 23:53:02 1.349.2.8 +++ loncom/interface/lonnavmaps.pm 2008/12/21 16:27:26 1.415.2.3 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.349.2.8 2006/05/11 23:53:02 albertel Exp $ +# $Id: lonnavmaps.pm,v 1.415.2.3 2008/12/21 16:27:26 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 &mt("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') { @@ -545,13 +236,13 @@ sub getDescription { my $maxtries = $res->maxtries($part); my $triesString = ""; if ($tries && $maxtries) { - $triesString = "($tries of $maxtries tries used)"; + $triesString = '('.&mt('[_1] of [quant,_2,try,tries] used',$tries,$maxtries).')'; if ($maxtries > 1 && $maxtries - $tries == 1) { $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 ='  '; + $icon = ''.&mt('Task');
+	    } else {
+		$icon .= 'problem.gif'; } else { $icon = $params->{'indentString'}; } @@ -1112,30 +817,31 @@ sub render_resource { } my $folderType = $resource->is_sequence() ? 'folder' : 'page'; - + my $title=$resource->title; + $title=~s/\"/\"/g; if (!$params->{'resource_no_folder_link'}) { $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif'; - $icon = "".
-		($nowOpen ? "; + $icon = "\""."; $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 $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.nomanip.gif'; - $icon = "".
-		($nowOpen ? "; + $icon = "\""."; $linkopen = ""; $linkclose = ""; @@ -1143,14 +849,17 @@ sub render_resource { } if ($resource->randomout()) { - $nonLinkedText .= ' (hidden) '; + $nonLinkedText .= ' ('.&mt('hidden').') '; } if (!$resource->condval()) { - $nonLinkedText .= ' (conditionally hidden) '; + $nonLinkedText .= ' ('.&mt('conditionally hidden').') '; } - + if (($resource->is_practice()) && ($resource->is_raw_problem())) { + $nonLinkedText .=' '.&mt('not graded').''; + } + # We're done preparing and finally ready to start the rendering - my $result = ""; + my $result = ""; my $indentLevel = $params->{'indentLevel'}; if ($newBranchText) { $indentLevel--; } @@ -1178,13 +887,13 @@ sub render_resource { if ($resource->is_problem() && $part ne '0' && !$params->{'condensed'}) { my $displaypart=$resource->part_display($part); - $partLabel = " (Part: $displaypart)"; - if ($link!~/\#/) { $link.='#'.&Apache::lonnet::escape($part); } + $partLabel = " (".&mt('Part: [_1]', $displaypart).")"; + if ($link!~/\#/) { $link.='#'.&escape($part); } $title = ""; } if ($params->{'condensed'} && $resource->countParts() > 1) { - $nonLinkedText .= ' (' . $resource->countParts() . ' parts)'; + $nonLinkedText .= ' ('.&mt('[_1] parts', $resource->countParts()).')'; } my $target; @@ -1214,17 +923,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').''; } } @@ -1233,13 +942,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').''; } } @@ -1249,7 +958,7 @@ sub render_communication_status { $discussionHTML = $feedbackHTML = $errorHTML = ''; } - return "$discussionHTML$feedbackHTML$errorHTML "; + return "$discussionHTML$feedbackHTML$errorHTML "; } sub render_quick_status { @@ -1274,7 +983,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"; } @@ -1286,12 +995,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) || @@ -1301,14 +1010,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 @@ -1343,7 +1055,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 ''; } @@ -1442,9 +1153,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,9 +1182,9 @@ sub render { if (!defined($navmap)) { $navmap = Apache::lonnavmaps::navmap->new(); if (!defined($navmap)) { - # no londer in course - return 'No course selected
- Select a course
'; + # no longer in course + return ''.&mt('No course selected').'
+ '.&mt('Select a course').'
'; } } @@ -1539,6 +1250,11 @@ sub render { # Step 1: Check to see if we have a navmap if (!defined($navmap)) { $navmap = Apache::lonnavmaps::navmap->new(); + if (!defined($navmap)) { + # no longer in course + return ''.&mt('No course selected').'
+ '.&mt('Select a course').'
'; + } } # See if we're being passed a specific map @@ -1550,7 +1266,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'}); } } @@ -1587,7 +1303,6 @@ sub render { # Print key? if ($printKey) { $result .= ''; - my $date=localtime; $result.=''; my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc"); if ($navmap->{LAST_CHECK}) { @@ -1610,13 +1325,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', @@ -1661,7 +1376,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 { @@ -1926,7 +1641,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. @@ -2031,7 +1746,7 @@ ENDBLOCK $result.='} - '."\n"; foreach my $link (@linkorder) { if (defined($linkitems->{$link})) { if ($linkitems->{$link}{'text'} ne '') { @@ -2042,7 +1757,7 @@ ENDBLOCK } $result .= '  - '."\n"; + '."\n"; return $result; } @@ -2066,15 +1781,16 @@ In order of increasing complexity and po =over 4 -=item * C<$navmap-EgetByX>, where X is B, B, B or B. This provides +=item * C<$navmap-EgetByX>, where X is B, B or B and getResourceByUrl. This provides various ways to obtain resource objects, based on various identifiers. Use this when you want to request information about one object or a handful of resources you already know the identities of, from some other source. For more about Ids, Symbs, and MapPcs, see the Resource documentation. Note that Url should be a B, - not your first choice; it only works when there is only one + not your first choice; it only really works when there is only one instance of the resource in the course, which only applies to - maps, and even that may change in the future. + maps, and even that may change in the future (see the B + documentation for more details.) =item * CretrieveResources(args)>. This retrieves resources matching some criterion and returns them @@ -2117,6 +1833,7 @@ See iterator documentation below. use strict; use GDBM_File; use Apache::lonnet; +use LONCAPA; sub new { # magic invocation to create a class instance @@ -2216,10 +1933,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 +1946,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; @@ -2269,16 +2000,18 @@ sub get_user_data { sub get_discussion_data { my $self = shift; if ($self->{RETRIEVED_DISCUSSION_DATA}) { - return $self->{DISCUSSION_DATA}; + return $self->{DISCUSSION_DATA}; } - + + $self->generate_email_discuss_status(); + my $cid=$env{'request.course.id'}; my $cdom=$env{'course.'.$cid.'.domain'}; my $cnum=$env{'course.'.$cid.'.num'}; - # Retrieve discussion data for resources in course - my %discussion_data = &Apache::lonnet::dump($cid,$cdom,$cnum); - + my %discussion_data = &Apache::lonnet::dumpstore($cid,$cdom,$cnum); + + $self->{DISCUSSION_DATA} = \%discussion_data; $self->{RETRIEVED_DISCUSSION_DATA} = 1; return $self->{DISCUSSION_DATA}; @@ -2340,10 +2073,71 @@ sub hasDiscussion { } } +sub last_post_time { + my $self = shift; + my $symb = shift; + my $ressymb = $self->wrap_symb($symb); + return $self->{DISCUSSION_TIME}->{$ressymb}; +} + +sub discussion_info { + my $self = shift; + my $symb = shift; + my $filter = shift; + + $self->get_discussion_data(); + + 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 $count = 0; + my $hiddenflag = 0; + my $deletedflag = 0; + my ($hidden,$deleted,%info); + + for (my $id=$version; $id>0; $id--) { + my $vkeys=$self->{DISCUSSION_DATA}{$id.':keys:'.$discsymb}; + my @keys=split(/:/,$vkeys); + if (grep(/^hidden$/ ,@keys)) { + if (!$hiddenflag) { + $hidden = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':hidden'}; + $hiddenflag = 1; + } + } elsif (grep(/^deleted$/,@keys)) { + if (!$deletedflag) { + $deleted = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':deleted'}; + $deletedflag = 1; + } + } else { + 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 ($count,%info); + } + 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; } @@ -2351,6 +2145,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. @@ -2358,23 +2162,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 @@ -2400,7 +2229,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; @@ -2474,16 +2303,28 @@ 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 (ref($self->{PARM_CACHE}->{$hashkey}) eq 'ARRAY') { + if (defined($self->{PARM_CACHE}->{$hashkey}->[0])) { + if (wantarray) { + return @{$self->{PARM_CACHE}->{$hashkey}}; + } else { + return $self->{PARM_CACHE}->{$hashkey}->[0]; + } + } + } else { + return $self->{PARM_CACHE}->{$hashkey}; + } } - - 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 { @@ -2495,14 +2336,20 @@ sub parmval_real { my $cid=$env{'request.course.id'}; my $csec=$env{'request.course.sec'}; + my $cgroup=''; + my @cgrps=split(/:/,$env{'request.course.groups'}); + if (@cgrps > 0) { + @cgrps = sort(@cgrps); + $cgroup = $cgrps[0]; + } 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\_//; @@ -2512,6 +2359,10 @@ sub parmval_real { my $mapparm=$mapname.'___(all).'.$what; my $usercourseprefix=$cid; + my $grplevel=$usercourseprefix.'.['.$cgroup.'].'.$what; + my $grplevelr=$usercourseprefix.'.['.$cgroup.'].'.$symbparm; + my $grplevelm=$usercourseprefix.'.['.$cgroup.'].'.$mapparm; + my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what; my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm; my $seclevelm=$usercourseprefix.'.['.$csec.'].'.$mapparm; @@ -2526,42 +2377,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},'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); @@ -2571,13 +2429,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 @@ -2585,7 +2443,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)" @@ -2620,7 +2478,7 @@ all matching resources. =item * B(map, filterFunc, recursive, showall): -Convience method for +Convenience method for scalar(retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0 @@ -2698,6 +2556,10 @@ sub retrieveResources { my @resources = (); + if (&$filterFunc($map)) { + push(@resources, $map); + } + # Run down the iterator and collect the resources. my $curRes; @@ -2707,7 +2569,7 @@ sub retrieveResources { next; } - push @resources, $curRes; + push(@resources, $curRes); if ($bailout) { return @resources; @@ -2738,7 +2600,7 @@ sub usedVersion { 1; package Apache::lonnavmaps::iterator; -use WeakRef; +use Scalar::Util qw(weaken); use Apache::lonnet; =pod @@ -2854,7 +2716,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 @@ -3038,6 +2900,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 @@ -3139,7 +3005,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 @@ -3219,7 +3085,7 @@ sub populateStack { 1; package Apache::lonnavmaps::DFSiterator; -use WeakRef; +use Scalar::Util qw(weaken); use Apache::lonnet; # Not documented in the perldoc: This is a simple iterator that just walks @@ -3348,9 +3214,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; } } @@ -3403,7 +3269,7 @@ sub populateStack { 1; package Apache::lonnavmaps::resource; -use WeakRef; +use Scalar::Util qw(weaken); use Apache::lonnet; =pod @@ -3475,7 +3341,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 @@ -3551,8 +3417,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: @@ -3582,8 +3453,13 @@ 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.parameter_randompick'}; + my $randompick = $self->parmval('randompick'); + return $randompick; +} +sub randomorder { + my $self = shift; + my $randomorder = $self->parmval('randomorder'); + return ($randomorder =~ /^yes$/i); } sub link { my $self=shift; @@ -3661,6 +3537,7 @@ sub compTitle { } return $title; } + =pod B @@ -3701,6 +3578,17 @@ sub retrieveResources { return $self->{NAV_MAP}->retrieveResources(@_); } +sub is_exam { + my ($self,$part) = @_; + my $type = $self->parmval('type',$part); + if ($type eq 'exam') { + return 1; + } + if ($self->src() =~ /\.(exam)$/) { + return 1; + } + return 0; +} sub is_html { my $self=shift; my $src = $self->src(); @@ -3713,11 +3601,32 @@ sub is_page { return $self->navHash("is_map_", 1) && $self->navHash("map_type_" . $self->map_pc()) eq 'page'; } +sub is_practice { + my $self=shift; + my ($part) = @_; + my $type = $self->parmval('type',$part); + if ($type eq 'practice') { + return 1; + } + return 0; +} sub is_problem { my $self=shift; my $src = $self->src(); - return ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) + if ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) { + return !($self->is_practice()); + } + return 0; +} +sub is_raw_problem { + my $self=shift; + my $src = $self->src(); + if ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) { + return 1; + } + return 0; } + sub contains_problem { my $self=shift; if ($self->is_page()) { @@ -3726,6 +3635,15 @@ 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; return $self->navHash("is_map_", 1) && @@ -3734,7 +3652,8 @@ sub is_sequence { sub is_survey { my $self = shift(); my $part = shift(); - if ($self->parmval('type',$part) eq 'survey') { + my $type = $self->parmval('type',$part); + if ($type eq 'survey') { return 1; } if ($self->src() =~ /\.(survey)$/) { @@ -3742,6 +3661,11 @@ sub is_survey { } return 0; } +sub is_task { + my $self=shift; + my $src = $self->src(); + return ($src =~ /\.(task)$/) +} sub is_empty_sequence { my $self=shift; @@ -3827,9 +3751,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 @@ -3905,16 +3829,19 @@ Get the weight for the problem. sub acc { (my $self, my $part) = @_; - return $self->parmval("acc", $part); + my $acc = $self->parmval("acc", $part); + return $acc; } sub answerdate { (my $self, my $part) = @_; # Handle intervals - if ($self->parmval("answerdate.type", $part) eq 'date_interval') { - return $self->duedate($part) + - $self->parmval("answerdate", $part); + my $answerdatetype = $self->parmval("answerdate.type", $part); + my $answerdate = $self->parmval("answerdate", $part); + my $duedate = $self->parmval("duedate", $part); + if ($answerdatetype eq 'date_interval') { + $answerdate = $duedate + $answerdate; } - return $self->parmval("answerdate", $part); + return $answerdate; } sub awarded { my $self = shift; my $part = shift; @@ -3922,44 +3849,72 @@ 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) = @_; - return $self->parmval("handgrade", $part); + my @response_ids = $self->responseIds($part); + if (@response_ids) { + foreach my $response_id (@response_ids) { + my $handgrade = $self->parmval("handgrade",$part.'_'.$response_id); + if (lc($handgrade) eq 'yes') { + return 'yes'; + } + } + } + my $handgrade = $self->parmval("handgrade", $part); + return $handgrade; } sub maxtries { (my $self, my $part) = @_; - return $self->parmval("maxtries", $part); + my $maxtries = $self->parmval("maxtries", $part); + return $maxtries; } sub opendate { (my $self, my $part) = @_; - if ($self->parmval("opendate.type", $part) eq 'date_interval') { - return $self->duedate($part) - - $self->parmval("opendate", $part); + my $opendatetype = $self->parmval("opendate.type", $part); + my $opendate = $self->parmval("opendate", $part); + if ($opendatetype eq 'date_interval') { + my $duedate = $self->duedate($part); + $opendate = $duedate - $opendate; } - return $self->parmval("opendate"); + return $opendate; } sub problemstatus { (my $self, my $part) = @_; - return lc $self->parmval("problemstatus", $part); + my $problemstatus = $self->parmval("problemstatus", $part); + return lc($problemstatus); } sub sig { (my $self, my $part) = @_; - return $self->parmval("sig", $part); + my $sig = $self->parmval("sig", $part); + return $sig; } sub tol { (my $self, my $part) = @_; - return $self->parmval("tol", $part); + my $tol = $self->parmval("tol", $part); + return $tol; } -sub tries { +sub tries { my $self = shift; my $tries = $self->queryRestoreHash('tries', shift); if (!defined($tries)) { return '0';} @@ -3967,15 +3922,17 @@ sub tries { } sub type { (my $self, my $part) = @_; - return $self->parmval("type", $part); + my $type = $self->parmval("type", $part); + return $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(); @@ -4026,6 +3983,21 @@ Returns a false value if there has been logged in, true if there has. Always returns false if the discussion data was not extracted when the nav map was constructed. +=item * B: + +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: + +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 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: Gets the feedback for the resource and returns the raw feedback string @@ -4033,8 +4005,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. @@ -4046,18 +4018,30 @@ sub hasDiscussion { return $self->{NAV_MAP}->hasDiscussion($self->symb()); } +sub last_post_time { + my $self = shift; + return $self->{NAV_MAP}->last_post_time($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 @@ -4143,7 +4127,7 @@ sub countResponses { sub responseTypes { my $self = shift; my %responses; - foreach my $part ($self->parts()) { + foreach my $part (@{$self->parts()}) { foreach my $responsetype ($self->responseType($part)) { $responses{$responsetype}++ if (defined($responsetype)); } @@ -4218,8 +4202,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})) { @@ -4244,8 +4228,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 @@ -4254,8 +4238,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_(.*)/) { + foreach my $data (split /,/, $metadata) { + if ($data =~ /^([a-zA-Z]+)response_(.*)/ + || $data =~ /^(Task)_(.*)/) { my $responseType = $1; my $partStuff = $2; my $partIdSoFar = ''; @@ -4267,8 +4252,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); } } } @@ -4317,13 +4309,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 ... } @@ -4614,7 +4606,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').'