--- loncom/interface/lonnavmaps.pm 2003/10/08 19:22:17 1.238 +++ loncom/interface/lonnavmaps.pm 2004/07/27 23:35:34 1.273 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.238 2003/10/08 19:22:17 albertel Exp $ +# $Id: lonnavmaps.pm,v 1.273 2004/07/27 23:35:34 www Exp $ # # Copyright Michigan State University Board of Trustees # @@ -25,20 +25,7 @@ # # http://www.lon-capa.org/ # -# (Page Handler -# -# (TeX Content Handler -# -# 05/29/00,05/30 Gerd Kortemeyer) -# 08/30,08/31,09/06,09/14,09/15,09/16,09/19,09/20,09/21,09/23, -# 10/02,10/10,10/14,10/16,10/18,10/19,10/31,11/6,11/14,11/16 Gerd Kortemeyer) -# -# 3/1/1,6/1,17/1,29/1,30/1,2/8,9/21,9/24,9/25 Gerd Kortemeyer -# YEAR=2002 -# 1/1 Gerd Kortemeyer -# Oct-Nov Jeremy Bowers -# YEAR=2003 -# Jeremy Bowers ... lots of days +### package Apache::lonnavmaps; @@ -96,6 +83,56 @@ my %colormap = # is not yet done and due in less then 24 hours my $hurryUpColor = "#FF0000"; +sub launch_win { + my ($mode,$script)=@_; + my $result; + if ($script ne 'no') { + $result.=''; + } + if ($mode eq 'link') { + $result.='' + .&mt("Launch navigation in separate window")." "; + } + return $result; +} + +sub close { + if ($ENV{'environment.remotenavmap'} ne 'on') { return ''; } + return(< +window.status='Accessing Nav Control'; +menu=window.open("/adm/rat/empty.html","loncapanav", + "height=600,width=400,scrollbars=1"); +window.status='Closing Nav Control'; +menu.close(); +window.status='Done.'; + +ENDCLOSE +} + +sub update { + if ($ENV{'environment.remotenavmap'} ne 'on') { return ''; } + if (!$ENV{'request.course.id'}) { return ''; } + if ($ENV{'REQUEST_URI'}=~m|^/adm/navmaps|) { return ''; } + return(< + +ENDUPDATE +} + sub handler { my $r = shift; real_handler($r); @@ -124,6 +161,28 @@ sub real_handler { &Apache::loncommon::no_cache($r); $r->send_http_header; + if ($ENV{QUERY_STRING} eq 'collapseExternal') { + &Apache::lonnet::put('environment',{'remotenavmap' => 'off'}); + &Apache::lonnet::appenv('environment.remotenavmap' => 'off'); + $r->print(<<"ENDSUBM"); + + + + + + +ENDSUBM + return; + } + if ($ENV{QUERY_STRING} eq 'launchExternal') { + &Apache::lonnet::put('environment',{'remotenavmap' => 'on'}); + &Apache::lonnet::appenv('environment.remotenavmap' => 'on'); + } + # Create the nav map my $navmap = Apache::lonnavmaps::navmap->new(); @@ -136,22 +195,36 @@ sub real_handler { $r->print("\n"); $r->print("".&mt('Navigate Course Contents').""); # ------------------------------------------------------------ Get query string - &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register']); + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register','sort']); # ----------------------------------------------------- Force menu registration my $addentries=''; + my $more_unload; + if ($ENV{'environment.remotenavmap'} eq 'on') { + $r->print(''); +# FIXME need to be smarter to only catch window close events +# $more_unload="collapse()" + } if ($ENV{'form.register'}) { - $addentries=' onLoad="'.&Apache::lonmenu::loadevents(). - '" onUnload="'.&Apache::lonmenu::unloadevents().'"'; - $r->print(&Apache::lonmenu::registerurl(1)); + $addentries=' onLoad="'.&Apache::lonmenu::loadevents(). + '" onUnload="'.&Apache::lonmenu::unloadevents().';'. + $more_unload.'"'; + $r->print(&Apache::lonmenu::registerurl(1)); + } else { + $addentries=' onUnload="'.$more_unload.'"'; } # Header $r->print(''. &Apache::loncommon::bodytag('Navigate Course Contents','', $addentries,'','',$ENV{'form.register'})); - $r->print(''); - + $r->print(''. + &Apache::loncommon::help_open_menu('','Navigation Screen','Navigation_Screen','',undef,'RAT')); + $r->rflush(); # Check that it's defined @@ -185,6 +258,27 @@ sub real_handler { } } + if ($ENV{QUERY_STRING} eq 'launchExternal') { + $r->print(' +
+
'); + $r->print(' + '); + } + + if ($ENV{'environment.remotenavmap'} ne 'on') { + $r->print(&launch_win('link','yes')); + } + if ($ENV{'environment.remotenavmap'} eq 'on') { +# $r->print("" . + $r->print("" . + &mt("Close separate navigation window"). + "    "); + } + my $jumpToFirstHomework = 0; # Check to see if the student is jumping to next open, do-able problem if ($ENV{QUERY_STRING} eq 'jumpToFirstHomework') { @@ -245,8 +339,20 @@ sub real_handler { &mt("Show Only Uncompleted Homework")."    "); } + $r->print("
+ + Sort by: + + + +
"); # renderer call - my $renderArgs = { 'cols' => [0,1,2,3], + my $renderArgs = { 'cols' => [0,2,3], + 'sort' => $ENV{'form.sort'}, 'url' => '/adm/navmaps', 'navmap' => $navmap, 'suppressNavmap' => 1, @@ -303,8 +409,16 @@ sub getLinkForResource { # Check to see if there are any pages in the stack foreach $res (@$stack) { - if (defined($res) && $res->is_page()) { - return $res->src(); + if (defined($res)) { + if ($res->is_page()) { + return $res->src(); + } + # in case folder was skipped over as "only sequence" + my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb()); + if ($map=~/\.page$/) { + return &Apache::lonnet::clutter($map).'#'. + &Apache::lonnet::escape(&Apache::lonnet::declutter($src)); + } } } @@ -319,9 +433,9 @@ sub getLinkForResource { return $res->src(); } -# Convenience function: This seperates the logic of how to create +# Convenience function: This separates the logic of how to create # the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned", -# etc.) into a seperate function. It takes a resource object as the +# etc.) into a separate function. It takes a resource object as the # first parameter, and the part number of the resource as the second. # It's basically a big switch statement on the status of the resource. @@ -371,7 +485,7 @@ sub getDescription { $triesString = "$triesString"; } } - if ($res->duedate()) { + if ($res->duedate($part)) { return &mt("Due")." " . timeToHumanString($res->duedate($part)) . " $triesString"; } else { @@ -386,15 +500,15 @@ sub getDescription { # Convenience function, so others can use it: Is the problem due in less then # 24 hours, and still can be done? -sub dueInLessThen24Hours { +sub dueInLessThan24Hours { my $res = shift; my $part = shift; my $status = $res->status($part); return ($status == $res->OPEN() || $status == $res->TRIES_LEFT()) && - $res->duedate() && $res->duedate() < time()+(24*60*60) && - $res->duedate() > time(); + $res->duedate($part) && $res->duedate($part) < time()+(24*60*60) && + $res->duedate($part) > time(); } # Convenience function, so others can use it: Is there only one try remaining for the @@ -406,8 +520,8 @@ sub lastTry { my $tries = $res->tries($part); my $maxtries = $res->maxtries($part); return $tries && $maxtries && $maxtries > 1 && - $maxtries - $tries == 1 && $res->duedate() && - $res->duedate() > time(); + $maxtries - $tries == 1 && $res->duedate($part) && + $res->duedate($part) > time(); } # This puts a human-readable name on the ENV variable. @@ -869,10 +983,7 @@ sub render_resource { my $filter = $it->{FILTER}; my $title = $resource->compTitle(); - if ($src =~ /^\/uploaded\//) { - $nonLinkedText=$title; - $title = ''; - } + my $partLabel = ""; my $newBranchText = ""; @@ -895,12 +1006,8 @@ sub render_resource { $icon = $params->{'indentString'}; } } else { - my $curfext= (split (/\./,$resource->src))[-1]; - my $embstyle = &Apache::loncommon::fileembstyle($curfext); - # The unless conditional that follows is a bit of overkill - if (!(!defined($embstyle) || $embstyle eq 'unk' || $embstyle eq 'hdn')) { - $icon = ""; - } + $icon = ""; } # Display the correct map icon to open or shut map @@ -970,7 +1077,11 @@ sub render_resource { if ($resource->is_problem() && $part ne '0' && !$params->{'condensed'}) { - $partLabel = " (Part $part)"; + my $displaypart=&Apache::lonnet::EXT('resource.'.$part.'.display', + $resource->symb()); + unless ($displaypart) { $displaypart=$part; } + $partLabel = " (Part: $displaypart)"; + $link.='#'.&Apache::lonnet::escape($part); $title = ""; } @@ -978,9 +1089,12 @@ sub render_resource { $nonLinkedText .= ' (' . $resource->countParts() . ' parts)'; } - if (!$params->{'resource_nolink'} && $src !~ /^\/uploaded\// && - !$resource->is_sequence()) { - $result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText"; + my $target; + if ($ENV{'environment.remotenavmap'} eq 'on') { + $target=' target="loncapaclient" '; + } + if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) { + $result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText"; } else { $result .= " $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText"; } @@ -1016,8 +1130,11 @@ sub render_communication_status { if ($resource->getErrors()) { my $errors = $resource->getErrors(); + my $errorcount = 0; foreach (split(/,/, $errors)) { + last if ($errorcount>=10); # Only output 10 bombs maximum if ($_) { + $errorcount++; $errorHTML .= ' ' . 'is_problem()) { $color = $colormap{$resource->status}; - if (dueInLessThen24Hours($resource, $part) || + if (dueInLessThan24Hours($resource, $part) || lastTry($resource, $part)) { $color = $hurryUpColor; } @@ -1121,35 +1238,53 @@ my @statuses = ($resObj->CORRECT, $resOb use Data::Dumper; sub render_parts_summary_status { my ($resource, $part, $params) = @_; - if (!$resource->is_problem()) { return ''; } + if (!$resource->is_problem() && !$resource->contains_problem) { return ''; } if ($params->{showParts}) { return ''; } my $td = "\n"; my $endtd = "\n"; + my @probs; - # If there is a single part, just show the simple status - if ($resource->singlepart()) { - my $status = $resource->simpleStatus('0'); - return $td . "" - . $statusStrings{$status} . "" . $endtd; - } - - # Now we can be sure the $part doesn't really matter. - my $statusCount = $resource->simpleStatusCount(); - my @counts; - foreach my $status(@statuses) { - # decouple display order from the simpleStatusCount order - my $slot = Apache::lonnavmaps::resource::statusToSlot($status); - if ($statusCount->[$slot]) { - push @counts, "" . $statusCount->[$slot] . ' ' + if ($resource->contains_problem) { + @probs=$resource->retrieveResources($resource,sub { $_[0]->is_problem() },1,0); + } else { + @probs=($resource); + } + my $return; + my %overallstatus; + my $totalParts; + foreach my $resource (@probs) { + # If there is a single part, just show the simple status + if ($resource->singlepart()) { + my $status = $resource->simpleStatus(${$resource->parts}[0]); + $overallstatus{$status}++; + $totalParts++; + next; + } + # Now we can be sure the $part doesn't really matter. + my $statusCount = $resource->simpleStatusCount(); + my @counts; + foreach my $status (@statuses) { + # decouple display order from the simpleStatusCount order + my $slot = Apache::lonnavmaps::resource::statusToSlot($status); + if ($statusCount->[$slot]) { + $overallstatus{$status}+=$statusCount->[$slot]; + $totalParts+=$statusCount->[$slot]; + } + } + } + $return.= $td . $totalParts . ' parts: '; + foreach my $status (@statuses) { + if ($overallstatus{$status}) { + $return.="" . $overallstatus{$status} . ' ' . $statusStrings{$status} . ""; } } - - return $td . $resource->countParts() . ' parts: ' . join (', ', @counts) . $endtd; + $return.= $endtd; + return $return; } my @preparedColumns = (\&render_resource, \&render_communication_status, @@ -1184,6 +1319,7 @@ sub render { my $jump = $args->{'jump'}; my $here = $args->{'here'}; my $suppressNavmap = setDefault($args->{'suppressNavmap'}, 0); + my $closeAllPages = setDefault($args->{'closeAllPages'}, 0); my $currentJumpDelta = 2; # change this to change how many resources are displayed # before the current resource when using #current @@ -1236,7 +1372,10 @@ sub render { #$currenturl=~s/^[^\/]+//; $here = $jump = &Apache::lonnet::symbread($currenturl); - } + } else { + &Apache::lonnet::logthis("Hrrm,"); + + } # Step three: Ensure the folders are open my $mapIterator = $navmap->getIterator(undef, undef, undef, 1); @@ -1292,7 +1431,7 @@ sub render { $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition); } } - + # (re-)Locate the jump point, if any # Note this does not take filtering or hidden into account... need # to be fixed? @@ -1355,9 +1494,43 @@ sub render { "&here=" . Apache::lonnet::escape($here) . "\">".&mt('Open All Folders').""; } - $result .= "

\n"; - } + $result .= "\n"; + } + # Check for any unread discussions in all resources. + if (!$args->{'resource_no_folder_link'}) { + my $totdisc = 0; + my $haveDisc = ''; + my @allres=$navmap->retrieveResources(); + foreach my $resource (@allres) { + if ($resource->hasDiscussion()) { + my $ressymb; + if ($resource->symb() =~ m-(___adm/\w+/\w+)/(\d+)/bulletinboard$-) { + $ressymb = 'bulletin___'.$2.$1.'/'.$2.'/bulletinboard'; + } else { + $ressymb = $resource->symb(); + } + $haveDisc .= $ressymb.':'; + $totdisc ++; + } + } + if ($totdisc > 0) { + $haveDisc =~ s/:$//; + my %lt = &Apache::lonlocal::texthash( + 'mapr' => 'Mark all posts read', + ); + $result .= (<$lt{'mapr'} (Help: NavMaps_MarkPostsLink) +
+ + +
+END + } else { + $result .= '
'; + } + } + $result .= "
\n"; if ($r) { $r->print($result); $r->rflush(); @@ -1429,7 +1602,33 @@ sub render { $args->{'here'} = $here; $args->{'indentLevel'} = -1; # first BEGIN_MAP takes this to 0 - while ($curRes = $it->next()) { + my @resources; + my $code='';# sub { !(shift->is_map();) }; + if ($args->{'sort'} eq 'title') { + @resources=$navmap->retrieveResources(undef, + sub { !shift->is_map(); }); + @resources= sort {lc($a->compTitle) cmp lc($b->compTitle)} @resources; + } elsif ($args->{'sort'} eq 'duedate') { + @resources=$navmap->retrieveResources(undef, + sub { shift->is_problem(); }); + @resources= sort + { + if ($a->duedate ne $b->duedate) { + return $a->duedate cmp $b->duedate; + } else { + lc($a->compTitle) cmp lc($b->compTitle) + } + } @resources; + } + + while (1) { + if ($args->{'sort'}) { + $curRes = shift(@resources); + } else { + $curRes = $it->next($closeAllPages); + } + if (!$curRes) { last; } + # Maintain indentation level. if ($curRes == $it->BEGIN_MAP() || $curRes == $it->BEGIN_BRANCH() ) { @@ -1543,14 +1742,23 @@ sub render { # Set up some data about the parts that the cols might want my $filter = $it->{FILTER}; - my $stack = $it->getStack(); - my $src = getLinkForResource($stack); - + my $src; + if ($args->{'sort'}) { + $src = $curRes->src(); # FIXME this is wrong for .pages + } else { + my $stack = $it->getStack(); + $src=getLinkForResource($stack); + } + my $anchor=''; + if ($src=~s/(\#.*)$//) { + $anchor=$1; + } my $srcHasQuestion = $src =~ /\?/; $args->{"resourceLink"} = $src. ($srcHasQuestion?'&':'?') . - 'symb=' . &Apache::lonnet::escape($curRes->symb()); - + 'symb=' . &Apache::lonnet::escape($curRes->symb()). + $anchor; + # Now, display each column. foreach my $col (@$cols) { my $colHTML = ''; @@ -1600,7 +1808,12 @@ sub render { # it's quite likely this might fix other browsers, too, and # certainly won't hurt anything. if ($displayedJumpMarker) { - $result .= "\n"; + $result .= " +"; } $result .= ""; @@ -1791,6 +2004,7 @@ sub generate_course_user_opt { sub generate_email_discuss_status { my $self = shift; + my $symb = shift; if ($self->{EMAIL_DISCUSS_GENERATED}) { return; } my $cid=$ENV{'request.course.id'}; @@ -1803,6 +2017,15 @@ sub generate_email_discuss_status { $courseLeaveTime : $logoutTime); my %discussiontime = &Apache::lonnet::dump('discussiontimes', $cdom, $cnum); + my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss', + $ENV{'user.domain'},$ENV{'user.name'},'lastread'); + my %lastreadtime = (); + foreach (keys %lastread) { + my $key = $_; + $key =~ s/_lastread$//; + $lastreadtime{$key} = $lastread{$_}; + } + my %feedback=(); my %error=(); my $keys = &Apache::lonnet::reply('keys:'. @@ -1836,6 +2059,7 @@ sub generate_email_discuss_status { $self->{ERROR_MSG} = \%error; # what is this? JB $self->{DISCUSSION_TIME} = \%discussiontime; $self->{EMAIL_STATUS} = \%emailstatus; + $self->{LAST_READ} = \%lastreadtime; $self->{EMAIL_DISCUSS_GENERATED} = 1; } @@ -1904,8 +2128,21 @@ sub hasDiscussion { if (!defined($self->{DISCUSSION_TIME})) { return 0; } #return defined($self->{DISCUSSION_TIME}->{$symb}); - return $self->{DISCUSSION_TIME}->{$symb} > - $self->{LAST_CHECK}; + +# backward compatibility (bulletin boards used to be 'wrapped') + my $ressymb = $symb; + if ($ressymb =~ m|adm/(\w+)/(\w+)/(\d+)/bulletinboard$|) { + unless ($ressymb =~ m|adm/wrapper/adm|) { + $ressymb = 'bulletin___'.$3.'___adm/wrapper/adm/'.$1.'/'.$2.'/'.$3.'/bulletinboard'; + } + } + + if ( defined ( $self->{LAST_READ}->{$ressymb} ) ) { + return $self->{DISCUSSION_TIME}->{$ressymb} > $self->{LAST_READ}->{$ressymb}; + } else { +# return $self->{DISCUSSION_TIME}->{$ressymb} > $self->{LAST_CHECK}; # v.1.1 behavior + return $self->{DISCUSSION_TIME}->{$ressymb} > 0; # in 1.2 will display speech bubble icons for all items with posts until marked as read (even if read in v 1.1). + } } # Private method: Does the given resource (as a symb string) have @@ -2102,7 +2339,11 @@ sub parmval_real { # ----------------------------------------------------- fourth , check default - my $default=&Apache::lonnet::metadata($fn,$rwhat.'.default'); + my $meta_rwhat=$rwhat; + $meta_rwhat=~s/\./_/g; + my $default=&Apache::lonnet::metadata($fn,$meta_rwhat); + if (defined($default)) { return $default} + $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat); if (defined($default)) { return $default} # --------------------------------------------------- fifth , cascade up parts @@ -2157,7 +2398,7 @@ want to know is if I resources matc parameter will allow you to avoid potentially expensive enumeration of all matching resources. -=item * B(map, filterFunc, recursive): +=item * B(map, filterFunc, recursive): Convience method for @@ -2345,6 +2586,8 @@ consisting entirely of empty resources e ending resource, will cause a lot of BRANCH_STARTs and BRANCH_ENDs, but only one resource will be returned. +=back + =head2 Normal Usage Normal usage of the iterator object is to do the following: @@ -2365,8 +2608,6 @@ the depth of the iterator to see when it code. It is difficult to get right and harder to understand then this. They should be migrated to this new style. -=back - =cut # Here are the tokens for the iterator: @@ -2503,7 +2744,7 @@ sub new { } # Check: Was this only one resource, a map? - if ($resourceCount == 1 && $resource->is_map() && !$self->{FORCE_TOP}) { + if ($resourceCount == 1 && $resource->is_sequence() && !$self->{FORCE_TOP}) { my $firstResource = $resource->map_start(); my $finishResource = $resource->map_finish(); return @@ -2536,7 +2777,7 @@ sub new { sub next { my $self = shift; - + my $closeAllPages=shift; if ($self->{FINISHED}) { return END_ITERATOR(); } @@ -2550,7 +2791,7 @@ sub next { if ($self->{RECURSIVE_ITERATOR_FLAG}) { # grab the next from the recursive iterator - my $next = $self->{RECURSIVE_ITERATOR}->next(); + my $next = $self->{RECURSIVE_ITERATOR}->next($closeAllPages); # is it a begin or end map? If so, update the depth if ($next == BEGIN_MAP() ) { $self->{RECURSIVE_DEPTH}++; } @@ -2664,7 +2905,7 @@ sub next { # That ends the main iterator logic. Now, do we want to recurse # down this map (if this resource is a map)? - if ($self->{HERE}->is_map() && + if ( ($self->{HERE}->is_sequence() || (!$closeAllPages && $self->{HERE}->is_page())) && (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) { $self->{RECURSIVE_ITERATOR_FLAG} = 1; my $firstResource = $self->{HERE}->map_start(); @@ -2682,7 +2923,7 @@ sub next { my $browsePriv = $self->{HERE}->browsePriv(); if (!$self->{HERE}->src() || (!($browsePriv eq 'F') && !($browsePriv eq '2')) ) { - return $self->next(); + return $self->next($closeAllPages); } return $self->{HERE}; @@ -2734,7 +2975,7 @@ package Apache::lonnavmaps::DFSiterator; # useful for pre-processing of some kind, and is in fact used by the main # iterator that way, but that's about it. # One could imagine merging this into the init routine of the main iterator, -# but this might as well be left seperate, since it is possible some other +# but this might as well be left separate, since it is possible some other # use might be found for it. - Jeremy # Unlike the main iterator, this DOES return all resources, even blank ones. @@ -3151,6 +3392,15 @@ Returns true if the resource is a sequen =cut +sub hasResource { + my $self = shift; + return $self->{NAV_MAP}->hasResource(@_); +} + +sub retrieveResources { + my $self = shift; + return $self->{NAV_MAP}->retrieveResources(@_); +} sub is_html { my $self=shift; @@ -3167,7 +3417,15 @@ sub is_page { sub is_problem { my $self=shift; my $src = $self->src(); - return ($src =~ /problem$/); + return ($src =~ /\.(problem|exam|quiz|assess|survey|form|library)$/) +} +sub contains_problem { + my $self=shift; + if ($self->is_page()) { + my $hasProblem=$self->hasResource($self,sub { $_[0]->is_problem() },1); + return $hasProblem; + } + return 0; } sub is_sequence { my $self=shift; @@ -3175,6 +3433,23 @@ sub is_sequence { return $self->navHash("is_map_", 1) && $self->navHash("map_type_" . $self->map_pc()) eq 'sequence'; } +sub is_survey { + my $self = shift(); + my $part = shift(); + if ($self->parmval('type',$part) eq 'survey') { + return 1; + } + if ($self->src() =~ /\.(survey)$/) { + return 1; + } + return 0; +} + +sub is_empty_sequence { + my $self=shift; + my $src = $self->src(); + return !$self->is_page() && $self->navHash("is_map_", 1) && !$self->navHash("map_type_" . $self->map_pc()); +} # Private method: Shells out to the parmval in the nav map, handler parts. sub parmval { @@ -3351,6 +3626,11 @@ sub awarded { } sub duedate { (my $self, my $part) = @_; + my $interval=$self->parmval("interval", $part); + if ($interval) { + my $first_access=&Apache::lonnet::get_first_access('map',$self->symb); + if ($first_access) { return ($first_access+$interval); } + } return $self->parmval("duedate", $part); } sub maxtries { @@ -3578,32 +3858,44 @@ sub extractParts { # Retrieve part count, if this is a problem if ($self->is_problem()) { + my $partorder = &Apache::lonnet::metadata($self->src(), 'partorder'); my $metadata = &Apache::lonnet::metadata($self->src(), 'packages'); - if (!$metadata) { - $self->{RESOURCE_ERROR} = 1; - $self->{PARTS} = []; - $self->{PART_TYPE} = {}; - return; - } - foreach (split(/\,/,$metadata)) { - if ($_ =~ /^part_(.*)$/) { - my $part = $1; - # This floods the logs if it blows up - if (defined($parts{$part})) { - Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->symb()); - } - - # check to see if part is turned off. - if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) { - $parts{$part} = 1; - } - } + if ($partorder) { + my @parts; + for my $part (split (/,/,$partorder)) { + if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) { + push @parts, $part; + $parts{$part} = 1; + } + } + $self->{PARTS} = \@parts; + } else { + if (!$metadata) { + $self->{RESOURCE_ERROR} = 1; + $self->{PARTS} = []; + $self->{PART_TYPE} = {}; + return; + } + foreach (split(/\,/,$metadata)) { + if ($_ =~ /^part_(.*)$/) { + my $part = $1; + # This floods the logs if it blows up + if (defined($parts{$part})) { + &Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->symb()); + } + + # check to see if part is turned off. + + if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) { + $parts{$part} = 1; + } + } + } + my @sortedParts = sort keys %parts; + $self->{PARTS} = \@sortedParts; } - - my @sortedParts = sort keys %parts; - $self->{PARTS} = \@sortedParts; my %responseIdHash; my %responseTypeHash; @@ -3615,7 +3907,7 @@ sub extractParts { } # Now, the unfortunate thing about this is that parts, part name, and - # response if are delimited by underscores, but both the part + # response id are delimited by underscores, but both the part # name and response id can themselves have underscores in them. # 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 @@ -3627,7 +3919,6 @@ sub extractParts { my $partIdSoFar = ''; my @partChunks = split /_/, $partStuff; my $i = 0; - for ($i = 0; $i < scalar(@partChunks); $i++) { if ($partIdSoFar) { $partIdSoFar .= '_'; } $partIdSoFar .= $partChunks[$i]; @@ -3640,7 +3931,20 @@ sub extractParts { } } } - + my $resorder = &Apache::lonnet::metadata($self->src(),'responseorder'); + if ($resorder) { + my @resorder=split(/,/,$resorder); + foreach my $part (keys(%responseIdHash)) { + my %resids = map { ($_,1) } @{ $responseIdHash{$part} }; + my @neworder; + foreach my $possibleid (@resorder) { + if (exists($resids{$possibleid})) { + push(@neworder,$possibleid); + } + } + $responseIdHash{$part}=\@neworder; + } + } $self->{RESPONSE_IDS} = \%responseIdHash; $self->{RESPONSE_TYPES} = \%responseTypeHash; } @@ -3832,7 +4136,7 @@ sub getCompletionStatus { my $status = $self->queryRestoreHash('solved', shift); - # Left as seperate if statements in case we ever do more with this + # Left as separate if statements in case we ever do more with this if ($status eq 'correct_by_student') {return $self->CORRECT;} if ($status eq 'correct_by_override') {return $self->CORRECT_BY_OVERRIDE; } if ($status eq 'incorrect_attempted') {return $self->INCORRECT; } @@ -3984,7 +4288,7 @@ sub status { if ($dateStatus == PAST_DUE_ANSWER_LATER || $dateStatus == PAST_DUE_NO_ANSWER ) { - return $dateStatus; + return $suppressFeedback ? ANSWER_SUBMITTED : $dateStatus; } if ($dateStatus == ANSWER_OPEN) {