--- loncom/interface/lonnavmaps.pm 2004/01/30 23:38:19 1.244 +++ loncom/interface/lonnavmaps.pm 2004/09/15 21:10:11 1.290 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.244 2004/01/30 23:38:19 albertel Exp $ +# $Id: lonnavmaps.pm,v 1.290 2004/09/15 21:10:11 matthew 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,73 @@ my %colormap = # is not yet done and due in less then 24 hours my $hurryUpColor = "#FF0000"; +sub launch_win { + my ($mode,$script,$toplinkitems)=@_; + 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(< +window.status='Accessing Nav Control'; +menu=window.open("/adm/rat/empty.html","loncapanav", + "height=600,width=400,scrollbars=1"); +window.status='Closing Nav Control'; +menu.close(); +window.status='Done.'; + +ENDCLOSE +} + +sub nav_control_js { + my $nav=($ENV{'environment.remotenavmap'} eq 'on'); + return (< + +ENDUPDATE +} + sub handler { my $r = shift; real_handler($r); @@ -124,6 +178,48 @@ sub real_handler { &Apache::loncommon::no_cache($r); $r->send_http_header; + my %toplinkitems=(); + + if ($ENV{QUERY_STRING} eq 'collapseExternal') { + &Apache::lonnet::put('environment',{'remotenavmap' => 'off'}); + &Apache::lonnet::appenv('environment.remotenavmap' => 'off'); + my $menu=&Apache::lonmenu::reopenmenu(); + my $navstatus=&Apache::lonmenu::get_nav_status(); + if ($menu) { + $menu=(<print(<<"ENDSUBM"); + + + + + + +ENDSUBM + return; + } + if ($ENV{QUERY_STRING} eq 'launchExternal') { + &Apache::lonnet::put('environment',{'remotenavmap' => 'on'}); + &Apache::lonnet::appenv('environment.remotenavmap' => 'on'); + } + # Create the nav map my $navmap = Apache::lonnavmaps::navmap->new(); @@ -136,26 +232,43 @@ sub real_handler { $r->print("\n"); $r->print("".&mt('Navigate Course Contents').""); # ------------------------------------------------------------ Get query string - &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register']); + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register','sort','showOnlyHomework']); # ----------------------------------------------------- Force menu registration my $addentries=''; + my $more_unload; + my $body_only=''; + if ($ENV{'environment.remotenavmap'} eq 'on') { + $r->print(''); +# FIXME need to be smarter to only catch window close events +# $more_unload="collapse()" + $body_only=1; + } if ($ENV{'form.register'}) { - $addentries=' onLoad="'.&Apache::lonmenu::loadevents(). - '" onUnload="'.&Apache::lonmenu::unloadevents().'"'; - $r->print(&Apache::lonmenu::registerurl(1)); + $addentries=' onLoad="'.&Apache::lonmenu::loadevents(). + '" onUnload="'.&Apache::lonmenu::unloadevents().';'. + $more_unload.'"'; + $r->print(&Apache::lonmenu::registerurl(1)); + } else { + $addentries=' onUnload="'.$more_unload.'"'; } # Header $r->print(''. &Apache::loncommon::bodytag('Navigate Course Contents','', - $addentries,'','',$ENV{'form.register'})); + $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; @@ -185,9 +298,28 @@ 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',\%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} eq 'jumpToFirstHomework') { + if ($ENV{QUERY_STRING} =~ /^jumpToFirstHomework/) { $jumpToFirstHomework = 1; # Find the next homework problem that they can do. my $iterator = $navmap->getIterator(undef, undef, undef, 1); @@ -220,8 +352,9 @@ sub real_handler { $r->print("All homework assignments have been completed.

"); } } else { - $r->print("" . - &mt("Go To My First Homework Problem")."    "); + &add_linkitem(\%toplinkitems,'firsthomework', + 'location.href="navmaps?jumpToFirstHomework"', + "Show Me My First Homework Problem"); } my $suppressEmptySequences = 0; @@ -230,30 +363,51 @@ sub real_handler { # Display only due homework. my $showOnlyHomework = 0; - if ($ENV{QUERY_STRING} eq 'showOnlyHomework') { + if ($ENV{'form.showOnlyHomework'} eq "1") { $showOnlyHomework = 1; $suppressEmptySequences = 1; $filterFunc = sub { my $res = shift; return $res->completable() || $res->is_map(); }; + &add_linkitem(\%toplinkitems,'everything', + 'location.href="locatnavmaps?sort='.$ENV{'form.sort'}.'"', + "Show Everything"); $r->print("

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

"); $ENV{'form.filter'} = ''; $ENV{'form.condition'} = 1; $resource_no_folder_link = 1; } else { - $r->print("" . - &mt("Show Only Uncompleted Homework")."    "); - } - + &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, - 'r' => $r}; + 'sort_html'=> $sort_html, + 'r' => $r, + 'caller' => 'navmapsdisplay', + 'linkitems' => \%toplinkitems}; my $render = render($renderArgs); $navmap->untieHashes(); @@ -303,8 +457,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 +481,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. @@ -386,7 +548,7 @@ 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); @@ -517,7 +679,7 @@ sub timeToHumanString { } # Not this year, so show the year - my $timeStr = strftime("on %A, %b %e %G at %I:%M %P", localtime($time)); + my $timeStr = strftime("on %A, %b %e %Y at %I:%M %P", localtime($time)); $timeStr =~ s/12:00 am/00:00/; $timeStr =~ s/12:00 pm/noon/; return $timeStr; @@ -869,10 +1031,7 @@ sub render_resource { my $filter = $it->{FILTER}; my $title = $resource->compTitle(); - if ($src =~ /^\/uploaded\//) { - $nonLinkedText=$title; - $title = ''; - } + my $partLabel = ""; my $newBranchText = ""; @@ -966,7 +1125,9 @@ sub render_resource { if ($resource->is_problem() && $part ne '0' && !$params->{'condensed'}) { - $partLabel = " (Part $part)"; + my $displaypart=$resource->part_display($part); + $partLabel = " (Part: $displaypart)"; + $link.='#'.&Apache::lonnet::escape($part); $title = ""; } @@ -974,9 +1135,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"; } @@ -1012,8 +1176,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; } @@ -1117,35 +1284,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, @@ -1180,6 +1365,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 @@ -1288,7 +1474,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? @@ -1342,18 +1528,81 @@ sub render { } if ($printCloseAll && !$args->{'resource_no_folder_link'}) { + my ($link,$text); if ($condition) { - $result.="".&mt('Close All Folders').""; + $link='"navmaps?condition=0&filter=&'.$queryString. + '&here='.&Apache::lonnet::escape($here).'"'; + $text='Close All Folders'; } else { - $result.="".&mt('Open All Folders').""; + $link='"navmaps?condition=1&filter=&'.$queryString. + '&here='.&Apache::lonnet::escape($here).'"'; + $text='Open All Folders'; + } + if ($args->{'caller'} eq 'navmapsdisplay') { + &add_linkitem($args->{'linkitems'},'changefolder', + 'location.href='.$link,$text); + } else { + $result.=''.&mt($text).''; + } + $result .= "\n"; + } + + # Check for any unread discussions in all resources. + if ($args->{'caller'} eq 'navmapsdisplay') { + 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 $navurl = $ENV{'QUERY_STRING'}; + &add_linkitem($args->{'linkitems'},'clearbubbles', + 'document.clearbubbles.submit()', + 'Mark all posts read'); + $result .= (< + + + +END + } + } + + if ($args->{'caller'} eq 'navmapsdisplay') { + $result .= ''; + if ($ENV{'environment.remotenavmap'} ne 'on') { + $result .= ''; + } else { + $result .= ''; } - $result .= "

\n"; - } + $result.=&show_linkitems($args->{'linkitems'}); + if ($args->{'sort_html'}) { + if ($ENV{'environment.remotenavmap'} ne 'on') { + $result.=''. + ''; + } else { + $result.=''; + } + } + $result .= '
'. + &Apache::loncommon::help_open_menu('','Navigation Screen','Navigation_Screen','',undef,'RAT').' 
   '.$args->{'sort_html'}.'

'. + $args->{'sort_html'}.'
'; + } elsif ($args->{'sort_html'}) { + $result.=$args->{'sort_html'}; + } + $result .= "
\n"; if ($r) { $r->print($result); $r->rflush(); @@ -1425,7 +1674,48 @@ sub render { $args->{'here'} = $here; $args->{'indentLevel'} = -1; # first BEGIN_MAP takes this to 0 - while ($curRes = $it->next()) { + my @resources; + my $code='';# sub { !(shift->is_map();) }; + if ($args->{'sort'} eq 'title') { + my $oldFilterFunc = $filterFunc; + my $filterFunc= + sub { + my ($res)=@_; + if ($res->is_map()) { return 0;} + return &$oldFilterFunc($res); + }; + @resources=$navmap->retrieveResources(undef,$filterFunc); + @resources= sort { + my ($atitle,$btitle) = (lc($a->compTitle),lc($b->compTitle)); + $atitle=~s/^\s*//; + $btitle=~s/^\s*//; + return $atitle cmp $btitle + } @resources; + } elsif ($args->{'sort'} eq 'duedate') { + @resources=$navmap->retrieveResources(undef, + sub { shift->is_problem(); }); + @resources= sort + { + if ($a->duedate ne $b->duedate) { + return $a->duedate cmp $b->duedate; + } else { + lc($a->compTitle) cmp lc($b->compTitle) + } + } @resources; + } else { + #unknow sort mechanism or default + undef($args->{'sort'}); + } + + + while (1) { + if ($args->{'sort'}) { + $curRes = shift(@resources); + } else { + $curRes = $it->next($closeAllPages); + } + if (!$curRes) { last; } + # Maintain indentation level. if ($curRes == $it->BEGIN_MAP() || $curRes == $it->BEGIN_BRANCH() ) { @@ -1539,14 +1829,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 = ''; @@ -1596,7 +1895,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 .= ""; @@ -1612,6 +1916,45 @@ sub render { return $result; } +sub add_linkitem { + my ($linkitems,$name,$cmd,$text)=@_; + $$linkitems{$name}{'cmd'}=$cmd; + $$linkitems{$name}{'text'}=&mt($text); +} + +sub show_linkitems { + my ($linkitems)=@_; + my @linkorder = ("launchnav","closenav","firsthomework","everything", + "uncompleted","changefolder","clearbubbles"); + + my $result .= (< + +
+   +
'."\n"; + return $result; +} + 1; package Apache::lonnavmaps::navmap; @@ -1787,6 +2130,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'}; @@ -1799,6 +2143,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:'. @@ -1832,6 +2185,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; } @@ -1900,8 +2254,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 @@ -1972,9 +2339,14 @@ sub getById { sub getBySymb { my $self = shift; my $symb = shift; + my ($mapUrl, $id, $filename) = &Apache::lonnet::decode_symb($symb); my $map = $self->getResourceByUrl($mapUrl); - return $self->getById($map->map_pc() . '.' . $id); + my $returnvalue = undef; + if (ref($map)) { + $returnvalue = $self->getById($map->map_pc() .'.'.$id); + } + return $returnvalue; } sub getByMapPc { @@ -2098,7 +2470,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 @@ -2153,7 +2529,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 @@ -2164,6 +2540,7 @@ in the filter function. =cut + sub getResourceByUrl { my $self = shift; my $resUrl = shift; @@ -2499,7 +2876,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 @@ -2532,7 +2909,7 @@ sub new { sub next { my $self = shift; - + my $closeAllPages=shift; if ($self->{FINISHED}) { return END_ITERATOR(); } @@ -2546,7 +2923,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}++; } @@ -2660,7 +3037,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(); @@ -2678,7 +3055,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}; @@ -2730,7 +3107,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. @@ -3147,6 +3524,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; @@ -3163,7 +3549,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; @@ -3171,6 +3565,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 { @@ -3347,6 +3758,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 { @@ -3390,7 +3806,16 @@ sub weight { $self->symb(), $ENV{'user.domain'}, $ENV{'user.name'}, $ENV{'request.course.sec'}); - +} +sub part_display { + my $self= shift(); my $partID = shift(); + if (! defined($partID)) { $partID = '0'; } + my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display', + $self->symb); + if (! defined($display) || $display eq '') { + $display = $partID; + } + return $display; } # Multiple things need this @@ -3480,6 +3905,16 @@ Returns the number of parts of the probl for single part problems, returns 1. For multipart, it returns the number of parts in the problem, not including psuedo-part 0. +=item * B(): + +Returns the total number of responses in the problem a student can answer. + +=item * B(): + +Returns a hash whose keys are the response types. The values are the number +of times each response type is used. This is for the I problem, not +just a single part. + =item * B(): Returns true if the problem is multipart, false otherwise. Use this instead @@ -3526,6 +3961,26 @@ sub countParts { return scalar(@{$parts}); # + $delta; } +sub countResponses { + my $self = shift; + my $count; + foreach my $part ($self->parts()) { + $count+= $self->responseIds($part); + } + return $count; +} + +sub responseTypes { + my $self = shift; + my %Responses; + foreach my $part ($self->parts()) { + foreach my $responsetype ($self->responseType($part)) { + $Responses{$responsetype}++ if (defined($responsetype)); + } + } + return %Responses; +} + sub multipart { my $self = shift; return $self->countParts() > 1; @@ -3613,6 +4068,7 @@ sub extractParts { } + # These hashes probably do not need names that end with "Hash".... my %responseIdHash; my %responseTypeHash; @@ -3647,6 +4103,30 @@ sub extractParts { } } } + my $resorder = &Apache::lonnet::metadata($self->src(),'responseorder'); + # + # Reorder the arrays in the %responseIdHash and %responseTypeHash + if ($resorder) { + my @resorder=split(/,/,$resorder); + foreach my $part (keys(%responseIdHash)) { + my $i=0; + my %resids = map { ($_,$i++) } @{ $responseIdHash{$part} }; + my @neworder; + foreach my $possibleid (@resorder) { + if (exists($resids{$possibleid})) { + push(@neworder,$resids{$possibleid}); + } + } + my @ids; + my @type; + foreach my $element (@neworder) { + push (@ids,$responseIdHash{$part}->[$element]); + push (@type,$responseTypeHash{$part}->[$element]); + } + $responseIdHash{$part}=\@ids; + $responseTypeHash{$part}=\@type; + } + } $self->{RESPONSE_IDS} = \%responseIdHash; $self->{RESPONSE_TYPES} = \%responseTypeHash; } @@ -3838,8 +4318,9 @@ 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_scantron') {return $self->CORRECT;} if ($status eq 'correct_by_override') {return $self->CORRECT_BY_OVERRIDE; } if ($status eq 'incorrect_attempted') {return $self->INCORRECT; } if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; } @@ -3990,7 +4471,7 @@ sub status { if ($dateStatus == PAST_DUE_ANSWER_LATER || $dateStatus == PAST_DUE_NO_ANSWER ) { - return $dateStatus; + return $suppressFeedback ? ANSWER_SUBMITTED : $dateStatus; } if ($dateStatus == ANSWER_OPEN) { 500 Internal Server Error

Internal Server Error

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

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

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