Diff for /loncom/interface/lonnavmaps.pm between versions 1.357 and 1.464

version 1.357, 2005/12/29 18:54:30 version 1.464, 2011/09/22 12:48:40
Line 2 Line 2
 # Navigate Maps Handler  # Navigate Maps Handler
 #  #
 # $Id$  # $Id$
   
 #  #
 # Copyright Michigan State University Board of Trustees  # Copyright Michigan State University Board of Trustees
 #  #
Line 27 Line 28
 #  #
 ###  ###
   
 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 );  
   
 # symbolic constants  
 sub SYMB { return 1; }  
 sub URL { return 2; }  
 sub NOTHING { return 3; }  
   
 # Some data  
   
 my $resObj = "Apache::lonnavmaps::resource";  
   
 # Keep these mappings in sync with lonquickgrades, which uses the colors  
 # instead of the icons.  
 my %statusIconMap =   
     (  
      $resObj->CLOSED       => '',  
      $resObj->OPEN         => 'navmap.open.gif',  
      $resObj->CORRECT      => 'navmap.correct.gif',  
      $resObj->PARTIALLY_CORRECT      => 'navmap.partial.gif',  
      $resObj->INCORRECT    => 'navmap.wrong.gif',  
      $resObj->ATTEMPTED    => 'navmap.ellipsis.gif',  
      $resObj->ERROR        => ''  
      );  
   
 my %iconAltTags =   
     ( 'navmap.correct.gif' => 'Correct',  
       'navmap.wrong.gif'   => 'Incorrect',  
       'navmap.open.gif'    => 'Open' );  
   
 # Defines a status->color mapping, null string means don't color  
 my %colormap =   
     ( $resObj->NETWORK_FAILURE        => '',  
       $resObj->CORRECT                => '',  
       $resObj->EXCUSED                => '#3333FF',  
       $resObj->PAST_DUE_ANSWER_LATER  => '',  
       $resObj->PAST_DUE_NO_ANSWER     => '',  
       $resObj->ANSWER_OPEN            => '#006600',  
       $resObj->OPEN_LATER             => '',  
       $resObj->TRIES_LEFT             => '',  
       $resObj->INCORRECT              => '',  
       $resObj->OPEN                   => '',  
       $resObj->NOTHING_SET            => '',  
       $resObj->ATTEMPTED              => '',  
       $resObj->ANSWER_SUBMITTED       => '',  
       $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  
 my $hurryUpColor = "#FF0000";  
   
 sub launch_win {  
     my ($mode,$script,$toplinkitems,$firsttime)=@_;  
     my $result;  
     if ($script ne 'no') {  
  $result.='<script type="text/javascript">';  
     }  
     if ($firsttime) {  
  $result.='function launch_navmapwin() {  
                  newWindow=open(\'/adm/navmaps?launchExternalRoles\',\'loncapanav\',\'width=400,height=600,scrollbars=1\');  
                }';  
     } else {  
  $result.='function launch_navmapwin() {  
                  newWindow=open(\'/adm/navmaps?launchExternal\',\'loncapanav\',\'width=400,height=600,scrollbars=1\');  
                }';  
     }  
     if ($mode eq 'now') {  
  $result.="\nlaunch_navmapwin();\n";  
     }  
     if ($script ne 'no') {  
  $result.='</script>';  
     }  
     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(<<ENDCLOSE);  
 <script type="text/javascript">  
 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.';  
 </script>  
 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);  
 <form name="navform"></form>  
 <script type="text/javascript">  
 this.document.navform.action='/adm/navmaps#curloc';  
 this.document.navform.target='loncapanav';  
 this.document.navform.submit();  
 </script>  
 ENDUPDATE  
 }  
   
 sub handler {  
     my $r = shift;  
     real_handler($r);  
 }  
   
 sub real_handler {  
     my $r = shift;  
     #my $t0=[&gettimeofday()];  
     # Handle header-only request  
     if ($r->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=(<<MENU)  
              swmenu=$menu  
              swmenu.clearTimeout(swmenu.menucltim);  
      $navstatus  
 MENU  
         } else {  
     my $nothing = &Apache::lonhtmlcommon::javascript_nothing();  
     my $mainwindow='window.open('.$nothing.',"loncapaclient","",false);';  
     $menu=(<<MENU)  
              swmenu=$mainwindow  
      $navstatus  
 MENU  
  }  
         $r->send_http_header;  
  my $html=&Apache::lonxml::xmlbegin();  
  $r->print(<<"ENDSUBM");  
  $html  
         <head>  
   <script type="text/javascript">  
      function submitthis() {  
     $menu  
     self.close();  
     }  
   
    </script>  
         </head>  
  <body bgcolor="#FFFFFF" onLoad="submitthis()"></body>  
         </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(<<MENU);  
              <script type="text/javascript">  
              swmenu=$menu  
              swmenu.clearTimeout(swmenu.menucltim);  
      $navstatus  
              </script>  
 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<head>\n");  
     $r->print("<title>".&mt('Navigate Course Contents')."</title>");  
 # ------------------------------------------------------------ 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('<script type="text/javascript">  
                       function collapse() {  
                          this.document.location="/adm/navmaps?collapseExternal";  
                       }  
                    </script>');  
 # 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('</head>'.  
               &Apache::loncommon::bodytag('Navigate Course Contents','',  
   $addentries,$body_only,'',  
   $env{'form.register'}));  
     $r->print('<script>window.focus();</script>');  
        
     $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('<font size="+2" color="red">Coursemap undefined.</font>' .  
                   '</body></html>');  
         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('  
           <form name="returnwin" action="/adm/flip?postdata=navlaunch%3a"   
                 method="post" target="loncapaclient">  
           </form>');  
  $r->print('  
           <script type="text/javascript">  
               this.document.returnwin.submit();  
           </script>');  
     }  
   
     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("<font size='+2'>All homework assignments have been completed.</font><br /><br />");  
         }  
     } 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("<p><font size='+2'>".&mt("Uncompleted Homework")."</font></p>");  
         $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=("<form>  
                  <nobr>  
                     <input type=\"hidden\" name=\"showOnlyHomework\" value=\"".$env{'form.showOnlyHomework'}."\" />  
                     <input type=\"submit\" value=\"".&mt('Sort by:')."\" />  
                     <select name=\"sort\">  
                        <option value=\"default\" $selected{'default'}>".&mt('Default')."</option>  
                        <option value=\"title\"   $selected{'title'}  >".&mt('Title')."</option>  
                        <option value=\"duedate\" $selected{'duedate'}>".&mt('Duedate')."</option>  
                        <option value=\"discussion\" $selected{'discussion'}>".&mt('Has New Discussion')."</option>  
                     </select>  
                  </nobr>  
                </form>");  
     # 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("<p><font size='+1'>".&mt("All homework is currently completed").".</font></p>");  
         } else { # both jumpToFirstHomework and normal use the same: course must be empty  
             $r->print("<p><font size='+1'>This course is empty.</font></p>");  
         }  
     }  
     #my $td=&tv_interval($t0);  
     #$r->print("<br />$td");  
   
     $r->print("</body></html>");  
     $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  
 sub addToFilter {  
     my $hashIn = shift;  
     my $addition = shift;  
     my %hash = %$hashIn;  
     $hash{$addition} = 1;  
   
     return join (",", keys(%hash));  
 }  
   
 sub removeFromFilter {  
     my $hashIn = shift;  
     my $subtraction = shift;  
     my %hash = %$hashIn;  
   
     delete $hash{$subtraction};  
     return join(",", keys(%hash));  
 }  
   
 # Convenience function: Given a stack returned from getStack on the iterator,  
 # return the correct src() value.  
 sub getLinkForResource {  
     my $stack = shift;  
     my $res;  
   
     # Check to see if there are any pages in the stack  
     foreach $res (@$stack) {  
         if (defined($res)) {  
     my $anchor;  
     if ($res->is_page()) {  
  foreach (@$stack) { if (defined($_)) { $anchor = $_; }  }  
  $anchor=&Apache::lonnet::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());  
  return ($url,$res->shown_symb(),$anchor);  
     }  
         }  
     }  
   
     # Failing that, return the src of the last resource that is defined  
     # (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 = $_; }  
     }  
   
     return ($res->link(),$res->shown_symb());  
 }  
   
 # Convenience function: This separates the logic of how to create  
 # the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned",  
 # 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.  
   
 sub getDescription {  
     my $res = shift;  
     my $part = shift;  
     my $status = $res->status($part);  
   
     if ($status == $res->NETWORK_FAILURE) {   
         return &mt("Having technical difficulties; please check status later");   
     }  
     if ($status == $res->NOTHING_SET) {  
         return &mt("Not currently assigned.");  
     }  
     if ($status == $res->OPEN_LATER) {  
         return "Open " . timeToHumanString($res->opendate($part),'start');  
     }  
     if ($status == $res->OPEN) {  
         if ($res->duedate($part)) {  
             return &mt("Due")."  " .timeToHumanString($res->duedate($part),'end');  
         } else {  
             return &mt("Open, no due date");  
         }  
     }  
     if ($status == $res->PAST_DUE_ANSWER_LATER) {  
         return &mt("Answer open")." " . timeToHumanString($res->answerdate($part),'start');  
     }  
     if ($status == $res->PAST_DUE_NO_ANSWER) {  
         return &mt("Was due")." " . timeToHumanString($res->duedate($part),'end');  
     }  
     if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT)  
  && $res->handgrade($part) ne 'yes') {  
         return &mt("Answer available");  
     }  
     if ($status == $res->EXCUSED) {  
         return &mt("Excused by instructor");  
     }  
     if ($status == $res->ATTEMPTED) {  
         return &mt("Answer submitted, not yet graded");  
     }  
     if ($status == $res->TRIES_LEFT) {  
         my $tries = $res->tries($part);  
         my $maxtries = $res->maxtries($part);  
         my $triesString = "";  
         if ($tries && $maxtries) {  
             $triesString = "<font size=\"-1\"><i>($tries of $maxtries tries used)</i></font>";  
             if ($maxtries > 1 && $maxtries - $tries == 1) {  
                 $triesString = "<b>$triesString</b>";  
             }  
         }  
         if ($res->duedate($part)) {  
             return &mt("Due")." " . timeToHumanString($res->duedate($part),'end') .  
                 " $triesString";  
         } else {  
             return &mt("No due date")." $triesString";  
         }  
     }  
     if ($status == $res->ANSWER_SUBMITTED) {  
         return &mt('Answer submitted');  
     }  
 }  
   
 # Convenience function, so others can use it: Is the problem due in less then  
 # 24 hours, and still can be done?  
   
 sub dueInLessThan24Hours {  
     my $res = shift;  
     my $part = shift;  
     my $status = $res->status($part);  
   
     return ($status == $res->OPEN() ||  
             $status == $res->TRIES_LEFT()) &&  
     $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  
 # part, with more then one try to begin with, not due yet and still can be done?  
 sub lastTry {  
     my $res = shift;  
     my $part = shift;  
   
     my $tries = $res->tries($part);  
     my $maxtries = $res->maxtries($part);  
     return $tries && $maxtries && $maxtries > 1 &&  
         $maxtries - $tries == 1 && $res->duedate($part) &&  
         $res->duedate($part) > time();  
 }  
   
 # This puts a human-readable name on the env variable.  
   
 sub advancedUser {  
     return $env{'request.role.adv'};  
 }  
   
   
 # timeToHumanString takes a time number and converts it to a  
 # human-readable representation, meant to be used in the following  
 # manner:  
 # print "Due $timestring"  
 # print "Open $timestring"  
 # print "Answer available $timestring"  
 # Very, very, very, VERY English-only... goodness help a localizer on  
 # this func...  
   
   
 sub timeToHumanString {  
     my ($time,$type,$format) = @_;  
   
     # zero, '0' and blank are bad times  
     if (!$time) {  
         return &mt('never');  
     }  
     unless (&Apache::lonlocal::current_language()=~/^en/) {  
  return &Apache::lonlocal::locallocaltime($time);  
     }   
     my $now = time();  
   
     my @time = localtime($time);  
     my @now = localtime($now);  
   
     # Positive = future  
     my $delta = $time - $now;  
   
     my $minute = 60;  
     my $hour = 60 * $minute;  
     my $day = 24 * $hour;  
     my $week = 7 * $day;  
     my $inPast = 0;  
   
     # Logic in comments:  
     # Is it now? (extremely unlikely)  
     if ( $delta == 0 ) {  
         return "this instant";  
     }  
   
     if ($delta < 0) {  
         $inPast = 1;  
         $delta = -$delta;  
     }  
   
     if ( $delta > 0 ) {  
   
         my $tense = $inPast ? " ago" : "";  
         my $prefix = $inPast ? "" : "in ";  
           
         # Less then a minute  
         if ( $delta < $minute ) {  
             if ($delta == 1) { return "${prefix}1 second$tense"; }  
             return "$prefix$delta seconds$tense";  
         }  
   
         # Less then an hour  
         if ( $delta < $hour ) {  
             # If so, use minutes  
             my $minutes = floor($delta / 60);  
             if ($minutes == 1) { return "${prefix}1 minute$tense"; }  
             return "$prefix$minutes minutes$tense";  
         }  
           
         # Is it less then 24 hours away? If so,  
         # display hours + minutes  
         if ( $delta < $hour * 24) {  
             my $hours = floor($delta / $hour);  
             my $minutes = floor(($delta % $hour) / $minute);  
             my $hourString = "$hours hours";  
             my $minuteString = ", $minutes minutes";  
             if ($hours == 1) {  
                 $hourString = "1 hour";  
             }  
             if ($minutes == 1) {  
                 $minuteString = ", 1 minute";  
             }  
             if ($minutes == 0) {  
                 $minuteString = "";  
             }  
             return "$prefix$hourString$minuteString$tense";  
         }  
   
  # If there's a caller supplied format, use it.  
   
  if($format ne '') {  
     my $timeStr = strftime($format, localtime($time));  
     return $timeStr.&Apache::lonlocal::gettimezone();  
  }  
   
         # Less then 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));  
             $timeStr =~ s/12:00 am/00:00/;  
             $timeStr =~ s/12:00 pm/noon/;  
             return ($inPast ? "last " : "this ") .  
                 $timeStr.&Apache::lonlocal::gettimezone();  
         }  
           
  my $conjunction='on';  
  if ($type eq 'start') {  
     $conjunction='at';  
  } elsif ($type eq 'end') {  
     $conjunction='by';  
  }  
         # Is it this year?  
         if ( $time[5] == $now[5]) {  
             # Return on Month Day, HH:MM meridian  
             my $timeStr = strftime("$conjunction %A, %b %e at %I:%M %P", localtime($time));  
             $timeStr =~ s/12:00 am/00:00/;  
             $timeStr =~ s/12:00 pm/noon/;  
             return $timeStr.&Apache::lonlocal::gettimezone();  
         }  
   
         # Not this year, so show the year  
         my $timeStr = strftime("$conjunction %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.&Apache::lonlocal::gettimezone();  
     }  
 }  
   
   
 =pod  =pod
   
 =head1 NAME  =head1 NAME
   
 Apache::lonnavmap - Subroutines to handle and render the navigation  Apache::lonnavmaps - Subroutines to handle and render the navigation
     maps  
   
 =head1 SYNOPSIS  =head1 SYNOPSIS
   
   Handles navigational maps.
   
 The main handler generates the navigational listing for the course,  The main handler generates the navigational listing for the course,
 the other objects export this information in a usable fashion for  the other objects export this information in a usable fashion for
 other modules.  other modules.
   
   
   This is part of the LearningOnline Network with CAPA project
   described at http://www.lon-capa.org.
   
   
 =head1 OVERVIEW  =head1 OVERVIEW
   
 X<lonnavmaps, overview> When a user enters a course, LON-CAPA examines the  X<lonnavmaps, overview> When a user enters a course, LON-CAPA examines the
Line 751  to compute due to the amount of data tha Line 64  to compute due to the amount of data tha
 processed.  processed.
   
 Apache::lonnavmaps provides an object model for manipulating this  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   the hash. It also provides access to several auxilary functions 
 that aren't necessarily stored in the Big Hash, but are a per-  that aren't necessarily stored in the Big Hash, but are a per-
 resource sort of value, like whether there is any feedback on   resource sort of value, like whether there is any feedback on 
Line 797  Apache::lonnavmaps::render({}). Line 110  Apache::lonnavmaps::render({}).
 =head2 Overview of Columns  =head2 Overview of Columns
   
 The renderer will build an HTML table for the navmap and return  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  resource (or possibly each part). You tell the renderer how many
 columns to create and what to place in each column, optionally using  columns to create and what to place in each column, optionally using
 one or more of the prepared columns, and the renderer will assemble  one or more of the prepared columns, and the renderer will assemble
Line 928  instruct the renderer to render only a p Line 241  instruct the renderer to render only a p
 the source of the map you want to process, like  the source of the map you want to process, like
 '/res/103/jerf/navmap.course.sequence'.  '/res/103/jerf/navmap.course.sequence'.
   
   =item * B<include_top_level_map>: default: false
   
   If you need to include the top level map (meaning the course) in the
   rendered output set this to true
   
 =item * B<navmap>: default: constructs one from %env  =item * B<navmap>: default: constructs one from %env
   
 A reference to a navmap, used only if an iterator is not passed in. If  A reference to a navmap, used only if an iterator is not passed in. If
Line 1048  navmaps screen) uses this to display the Line 366  navmaps screen) uses this to display the
   
 =cut  =cut
   
   
   =head1 SUBROUTINES
   
   =over
   
   =item update()
   
   =item addToFilter()
   
   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
   
   =item removeFromFilter()
   
   =item getLinkForResource()
   
   Convenience function: Given a stack returned from getStack on the iterator,
   return the correct src() value.
   
   =item getDescription()
   
   Convenience function: This separates the logic of how to create
   the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned",
   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.
   
   =item dueInLessThan24Hours()
   
   Convenience function, so others can use it: Is the problem due in less than 24 hours, and still can be done?
   
   =item lastTry()
   
   Convenience function, so others can use it: Is there only one try remaining for the
   part, with more than one try to begin with, not due yet and still can be done?
   
   =item advancedUser()
   
   This puts a human-readable name on the env variable.
   
   =item timeToHumanString()
   
   timeToHumanString takes a time number and converts it to a
   human-readable representation, meant to be used in the following
   manner:
   
   =over 4
   
   =item * print "Due $timestring"
   
   =item * print "Open $timestring"
   
   =item * print "Answer available $timestring"
   
   =back
   
   Very, very, very, VERY English-only... goodness help a localizer on
   this func...
   
   =item resource()
   
   returns 0
   
   =item communication_status()
   
   returns 1
   
   =item quick_status()
   
   returns 2
   
   =item long_status()
   
   returns 3
   
   =item part_status_summary()
   
   returns 4
   
   =item render_resource()
   
   =item render_communication_status()
   
   =item render_quick_status()
   
   =item render_long_status()
   
   =item render_parts_summary_status()
   
   =item setDefault()
   
   =item cmp_title()
   
   =item render()
   
   =item add_linkitem()
   
   =item show_linkitems()
   
   =back
   
   =cut
   
   package Apache::lonnavmaps;
   
   use strict;
   use GDBM_File;
   use Apache::loncommon();
   use Apache::lonenc();
   use Apache::lonlocal;
   use Apache::lonnet;
   
   use POSIX qw (floor strftime);
   use Time::HiRes qw( gettimeofday tv_interval );
   use LONCAPA;
   use DateTime();
   use Data::Dumper;
   # symbolic constants
   sub SYMB { return 1; }
   sub URL { return 2; }
   sub NOTHING { return 3; }
   
   # Some data
   
   my $resObj = "Apache::lonnavmaps::resource";
   
   # Keep these mappings in sync with lonquickgrades, which usesthe colors
   # instead of the icons.
   my %statusIconMap = 
       (
        $resObj->CLOSED       => '',
        $resObj->OPEN         => 'navmap.open.gif',
        $resObj->CORRECT      => 'navmap.correct.gif',
        $resObj->PARTIALLY_CORRECT      => 'navmap.partial.gif',
        $resObj->INCORRECT    => 'navmap.wrong.gif',
        $resObj->ATTEMPTED    => 'navmap.ellipsis.gif',
        $resObj->ERROR        => ''
        );
   
   my %iconAltTags =   #texthash does not work here
       ( 'navmap.correct.gif'  => 'Correct',
         'navmap.wrong.gif'    => 'Incorrect',
         'navmap.open.gif'     => 'Is Open',
         'navmap.partial.gif'  => 'Partially Correct',
         'navmap.ellipsis.gif' => 'Attempted',
        );
   
   # Defines a status->color mapping, null string means don't color
   my %colormap = 
       ( $resObj->NETWORK_FAILURE        => '',
         $resObj->CORRECT                => '',
         $resObj->EXCUSED                => '#3333FF',
         $resObj->PAST_DUE_ANSWER_LATER  => '',
         $resObj->PAST_DUE_NO_ANSWER     => '',
         $resObj->ANSWER_OPEN            => '#006600',
         $resObj->OPEN_LATER             => '',
         $resObj->TRIES_LEFT             => '',
         $resObj->INCORRECT              => '',
         $resObj->OPEN                   => '',
         $resObj->NOTHING_SET            => '',
         $resObj->ATTEMPTED              => '',
         $resObj->CREDIT_ATTEMPTED       => '',
         $resObj->ANSWER_SUBMITTED       => '',
         $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 than 24 hours
   my $hurryUpColor = "#FF0000";
   
   my $future_slots_checked = 0;
   my $future_slots = 0;
   
   sub addToFilter {
       my $hashIn = shift;
       my $addition = shift;
       my %hash = %$hashIn;
       $hash{$addition} = 1;
   
       return join (",", keys(%hash));
   }
   
   sub removeFromFilter {
       my $hashIn = shift;
       my $subtraction = shift;
       my %hash = %$hashIn;
   
       delete $hash{$subtraction};
       return join(",", keys(%hash));
   }
   
   sub getLinkForResource {
       my $stack = shift;
       my $res;
   
       # Check to see if there are any pages in the stack
       foreach $res (@$stack) {
           if (defined($res)) {
       my $anchor;
       if ($res->is_page()) {
    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=&escape($res->shown_symb());
    return ($url,$res->shown_symb(),$anchor);
       }
           }
       }
   
       # Failing that, return the src of the last resource that is defined
       # (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 my $item (@$stack) {
           if (defined($item)) { $res = $item; }
       }
   
       if ($res) {
    return ($res->link(),$res->shown_symb());
       }
       return;
   }
   
   
   
   sub getDescription {
       my $res = shift;
       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"); 
       }
       if ($status == $res->NOTHING_SET) {
           return &Apache::lonhtmlcommon::direct_parm_link(&mt("Not currently assigned.",$res->symb(),'opendate'),$part);
       }
       if ($status == $res->OPEN_LATER) {
           return &mt("Open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($open,'start')),$res->symb(),'opendate',$part);
       }
       if ($res->simpleStatus($part) == $res->OPEN) {
           unless (&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) {
               my ($slot_status,$slot_time,$slot_name)=$res->check_for_slot($part);
               if ($slot_status == $res->UNKNOWN) {
                   return &mt('Reservation status unknown');
               } elsif ($slot_status == $res->RESERVED) {
                   return &mt('Reserved - ends [_1]',
                              timeToHumanString($slot_time,'end'));
               } elsif ($slot_status == $res->RESERVED_LOCATION) {
                   return &mt('Reserved - specific location(s) - ends [_1]',
                              timeToHumanString($slot_time,'end'));
               } elsif ($slot_status == $res->RESERVED_LATER) {
                   return &mt('Reserved - next open [_1]',
                              timeToHumanString($slot_time,'start'));
               } elsif ($slot_status == $res->RESERVABLE) {
                   return &mt('Reservable ending [_1]',
                              timeToHumanString($slot_time,'end'));
               } elsif ($slot_status == $res->RESERVABLE_LATER) {
                   return &mt('Reservable starting [_1]',
                              timeToHumanString($slot_time,'start'));
               } elsif ($slot_status == $res->NOT_IN_A_SLOT) {
                   return &mt('Reserve a time/place to work');
               } elsif ($slot_status == $res->NOTRESERVABLE) {
                   return &mt('Reservation not available');
               } elsif ($slot_status == $res->WAITING_FOR_GRADE) {
                   return &mt('Submission in grading queue');
               }
           }
       }
       if ($status == $res->OPEN) {
           if ($due) {
       if ($res->is_practice()) {
    return &mt("Closes [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start')),$res->symb(),'duedate',$part);
       } else {
    return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end')),$res->symb(),'duedate',$part);
       }
           } else {
               return &Apache::lonhtmlcommon::direct_parm_link(&mt("Open, no due date"),$res->symb(),'duedate',$part);
           }
       }
       if ($status == $res->PAST_DUE_ANSWER_LATER) {
           return &mt("Answer open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($answer,'start')),$res->symb(),'answerdate',$part);
       }
       if ($status == $res->PAST_DUE_NO_ANSWER) {
    if ($res->is_practice()) {
       return &mt("Closed [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start')),$res->symb(),'answerdate,duedate',$part);
    } else {
       return &mt("Was due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end')),$res->symb(),'answerdate,duedate',$part);
    }
       }
       if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT)
    && $res->handgrade($part) ne 'yes') {
           return &Apache::lonhtmlcommon::direct_parm_link(&mt("Answer available"),$res->symb(),'answerdate,duedate',$part);
       }
       if ($status == $res->EXCUSED) {
           return &mt("Excused by instructor");
       }
       if ($status == $res->ATTEMPTED) {
           if ($res->is_anonsurvey($part) || $res->is_survey($part)) {
               return &mt("Survey submission recorded");
           } else {
               return &mt("Answer submitted, not yet graded");
           }
       }
       if ($status == $res->CREDIT_ATTEMPTED) {
           if ($res->is_anonsurvey($part) || $res->is_survey($part)) {
               return &mt("Credit for survey submission");
           }
       }
       if ($status == $res->TRIES_LEFT) {
           my $tries = $res->tries($part);
           my $maxtries = $res->maxtries($part);
           my $triesString = "";
           if ($tries && $maxtries) {
               $triesString = '<font size="-1"><i>('.&mt('[_1] of [quant,_2,try,tries] used',$tries,$maxtries).')</i></font>';
               if ($maxtries > 1 && $maxtries - $tries == 1) {
                   $triesString = "<b>$triesString</b>";
               }
           }
           if ($due) {
               return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end')),$res->symb(),'duedate',$part) .
                   " $triesString";
           } else {
               return &Apache::lonhtmlcommon::direct_parm_link(&mt("No due date"),$res->symb(),'duedate',$part)." $triesString";
           }
       }
       if ($status == $res->ANSWER_SUBMITTED) {
           return &mt('Answer submitted');
       }
   }
   
   
   sub dueInLessThan24Hours {
       my $res = shift;
       my $part = shift;
       my $status = $res->status($part);
   
       return ($status == $res->OPEN() ||
               $status == $res->TRIES_LEFT()) &&
       $res->duedate($part) && $res->duedate($part) < time()+(24*60*60) &&
       $res->duedate($part) > time();
   }
   
   
   sub lastTry {
       my $res = shift;
       my $part = shift;
   
       my $tries = $res->tries($part);
       my $maxtries = $res->maxtries($part);
       return $tries && $maxtries && $maxtries > 1 &&
           $maxtries - $tries == 1 && $res->duedate($part) &&
           $res->duedate($part) > time();
   }
   
   
   sub advancedUser {
       return $env{'request.role.adv'};
   }
   
   sub timeToHumanString {
       my ($time,$type,$format) = @_;
   
       # zero, '0' and blank are bad times
       if (!$time) {
           return &mt('never');
       }
       unless (&Apache::lonlocal::current_language()=~/^en/) {
    return &Apache::lonlocal::locallocaltime($time);
       } 
       my $now = time();
   
       # Positive = future
       my $delta = $time - $now;
   
       my $minute = 60;
       my $hour = 60 * $minute;
       my $day = 24 * $hour;
       my $week = 7 * $day;
       my $inPast = 0;
   
       # Logic in comments:
       # Is it now? (extremely unlikely)
       if ( $delta == 0 ) {
           return "this instant";
       }
   
       if ($delta < 0) {
           $inPast = 1;
           $delta = -$delta;
       }
   
       if ( $delta > 0 ) {
   
           my $tense = $inPast ? " ago" : "";
           my $prefix = $inPast ? "" : "in ";
           
           # Less than a minute
           if ( $delta < $minute ) {
               if ($delta == 1) { return "${prefix}1 second$tense"; }
               return "$prefix$delta seconds$tense";
           }
   
           # Less than an hour
           if ( $delta < $hour ) {
               # If so, use minutes; or minutes, seconds (if format requires)
               my $minutes = floor($delta / 60);
               if (($format ne '') && ($format =~ /\%(T|S)/)) {
                   my $display;
                   if ($minutes == 1) {
                       $display = "${prefix}1 minute";
                   } else {
                       $display = "$prefix$minutes minutes";
                   }
                   my $seconds = $delta % $minute;
                   if ($seconds == 0) {
                       $display .= $tense;
                   } elsif ($seconds == 1) {
                       $display .= ", 1 second$tense";
                   } else {
                       $display .= ", $seconds seconds$tense";
                   }
                   return $display;
               }
               if ($minutes == 1) { return "${prefix}1 minute$tense"; }
               return "$prefix$minutes minutes$tense";
           }
           
           # Is it less than 24 hours away? If so,
           # display hours + minutes, (and + seconds, if format specified it)  
           if ( $delta < $hour * 24) {
               my $hours = floor($delta / $hour);
               my $minutes = floor(($delta % $hour) / $minute);
               my $hourString = "$hours hours";
               my $minuteString = ", $minutes minutes";
               if ($hours == 1) {
                   $hourString = "1 hour";
               }
               if ($minutes == 1) {
                   $minuteString = ", 1 minute";
               }
               if ($minutes == 0) {
                   $minuteString = "";
               }
               if (($format ne '') && ($format =~ /\%(T|S)/)) {
                   my $display = "$prefix$hourString$minuteString";
                   my $seconds = $delta-(($hours * $hour)+($minutes * $minute));
                   if ($seconds == 0) {
                       $display .= $tense;
                   } elsif ($seconds == 1) {
                       $display .= ", 1 second$tense";
                   } else {
                       $display .= ", $seconds seconds$tense";
                   }
                   return $display;
               }
               return "$prefix$hourString$minuteString$tense";
           }
   
           # Date/time is more than 24 hours away
   
    my $dt = DateTime->from_epoch(epoch => $time)
                    ->set_time_zone(&Apache::lonlocal::gettimezone());
   
    # If there's a caller supplied format, use it, unless it only displays
           # H:M:S or H:M.
   
    if (($format ne '') && ($format ne '%T') && ($format ne '%R')) {
       my $timeStr = $dt->strftime($format);
       return $timeStr.' ('.$dt->time_zone_short_name().')';
    }
   
           # Less than 5 days away, display day of the week and
           # HH:MM
   
           if ( $delta < $day * 5 ) {
               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;
           }
           
    my $conjunction='on';
    if ($type eq 'start') {
       $conjunction='at';
    } elsif ($type eq 'end') {
       $conjunction='by';
    }
           # Is it this year?
    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 = $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;
           }
   
           # Not this year, so show the year
           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;
       }
   }
   
   
 sub resource { return 0; }  sub resource { return 0; }
 sub communication_status { return 1; }  sub communication_status { return 1; }
 sub quick_status { return 2; }  sub quick_status { return 2; }
Line 1062  sub render_resource { Line 898  sub render_resource {
     my $link = $params->{"resourceLink"};      my $link = $params->{"resourceLink"};
   
     #  The URL part is not escaped at this point, but the symb is...       #  The URL part is not escaped at this point, but the symb is... 
     #  The stuff to the left of the ? must have ' replaced by \' since  
     #  it will be quoted with ' in the href.  
   
     my ($left,$right) = split(/\?/, $link);  
     $link = $left.'?'.$right;  
   
     my $src = $resource->src();      my $src = $resource->src();
     my $it = $params->{"iterator"};      my $it = $params->{"iterator"};
Line 1079  sub render_resource { Line 910  sub render_resource {
     my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons");      my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons");
     # If this is a new branch, label it so      # If this is a new branch, label it so
     if ($params->{'isNewBranch'}) {      if ($params->{'isNewBranch'}) {
         $newBranchText = "<img src='$location/branch.gif' border='0' alt='Branch' />";          $newBranchText = "<img src='$location/branch.gif' alt=".mt('Branch')." />";
     }      }
   
     # links to open and close the folder      # links to open and close the folder
   
           my $whitespace = $location.'/whitespace_21.gif';
     my $linkopen = "<a href=\"$link\">";      my $linkopen = "<img src='$whitespace' alt='' />"."<a href=\"$link\">";
   
   
     my $linkclose = "</a>";      my $linkclose = "</a>";
   
     # Default icon: unknown page      # Default icon: unknown page
     my $icon = "<img src='$location/unknown.gif' alt='' border='0' alt='&nbsp;&nbsp;' ' />";      my $icon = "<img class=\"LC_contentImage\" src='$location/unknown.gif' alt='' />";
           
     if ($resource->is_problem()) {      if ($resource->is_problem()) {
         if ($part eq '0' || $params->{'condensed'}) {          if ($part eq '0' || $params->{'condensed'}) {
             $icon ='<img src="'.$location.'/problem.gif" alt="&nbsp;&nbsp;" border="0" />';      $icon = '<img class="LC_contentImage" src="'.$location.'/';
       if ($resource->is_task()) {
    $icon .= 'task.gif" alt="'.&mt('Task');
       } else {
    $icon .= 'problem.gif" alt="'.&mt('Problem');
       }
       $icon .='" />';
         } else {          } else {
             $icon = $params->{'indentString'};              $icon = $params->{'indentString'};
         }          }
     } else {      } else {
  $icon = "<img src='".&Apache::loncommon::icon($resource->src)."' alt='&nbsp;&nbsp;' border='0' />";   $icon = "<img class=\"LC_contentImage\" src='".&Apache::loncommon::icon($resource->src)."' alt='' />";
     }      }
   
     # Display the correct map icon to open or shut map      # Display the correct map icon to open or shut map
Line 1110  sub render_resource { Line 945  sub render_resource {
         if ($it->{CONDITION}) {          if ($it->{CONDITION}) {
             $nowOpen = !$nowOpen;              $nowOpen = !$nowOpen;
         }          }
   
  my $folderType = $resource->is_sequence() ? 'folder' : 'page';   my $folderType = $resource->is_sequence() ? 'folder' : 'page';
           my $title=$resource->title;
    $title=~s/\"/\&qout;/g;
         if (!$params->{'resource_no_folder_link'}) {          if (!$params->{'resource_no_folder_link'}) {
             $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';              $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';
     $icon = "<img src='$location/$icon' alt='".              $icon = "<img src='$location/arrow." . ($nowOpen ? 'closed' : 'open') . ".gif' alt='' />"
  ($nowOpen ? 'Open Folder' : 'Close Folder')."' border='0' />";                      ."<img class=\"LC_contentImage\" src='$location/$icon' alt=\""
                       .($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" />";
             $linkopen = "<a href=\"" . $params->{'url'} . '?' .               $linkopen = "<a href=\"" . $params->{'url'} . '?' . 
                 $params->{'queryString'} . '&filter=';                  $params->{'queryString'} . '&amp;filter=';
             $linkopen .= ($nowOpen xor $it->{CONDITION}) ?              $linkopen .= ($nowOpen xor $it->{CONDITION}) ?
                 addToFilter($filter, $mapId) :                  addToFilter($filter, $mapId) :
                 removeFromFilter($filter, $mapId);                  removeFromFilter($filter, $mapId);
             $linkopen .= "&condition=" . $it->{CONDITION} . '&hereType='              $linkopen .= "&amp;condition=" . $it->{CONDITION} . '&amp;hereType='
                 . $params->{'hereType'} . '&here=' .                  . $params->{'hereType'} . '&amp;here=' .
                 &Apache::lonnet::escape($params->{'here'}) .                   &escape($params->{'here'}) . 
                 '&jump=' .                  '&amp;jump=' .
                 &Apache::lonnet::escape($resource->symb()) .                   &escape($resource->symb()) . 
                 "&folderManip=1\">";                  "&amp;folderManip=1\">";
   
         } else {          } else {
             # Don't allow users to manipulate folder              # Don't allow users to manipulate folder
             $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') .              $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';
                 '.nomanip.gif';              $icon = "<img class=\"LC_space\" src='$whitespace' alt='' />"."<img class=\"LC_contentImage\" src='$location/$icon' alt=\"".($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" />";
             $icon = "<img src='$location/$icon' alt='".  
  ($nowOpen ? 'Open Folder' : 'Close Folder')."' border='0' />";  
   
             $linkopen = "";              $linkopen = "";
             $linkclose = "";              $linkclose = "";
Line 1143  sub render_resource { Line 977  sub render_resource {
     }      }
   
     if ($resource->randomout()) {      if ($resource->randomout()) {
         $nonLinkedText .= ' <i>(hidden)</i> ';          $nonLinkedText .= ' <span class="LC_warning">('.&mt('hidden').')</span> ';
     }      }
     if (!$resource->condval()) {      if (!$resource->condval()) {
         $nonLinkedText .= ' <i>(conditionally hidden)</i> ';          $nonLinkedText .= ' <span class="LC_info">('.&mt('conditionally hidden').')</span> ';
       }
       if (($resource->is_practice()) && ($resource->is_raw_problem())) {
           $nonLinkedText .=' <font color="green"><b>'.&mt('not graded').'</b></font>';
     }      }
       
     # We're done preparing and finally ready to start the rendering  
     my $result = "<td align='left' valign='center'>";  
   
       # We're done preparing and finally ready to start the rendering
       my $result = '<td class="LC_middle">';
       my $newfolderType = $resource->is_sequence() ? 'folder' : 'page';
   
     my $indentLevel = $params->{'indentLevel'};      my $indentLevel = $params->{'indentLevel'};
     if ($newBranchText) { $indentLevel--; }      if ($newBranchText) { $indentLevel--; }
   
Line 1161  sub render_resource { Line 999  sub render_resource {
     }      }
   
     # Decide what to display      # Decide what to display
   
     $result .= "$newBranchText$linkopen$icon$linkclose";      $result .= "$newBranchText$linkopen$icon$linkclose";
           
     my $curMarkerBegin = '';      my $curMarkerBegin = '';
Line 1170  sub render_resource { Line 1007  sub render_resource {
     # Is this the current resource?      # Is this the current resource?
     if (!$params->{'displayedHereMarker'} &&       if (!$params->{'displayedHereMarker'} && 
         $resource->symb() eq $params->{'here'} ) {          $resource->symb() eq $params->{'here'} ) {
         $curMarkerBegin = '<font color="red" size="+2">&gt;</font>';          $curMarkerBegin = '<em>';
         $curMarkerEnd = '<font color="red" size="+2">&lt;</font>';          $curMarkerEnd = '</em>';
         $params->{'displayedHereMarker'} = 1;   $params->{'displayedHereMarker'} = 1;
     }      }
   
     if ($resource->is_problem() && $part ne '0' &&       if ($resource->is_problem() && $part ne '0' && 
         !$params->{'condensed'}) {          !$params->{'condensed'}) {
  my $displaypart=$resource->part_display($part);   my $displaypart=$resource->part_display($part);
         $partLabel = " (Part: $displaypart)";          $partLabel = " (".&mt('Part: [_1]', $displaypart).")";
  if ($link!~/\#/) { $link.='#'.&Apache::lonnet::escape($part); }   if ($link!~/\#/) { $link.='#'.&escape($part); }
         $title = "";          $title = "";
     }      }
   
     if ($params->{'condensed'} && $resource->countParts() > 1) {      if ($params->{'condensed'} && $resource->countParts() > 1) {
         $nonLinkedText .= ' (' . $resource->countParts() . ' parts)';          $nonLinkedText .= ' ('.&mt('[_1] parts', $resource->countParts()).')';
     }      }
   
     my $target;  
     if ($env{'environment.remotenavmap'} eq 'on') {  
  $target=' target="loncapaclient" ';  
     }  
     if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) {      if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) {
         $result .= "  $curMarkerBegin<a $target href=\"$link\">$title$partLabel</a>$curMarkerEnd $nonLinkedText</td>";          $result .= "$curMarkerBegin<a href=\"$link\">$title$partLabel</a>$curMarkerEnd$nonLinkedText</td>";
     } else {      } else {
         $result .= "  $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText</td>";          $result .= "$curMarkerBegin$linkopen$title$partLabel</a>$curMarkerEnd$nonLinkedText</td>";
     }      }
   
     return $result;      return $result;
Line 1205  sub render_communication_status { Line 1038  sub render_communication_status {
     my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = "";      my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = "";
   
     my $link = $params->{"resourceLink"};      my $link = $params->{"resourceLink"};
     my $target;      my $linkopen = "<a href=\"$link\">";
     if ($env{'environment.remotenavmap'} eq 'on') {  
  $target=' target="loncapaclient" ';  
     }  
     my $linkopen = "<a $target href=\"$link\">";  
     my $linkclose = "</a>";      my $linkclose = "</a>";
     my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");      my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");
     if ($resource->hasDiscussion()) {      if ($resource->hasDiscussion()) {
         $discussionHTML = $linkopen .          $discussionHTML = $linkopen .
             '<img border="0" src="'.$location.'/chat.gif" />' .              '<img alt="'.&mt('New Discussion').'" src="'.$location.'/chat.gif" title="'.&mt('New Discussion').'"/>' .
             $linkclose;              $linkclose;
     }      }
           
     if ($resource->getFeedback()) {      if ($resource->getFeedback()) {
         my $feedback = $resource->getFeedback();          my $feedback = $resource->getFeedback();
         foreach (split(/\,/, $feedback)) {          foreach my $msgid (split(/\,/, $feedback)) {
             if ($_) {              if ($msgid) {
                 $feedbackHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='                  $feedbackHTML .= '&nbsp;<a href="/adm/email?display='
                     . &Apache::lonnet::escape($_) . '">'                      . &escape($msgid) . '">'
                     . '<img src="'.$location.'/feedback.gif" '                      . '<img alt="'.&mt('New E-mail').'" src="'.$location.'/feedback.gif" title="'.&mt('New E-mail').'"/></a>';
                     . 'border="0" /></a>';  
             }              }
         }          }
     }      }
Line 1233  sub render_communication_status { Line 1061  sub render_communication_status {
     if ($resource->getErrors()) {      if ($resource->getErrors()) {
         my $errors = $resource->getErrors();          my $errors = $resource->getErrors();
         my $errorcount = 0;          my $errorcount = 0;
         foreach (split(/,/, $errors)) {          foreach my $msgid (split(/,/, $errors)) {
             last if ($errorcount>=10); # Only output 10 bombs maximum              last if ($errorcount>=10); # Only output 10 bombs maximum
             if ($_) {              if ($msgid) {
                 $errorcount++;                  $errorcount++;
                 $errorHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='                  $errorHTML .= '&nbsp;<a href="/adm/email?display='
                     . &Apache::lonnet::escape($_) . '">'                      . &escape($msgid) . '">'
                     . '<img src="'.$location.'/bomb.gif" '                      . '<img alt="'.&mt('New Error').'" src="'.$location.'/bomb.gif" title="'.&mt('New Error').'"/></a>';
                     . 'border="0" /></a>';  
             }              }
         }          }
     }      }
Line 1248  sub render_communication_status { Line 1075  sub render_communication_status {
     if ($params->{'multipart'} && $part != '0') {      if ($params->{'multipart'} && $part != '0') {
  $discussionHTML = $feedbackHTML = $errorHTML = '';   $discussionHTML = $feedbackHTML = $errorHTML = '';
     }      }
       return "<td class=\"LC_middle\">$discussionHTML$feedbackHTML$errorHTML&nbsp;</td>";
     return "<td width=\"75\" align=\"left\" valign=\"center\">$discussionHTML$feedbackHTML$errorHTML&nbsp;</td>";  
   
 }  }
 sub render_quick_status {  sub render_quick_status {
Line 1259  sub render_quick_status { Line 1085  sub render_quick_status {
         $params->{'multipart'} && $part eq "0";          $params->{'multipart'} && $part eq "0";
   
     my $link = $params->{"resourceLink"};      my $link = $params->{"resourceLink"};
     my $target;      my $linkopen = "<a href=\"$link\">";
     if ($env{'environment.remotenavmap'} eq 'on') {  
  $target=' target="loncapaclient" ';  
     }  
     my $linkopen = "<a $target href=\"$link\">";  
     my $linkclose = "</a>";      my $linkclose = "</a>";
   
    $result .= '<td class="LC_middle">';
     if ($resource->is_problem() &&      if ($resource->is_problem() &&
         !$firstDisplayed) {          !$firstDisplayed) {
   
         my $icon = $statusIconMap{$resource->simpleStatus($part)};          my $icon = $statusIconMap{$resource->simpleStatus($part)};
         my $alt = $iconAltTags{$icon};          my $alt = $iconAltTags{$icon};
         if ($icon) {          if ($icon) {
     my $location=      my $location=
  &Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon");   &Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon");
             $result .= "<td width='30' valign='center' width='50' align='right'>$linkopen<img width='25' height='25' src='$location' border='0' alt='$alt' />$linkclose</td>\n";   $result .= $linkopen.'<img src="'.$location.'" alt="'.&mt($alt).'" title="'.&mt($alt).'" />'.$linkclose;            
         } else {          } else {
             $result .= "<td width='30'>&nbsp;</td>\n";              $result .= "&nbsp;";
         }          }
     } else { # not problem, no icon      } else { # not problem, no icon
         $result .= "<td width='30'>&nbsp;</td>\n";          $result .= "&nbsp;";
     }      }
    $result .= "</td>\n";
     return $result;      return $result;
 }  }
 sub render_long_status {  sub render_long_status {
     my ($resource, $part, $params) = @_;      my ($resource, $part, $params) = @_;
     my $result = "<td align='right' valign='center'>\n";      my $result = '<td class="LC_middle LC_right">';
     my $firstDisplayed = !$params->{'condensed'} &&       my $firstDisplayed = !$params->{'condensed'} && 
         $params->{'multipart'} && $part eq "0";          $params->{'multipart'} && $part eq "0";
                                   
     my $color;      my $color;
     if ($resource->is_problem()) {      if ($resource->is_problem() || $resource->is_practice()) {
         $color = $colormap{$resource->status};          $color = $colormap{$resource->status};
                   
         if (dueInLessThan24Hours($resource, $part) ||          if (dueInLessThan24Hours($resource, $part) ||
Line 1301  sub render_long_status { Line 1123  sub render_long_status {
     }      }
           
     if ($resource->kind() eq "res" &&      if ($resource->kind() eq "res" &&
         $resource->is_problem() &&          $resource->is_raw_problem() &&
         !$firstDisplayed) {          !$firstDisplayed) {
         if ($color) {$result .= "<font color=\"$color\"><b>"; }          if ($color) {$result .= "<font color=\"$color\"><b>"; }
         $result .= getDescription($resource, $part);          $result .= getDescription($resource, $part);
         if ($color) {$result .= "</b></font>"; }          if ($color) {$result .= "</b></font>"; }
     }      }
     if ($resource->is_map() && advancedUser() && $resource->randompick()) {      if ($resource->is_map() && &advancedUser() && $resource->randompick()) {
         $result .= '(randomly select ' . $resource->randompick() .')';          $result .= &mt('(randomly select [_1])', $resource->randompick());
       }
       if ($resource->is_map() && &advancedUser() && $resource->randomorder()) {
           $result .= &mt('(randomly ordered)');
     }      }
   
     # Debugging code      # Debugging code
Line 1343  my %statusStrings = Line 1168  my %statusStrings =
      );       );
 my @statuses = ($resObj->CORRECT, $resObj->ATTEMPTED, $resObj->INCORRECT, $resObj->OPEN, $resObj->CLOSED, $resObj->ERROR);  my @statuses = ($resObj->CORRECT, $resObj->ATTEMPTED, $resObj->INCORRECT, $resObj->OPEN, $resObj->CLOSED, $resObj->ERROR);
   
 use Data::Dumper;  
 sub render_parts_summary_status {  sub render_parts_summary_status {
     my ($resource, $part, $params) = @_;      my ($resource, $part, $params) = @_;
     if (!$resource->is_problem() && !$resource->contains_problem) { return '<td></td>'; }      if (!$resource->is_problem() && !$resource->contains_problem) { return '<td></td>'; }
Line 1442  sub render { Line 1266  sub render {
     # marker      # marker
     my $filterHash = {};      my $filterHash = {};
     # Figure out what we're not displaying      # Figure out what we're not displaying
     foreach (split(/\,/, $env{"form.filter"})) {      foreach my $item (split(/\,/, $env{"form.filter"})) {
         if ($_) {          if ($item) {
             $filterHash->{$_} = "1";              $filterHash->{$item} = "1";
         }          }
     }      }
   
Line 1471  sub render { Line 1295  sub render {
         if (!defined($navmap)) {          if (!defined($navmap)) {
             $navmap = Apache::lonnavmaps::navmap->new();              $navmap = Apache::lonnavmaps::navmap->new();
     if (!defined($navmap)) {      if (!defined($navmap)) {
  # no londer in course   # no longer in course
  return '<font color="red">No course selected</font><br />   return '<span class="LC_error">'.&mt('No course selected').'</span><br />
                         <a href="/adm/roles">Select a course</a><br />';                          <a href="/adm/roles">'.&mt('Select a course').'</a><br />';
     }      }
  }   }
   
Line 1539  sub render { Line 1363  sub render {
         # Step 1: Check to see if we have a navmap          # Step 1: Check to see if we have a navmap
         if (!defined($navmap)) {          if (!defined($navmap)) {
             $navmap = Apache::lonnavmaps::navmap->new();              $navmap = Apache::lonnavmaps::navmap->new();
               if (!defined($navmap)) {
                   # no longer in course
                   return '<span class="LC_error">'.&mt('No course selected').'</span><br />
                           <a href="/adm/roles">'.&mt('Select a course').'</a><br />';
               }
         }          }
   
         # See if we're being passed a specific map          # See if we're being passed a specific map
Line 1550  sub render { Line 1379  sub render {
   
             $args->{'iterator'} = $it = $navmap->getIterator($firstResource, $finishResource, $filterHash, $condition);              $args->{'iterator'} = $it = $navmap->getIterator($firstResource, $finishResource, $filterHash, $condition);
         } else {          } 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'});
         }          }
     }      }
   
Line 1583  sub render { Line 1412  sub render {
     my $printKey = $args->{'printKey'};      my $printKey = $args->{'printKey'};
     my $printCloseAll = $args->{'printCloseAll'};      my $printCloseAll = $args->{'printCloseAll'};
     if (!defined($printCloseAll)) { $printCloseAll = 1; }      if (!defined($printCloseAll)) { $printCloseAll = 1; }
      
     # Print key?      # Print key?
     if ($printKey) {      if ($printKey) {
         $result .= '<table border="0" cellpadding="2" cellspacing="0">';          $result .= '<table border="0" cellpadding="2" cellspacing="0">';
         my $date=localtime;  
         $result.='<tr><td align="right" valign="bottom">Key:&nbsp;&nbsp;</td>';          $result.='<tr><td align="right" valign="bottom">Key:&nbsp;&nbsp;</td>';
  my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");   my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");
         if ($navmap->{LAST_CHECK}) {          if ($navmap->{LAST_CHECK}) {
             $result .=               $result .= 
                 '<img src="'.$location.'/chat.gif"> '.&mt('New discussion since').' '.                  '<img src="'.$location.'/chat.gif" alt="" /> '.&mt('New discussion since').' '.
                 strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})).                  strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})).
                 '</td><td align="center" valign="bottom">&nbsp;&nbsp;'.                  '</td><td align="center" valign="bottom">&nbsp;&nbsp;'.
                 '<img src="'.$location.'/feedback.gif"> '.&mt('New message (click to open)').'<p>'.                  '<img src="'.$location.'/feedback.gif" alt="" /> '.&mt('New message (click to open)').'<p>'.
                 '</td>';                   '</td>'; 
         } else {          } else {
             $result .= '<td align="center" valign="bottom">&nbsp;&nbsp;'.              $result .= '<td align="center" valign="bottom">&nbsp;&nbsp;'.
                 '<img src="'.$location.'/chat.gif"> '.&mt('Discussions').'</td><td align="center" valign="bottom">'.                  '<img src="'.$location.'/chat.gif" alt="" /> '.&mt('Discussions').'</td><td align="center" valign="bottom">'.
                 '&nbsp;&nbsp;<img src="'.$location.'/feedback.gif"> '.&mt('New message (click to open)').                  '&nbsp;&nbsp;<img src="'.$location.'/feedback.gif" alt="" /> '.&mt('New message (click to open)').
                 '</td>';                   '</td>'; 
         }          }
   
Line 1610  sub render { Line 1438  sub render {
     if ($printCloseAll && !$args->{'resource_no_folder_link'}) {      if ($printCloseAll && !$args->{'resource_no_folder_link'}) {
  my ($link,$text);   my ($link,$text);
         if ($condition) {          if ($condition) {
     $link='"navmaps?condition=0&filter=&'.$queryString.      $link='navmaps?condition=0&amp;filter=&amp;'.$queryString.
  '&here='.&Apache::lonnet::escape($here).'"';   '&amp;here='.&escape($here);
     $text='Close All Folders';      $text='Close all folders';
         } else {          } else {
     $link='"navmaps?condition=1&filter=&'.$queryString.      $link='navmaps?condition=1&amp;filter=&amp;'.$queryString.
  '&here='.&Apache::lonnet::escape($here).'"';   '&amp;here='.&escape($here);
     $text='Open All Folders';      $text='Open all folders';
           }
           if ($env{'form.register'}) {
               $link .= '&amp;register='.$env{'form.register'};
         }          }
  if ($args->{'caller'} eq 'navmapsdisplay') {   if ($args->{'caller'} eq 'navmapsdisplay') {
     &add_linkitem($args->{'linkitems'},'changefolder',      &add_linkitem($args->{'linkitems'},'changefolder',
   'location.href='.$link,$text);    "location.href='$link'",$text);
  } else {   } else {
     $result.='<a href='.$link.'>'.&mt($text).'</a>';      $result.= '<a href="'.$link.'">'.&mt($text).'</a>';
  }   }
         $result .= "\n";          $result .= "\n";
     }      }
Line 1638  sub render { Line 1469  sub render {
  <input type="hidden" name="navurl" value="$ENV{'QUERY_STRING'}" />   <input type="hidden" name="navurl" value="$ENV{'QUERY_STRING'}" />
  <input type="hidden" name="navtime" value="$time" />   <input type="hidden" name="navtime" value="$time" />
 END  END
           if ($env{'form.register'}) {
               $result .= '<input type="hidden" name="register" value="'.$env{'form.register'}.'" />';
           }
         if ($args->{'sort'} eq 'discussion') {           if ($args->{'sort'} eq 'discussion') { 
     my $totdisc = 0;      my $totdisc = 0;
     my $haveDisc = '';      my $haveDisc = '';
Line 1661  END Line 1495  END
   
     if ($args->{'caller'} eq 'navmapsdisplay') {      if ($args->{'caller'} eq 'navmapsdisplay') {
         $result .= '<table><tr><td>'.          $result .= '<table><tr><td>'.
                    &Apache::loncommon::help_open_menu('','Navigation Screen','Navigation_Screen','',undef,'RAT').'</td>';                     &Apache::loncommon::help_open_menu('Navigation Screen','Navigation_Screen',undef,'RAT').'</td>';
  if ($env{'environment.remotenavmap'} ne 'on') {  
     $result .= '<td>&nbsp;</td>';       $result .= '<td>&nbsp;</td>'; 
         } else {   $result.='<td class="LC_middle">'.&mt('Tools:').'</td>';
     $result .= '</tr><tr>';    $result.=&show_linkitems_toolbar($args->{'linkitems'});
         }  
  $result.=&show_linkitems($args->{'linkitems'});  
         if ($args->{'sort_html'}) {          if ($args->{'sort_html'}) {
     if ($env{'environment.remotenavmap'} ne 'on') {              $result.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>'.
  $result.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>'.                  '<td align="right">'.$args->{'sort_html'}.'</td></tr>';
     '<td align="right">'.$args->{'sort_html'}.'</td></tr>';          }
     } else {  
  $result.='</tr><tr><td align="left"><br />'.  
     $args->{'sort_html'}.'</td></tr>';  
     }  
  }  
         $result .= '</table>';          $result .= '</table>';
     } elsif ($args->{'sort_html'}) {       } elsif ($args->{'sort_html'}) { 
         $result.=$args->{'sort_html'};           $result.=$args->{'sort_html'}; 
     }      }
   
     $result .= "<br />\n";      #$result .= "<br />\n";
     if ($r) {      if ($r) {
         $r->print($result);          $r->print($result);
         $r->rflush();          $r->rflush();
         $result = "";          $result = "";
     }      }
     # End parameter setting      # End parameter setting
                   
       $result .= "<br />\n";
   
     # Data      # Data
     $result .= '<table cellspacing="0" cellpadding="3" border="0" bgcolor="#FFFFFF">' ."\n";      $result.=&Apache::loncommon::start_data_table("LC_tableOfContent");    
   
     my $res = "Apache::lonnavmaps::resource";      my $res = "Apache::lonnavmaps::resource";
     my %condenseStatuses =      my %condenseStatuses =
         ( $res->NETWORK_FAILURE    => 1,          ( $res->NETWORK_FAILURE    => 1,
           $res->NOTHING_SET        => 1,            $res->NOTHING_SET        => 1,
           $res->CORRECT            => 1 );            $res->CORRECT            => 1 );
     my @backgroundColors = ("#FFFFFF", "#F6F6F6");  
   
     # Shared variables      # Shared variables
     $args->{'counter'} = 0; # counts the rows      $args->{'counter'} = 0; # counts the rows
     $args->{'indentLevel'} = 0;      $args->{'indentLevel'} = 0;
     $args->{'isNewBranch'} = 0;      $args->{'isNewBranch'} = 0;
     $args->{'condensed'} = 0;          $args->{'condensed'} = 0;   
     my $location=  
  &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace1.gif");      my $location = &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace_21.gif");
     $args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='$location' width='25' height='1' alt='&nbsp;&nbsp;' border='0' />");      $args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='$location' alt='' />");
     $args->{'displayedHereMarker'} = 0;      $args->{'displayedHereMarker'} = 0;
   
     # If we're suppressing empty sequences, look for them here. Use DFS for speed,      # If we're suppressing empty sequences, look for them here. Use DFS for speed,
Line 1925  END Line 1753  END
     if (defined($anchor)) { $anchor='#'.$anchor; }      if (defined($anchor)) { $anchor='#'.$anchor; }
     my $srcHasQuestion = $src =~ /\?/;      my $srcHasQuestion = $src =~ /\?/;
     $args->{"resourceLink"} = $src.      $args->{"resourceLink"} = $src.
  ($srcHasQuestion?'&':'?') .   ($srcHasQuestion?'&amp;':'?') .
  'symb=' . &Apache::lonnet::escape($symb).$anchor;   'symb=' . &escape($symb).$anchor;
  }   }
         # Now, we've decided what parts to show. Loop through them and          # Now, we've decided what parts to show. Loop through them and
         # show them.          # show them.
         foreach my $part (@parts) {          foreach my $part (@parts) {
             $rownum ++;              $rownum ++;
             my $backgroundColor = $backgroundColors[$rownum % scalar(@backgroundColors)];  
                           
             $result .= "  <tr bgcolor='$backgroundColor'>\n";              $result .= &Apache::loncommon::start_data_table_row();
   
             # Set up some data about the parts that the cols might want              # Set up some data about the parts that the cols might want
             my $filter = $it->{FILTER};              my $filter = $it->{FILTER};
Line 1960  END Line 1787  END
                 }                  }
                 $result .= $colHTML . "\n";                  $result .= $colHTML . "\n";
             }              }
             $result .= "    </tr>\n";              $result .= &Apache::loncommon::end_data_table_row();
             $args->{'isNewBranch'} = 0;              $args->{'isNewBranch'} = 0;
         }          }
   
Line 1988  END Line 1815  END
     # it's quite likely this might fix other browsers, too, and       # it's quite likely this might fix other browsers, too, and 
     # certainly won't hurt anything.      # certainly won't hurt anything.
     if ($displayedJumpMarker) {      if ($displayedJumpMarker) {
         $result .= "          $result .= &Apache::lonhtmlcommon::scripttag("
 <script>  
 if (location.href.indexOf('#curloc')==-1) {  if (location.href.indexOf('#curloc')==-1) {
     setTimeout(\"location += '#curloc';\", 0)      setTimeout(\"location += '#curloc';\", 0)
 }  }
 </script>";  ");
     }      }
   
     $result .= "</table>";      $result.=&Apache::loncommon::end_data_table();
       
     if ($r) {      if ($r) {
         $r->print($result);          $r->print($result);
         $result = "";          $result = "";
Line 2013  sub add_linkitem { Line 1839  sub add_linkitem {
     $$linkitems{$name}{'text'}=&mt($text);      $$linkitems{$name}{'text'}=&mt($text);
 }  }
   
 sub show_linkitems {  sub show_linkitems_toolbar {
     my ($linkitems)=@_;      my ($linkitems,$condition)=@_;
     my @linkorder = ("blank","launchnav","closenav","firsthomework",      my @linkorder = ('firsthomework','everything','uncompleted',
      "everything","uncompleted","changefolder","clearbubbles");                       'changefolder','clearbubbles');
           my $result .='<td align="left">'."\n". 
     my $result .= (<<ENDBLOCK);                   '<span class="LC_nobreak">'."\n".
               <td align="left">                   '<ul id="LC_toolbar">';
 <script type="text/javascript">  
     function changeNavDisplay () {  
  var navchoice = document.linkitems.toplink[document.linkitems.toplink.selectedIndex].value;  
 ENDBLOCK  
     foreach my $link (@linkorder) {  
  $result.= "if (navchoice == '$link') {".  
     $linkitems->{$link}{'cmd'}."}\n";  
     }  
     $result.='}  
               </script>  
                    <form name="linkitems" method="post">  
                        <nobr><select name="toplink">'."\n";  
     foreach my $link (@linkorder) {      foreach my $link (@linkorder) {
  if (defined($linkitems->{$link})) {          my $link_id = 'LC_content_toolbar_'.$link;
     if ($linkitems->{$link}{'text'} ne '') {          if (defined($linkitems->{$link})) {
  $result .= ' <option value="'.$link.'">'.              if ($linkitems->{$link}{'text'} ne '') {
     $linkitems->{$link}{'text'}."</option>\n";                  $linkitems->{$link}{'cmd'}=~s/"/'/g;
     }                  if ($linkitems->{$link}{'cmd'}) {
  }                      if ($link eq 'changefolder') {
                           if ($condition) {
                               $link_id='LC_content_toolbar_changefolder_toggled';
                           } else {
                               $link_id='LC_content_toolbar_changefolder';
                           }
                       }
                       $result .= '<li><a href="#" '.
                                  'onclick="'.$linkitems->{$link}{'cmd'}.'" '.
                                  'id="'.$link_id.'" '.
                                  'class="LC_toolbarItem" '.
                                  'title="'.$linkitems->{$link}{'text'}.'">'.
                                  '</a></li>'."\n";
                   }
               }
           }
     }      }
     $result .= '</select>&nbsp;<input type="button" name="chgnav"      $result .= '</ul>'.
                    value="Go" onClick="javascript:changeNavDisplay()" />                 '</span></td>'."\n";
                 </nobr></form></td>'."\n";  
   
     return $result;      return $result;
 }  }
   
   
 1;  1;
   
   
   
   
   
   
   
   
   
 package Apache::lonnavmaps::navmap;  package Apache::lonnavmaps::navmap;
   
 =pod  =pod
Line 2066  In order of increasing complexity and po Line 1902  In order of increasing complexity and po
   
 =over 4  =over 4
   
 =item * C<$navmap-E<gt>getByX>, where X is B<Id>, B<Symb>, B<Url> or B<MapPc>. This provides  =item * C<$navmap-E<gt>getByX>, where X is B<Id>, B<Symb> or B<MapPc> and getResourceByUrl. This provides
     various ways to obtain resource objects, based on various identifiers.      various ways to obtain resource objects, based on various identifiers.
     Use this when you want to request information about one object or       Use this when you want to request information about one object or 
     a handful of resources you already know the identities of, from some      a handful of resources you already know the identities of, from some
     other source. For more about Ids, Symbs, and MapPcs, see the      other source. For more about Ids, Symbs, and MapPcs, see the
     Resource documentation. Note that Url should be a B<last resort>,      Resource documentation. Note that Url should be a B<last resort>,
     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      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<getResourceByUrl>
       documentation for more details.)
   
 =item * C<my @resources = $navmap-E<gt>retrieveResources(args)>. This  =item * C<my @resources = $navmap-E<gt>retrieveResources(args)>. This
     retrieves resources matching some criterion and returns them      retrieves resources matching some criterion and returns them
Line 2117  See iterator documentation below. Line 1954  See iterator documentation below.
 use strict;  use strict;
 use GDBM_File;  use GDBM_File;
 use Apache::lonnet;  use Apache::lonnet;
   use LONCAPA;
   
 sub new {  sub new {
     # magic invocation to create a class instance      # magic invocation to create a class instance
Line 2124  sub new { Line 1962  sub new {
     my $class = ref($proto) || $proto;      my $class = ref($proto) || $proto;
     my $self = {};      my $self = {};
   
       $self->{USERNAME} = shift || $env{'user.name'};
       $self->{DOMAIN}   = shift || $env{'user.domain'};
   
     # Resource cache stores navmap resources as we reference them. We generate      # Resource cache stores navmap resources as we reference them. We generate
     # them on-demand so we don't pay for creating resources unless we use them.      # them on-demand so we don't pay for creating resources unless we use them.
     $self->{RESOURCE_CACHE} = {};      $self->{RESOURCE_CACHE} = {};
Line 2162  sub generate_course_user_opt { Line 2003  sub generate_course_user_opt {
     my $self = shift;      my $self = shift;
     if ($self->{COURSE_USER_OPT_GENERATED}) { return; }      if ($self->{COURSE_USER_OPT_GENERATED}) { return; }
   
     my $uname=$env{'user.name'};      my $uname=$self->{USERNAME};
     my $udom=$env{'user.domain'};      my $udom=$self->{DOMAIN};
   
     my $cid=$env{'request.course.id'};      my $cid=$env{'request.course.id'};
     my $cdom=$env{'course.'.$cid.'.domain'};      my $cdom=$env{'course.'.$cid.'.domain'};
     my $cnum=$env{'course.'.$cid.'.num'};      my $cnum=$env{'course.'.$cid.'.num'};
Line 2206  sub generate_email_discuss_status { Line 2048  sub generate_email_discuss_status {
     my $cdom=$env{'course.'.$cid.'.domain'};      my $cdom=$env{'course.'.$cid.'.domain'};
     my $cnum=$env{'course.'.$cid.'.num'};      my $cnum=$env{'course.'.$cid.'.num'};
           
     my %emailstatus = &Apache::lonnet::dump('email_status');      my %emailstatus = &Apache::lonnet::dump('email_status',$self->{DOMAIN},$self->{USERNAME});
     my $logoutTime = $emailstatus{'logout'};      my $logoutTime = $emailstatus{'logout'};
     my $courseLeaveTime = $emailstatus{'logout_'.$env{'request.course.id'}};      my $courseLeaveTime = $emailstatus{'logout_'.$env{'request.course.id'}};
     $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?      $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?
Line 2214  sub generate_email_discuss_status { Line 2056  sub generate_email_discuss_status {
     my %discussiontime = &Apache::lonnet::dump('discussiontimes',       my %discussiontime = &Apache::lonnet::dump('discussiontimes', 
        $cdom, $cnum);         $cdom, $cnum);
     my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',      my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',
                                         $env{'user.domain'},$env{'user.name'},'lastread');                                          $self->{DOMAIN},$self->{USERNAME},'lastread');
     my %lastreadtime = ();      my %lastreadtime = ();
     foreach (keys %lastread) {      foreach my $key (keys %lastread) {
         my $key = $_;          my $shortkey = $key;
         $key =~ s/_lastread$//;          $shortkey =~ s/_lastread$//;
         $lastreadtime{$key} = $lastread{$_};          $lastreadtime{$shortkey} = $lastread{$key};
     }      }
   
     my %feedback=();      my %feedback=();
     my %error=();      my %error=();
     my @keys = &Apache::lonnet::getkeys('nohist_email',$env{'user.domain'},      my @keys = &Apache::lonnet::getkeys('nohist_email',$self->{DOMAIN},
  $env{'user.name'});   $self->{USERNAME});
           
     foreach my $msgid (@keys) {      foreach my $msgid (@keys) {
  if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {   if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {
     my $plain=              my ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$fromcid,
  &Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid));                  $symb,$error) = &Apache::lonmsg::unpackmsgid($msgid);
     if ($plain=~/ \[([^\]]+)\]\:/) {              &Apache::lonenc::check_decrypt(\$symb); 
  my $url=$1;              if (($fromcid ne '') && ($fromcid ne $cid)) {
  if ($plain=~/\:Error \[/) {                  next;
     $error{$url}.=','.$msgid;               }
  } else {              if (defined($symb)) {
     $feedback{$url}.=','.$msgid;                  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;      $self->{FEEDBACK} = \%feedback;
     #or errors      #or errors (will be urls pre 2.3)
     $self->{ERROR_MSG} = \%error;      $self->{ERROR_MSG} = \%error;
     $self->{DISCUSSION_TIME} = \%discussiontime;      $self->{DISCUSSION_TIME} = \%discussiontime;
     $self->{EMAIL_STATUS} = \%emailstatus;      $self->{EMAIL_STATUS} = \%emailstatus;
Line 2259  sub get_user_data { Line 2115  sub get_user_data {
   
     # Retrieve performance data on problems      # Retrieve performance data on problems
     my %student_data = Apache::lonnet::currentdump($env{'request.course.id'},      my %student_data = Apache::lonnet::currentdump($env{'request.course.id'},
    $env{'user.domain'},     $self->{DOMAIN},
    $env{'user.name'});     $self->{USERNAME});
     $self->{STUDENT_DATA} = \%student_data;      $self->{STUDENT_DATA} = \%student_data;
   
     $self->{RETRIEVED_USER_DATA} = 1;      $self->{RETRIEVED_USER_DATA} = 1;
Line 2269  sub get_user_data { Line 2125  sub get_user_data {
 sub get_discussion_data {  sub get_discussion_data {
     my $self = shift;      my $self = shift;
     if ($self->{RETRIEVED_DISCUSSION_DATA}) {      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 $cid=$env{'request.course.id'};
     my $cdom=$env{'course.'.$cid.'.domain'};      my $cdom=$env{'course.'.$cid.'.domain'};
     my $cnum=$env{'course.'.$cid.'.num'};      my $cnum=$env{'course.'.$cid.'.num'};
                                                                                   
     # Retrieve discussion data for resources in course      # 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->{DISCUSSION_DATA} = \%discussion_data;
     $self->{RETRIEVED_DISCUSSION_DATA} = 1;      $self->{RETRIEVED_DISCUSSION_DATA} = 1;
     return $self->{DISCUSSION_DATA};      return $self->{DISCUSSION_DATA};
Line 2340  sub hasDiscussion { Line 2198  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 {  sub wrap_symb {
     my $self = shift;      my $self = shift;
     my $symb = shift;      my $symb = shift;
     if ($symb =~ m-___(adm/\w+/\w+/)(\d+)(/bulletinboard)$-) {      if ($symb =~ m-___(adm/[^/]+/[^/]+/)(\d+)(/bulletinboard)$-) {
         unless ($symb =~ m|adm/wrapper/adm|) {          unless ($symb =~ m|adm/wrapper/adm|) {
             $symb = 'bulletin___'.$2.'___adm/wrapper/'.$1.$2.$3;              $symb = 'bulletin___'.$2.'___adm/wrapper/'.$1.$2.$3;
         }          }
Line 2351  sub wrap_symb { Line 2270  sub wrap_symb {
     return $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  # Private method: Does the given resource (as a symb string) have
 # current feedback? Returns the string in the feedback hash, which  # current feedback? Returns the string in the feedback hash, which
 # will be false if it does not exist.  # will be false if it does not exist.
Line 2358  sub wrap_symb { Line 2287  sub wrap_symb {
 sub getFeedback {   sub getFeedback { 
     my $self = shift;      my $self = shift;
     my $symb = shift;      my $symb = shift;
       my $source = shift;
   
     $self->generate_email_discuss_status();      $self->generate_email_discuss_status();
   
     if (!defined($self->{FEEDBACK})) { return ""; }      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).  # Private method: Get the errors for that resource (by source).
 sub getErrors {   sub getErrors { 
     my $self = shift;      my $self = shift;
       my $symb = shift;
     my $src = shift;      my $src = shift;
   
     $self->generate_email_discuss_status();      $self->generate_email_discuss_status();
   
     if (!defined($self->{ERROR_MSG})) { return ""; }      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  =pod
Line 2391  resource object. Line 2345  resource object.
 Based on the symb of the resource, get a resource object for that  Based on the symb of the resource, get a resource object for that
 resource. This is one of the proper ways to get a resource object.  resource. This is one of the proper ways to get a resource object.
   
 =item * B<getMapByMapPc>(map_pc):  =item * B<getByMapPc>(map_pc):
   
 Based on the map_pc of the resource, get a resource object for  Based on the map_pc of the resource, get a resource object for
 the given map. This is one of the proper ways to get a resource object.  the given map. This is one of the proper ways to get a resource object.
Line 2400  the given map. This is one of the proper Line 2354  the given map. This is one of the proper
   
 # The strategy here is to cache the resource objects, and only construct them  # 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  # 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 {  sub getById {
     my $self = shift;      my $self = shift;
Line 2474  sub finishResource { Line 2428  sub finishResource {
 # the actual lookup; parmval caches the results.  # the actual lookup; parmval caches the results.
 sub parmval {  sub parmval {
     my $self = shift;      my $self = shift;
     my ($what,$symb)=@_;      my ($what,$symb,$recurse)=@_;
     my $hashkey = $what."|||".$symb;      my $hashkey = $what."|||".$symb;
       my $cache = $self->{PARM_CACHE};
     if (defined($self->{PARM_CACHE}->{$hashkey})) {      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, $recurse);
     my $result = $self->parmval_real($what, $symb);  
     $self->{PARM_CACHE}->{$hashkey} = $result;      $self->{PARM_CACHE}->{$hashkey} = $result;
     return $result;      if (wantarray) {
           return @{$result};
       }
       return $result->[0];
 }  }
   
 sub parmval_real {  sub parmval_real {
Line 2501  sub parmval_real { Line 2467  sub parmval_real {
         @cgrps = sort(@cgrps);          @cgrps = sort(@cgrps);
         $cgroup = $cgrps[0];          $cgroup = $cgrps[0];
     }       } 
     my $uname=$env{'user.name'};      my $uname=$self->{USERNAME};
     my $udom=$env{'user.domain'};      my $udom=$self->{DOMAIN};
   
     unless ($symb) { return ''; }      unless ($symb) { return ['']; }
     my $result='';      my $result='';
   
     my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);      my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
       $mapname = &Apache::lonnet::deversion($mapname);
 # ----------------------------------------------------- Cascading lookup scheme  # ----------------------------------------------------- Cascading lookup scheme
     my $rwhat=$what;      my $rwhat=$what;
     $what=~s/^parameter\_//;      $what=~s/^parameter\_//;
Line 2536  sub parmval_real { Line 2502  sub parmval_real {
   
 # ---------------------------------------------------------- first, check user  # ---------------------------------------------------------- first, check user
     if ($uname and defined($useropt)) {      if ($uname and defined($useropt)) {
         if (defined($$useropt{$courselevelr})) { return $$useropt{$courselevelr}; }          if (defined($$useropt{$courselevelr})) { return [$$useropt{$courselevelr},'resource']; }
         if (defined($$useropt{$courselevelm})) { return $$useropt{$courselevelm}; }          if (defined($$useropt{$courselevelm})) { return [$$useropt{$courselevelm},'map']; }
         if (defined($$useropt{$courselevel})) { return $$useropt{$courselevel}; }          if (defined($$useropt{$courselevel})) { return [$$useropt{$courselevel},'course']; }
     }      }
   
 # ------------------------------------------------------- second, check course  # ------------------------------------------------------- second, check course
     if ($cgroup ne '' and defined($courseopt)) {      if ($cgroup ne '' and defined($courseopt)) {
         if (defined($$courseopt{$grplevelr})) { return $$courseopt{$grplevelr}; }          if (defined($$courseopt{$grplevelr})) { return [$$courseopt{$grplevelr},'resource']; }
         if (defined($$courseopt{$grplevelm})) { return $$courseopt{$grplevelm}; }          if (defined($$courseopt{$grplevelm})) { return [$$courseopt{$grplevelm},'map']; }
         if (defined($$courseopt{$grplevel})) { return $$courseopt{$grplevel}; }          if (defined($$courseopt{$grplevel})) { return [$$courseopt{$grplevel},'course']; }
     }      }
   
     if ($csec and defined($courseopt)) {      if ($csec and defined($courseopt)) {
         if (defined($$courseopt{$seclevelr})) { return $$courseopt{$seclevelr}; }          if (defined($$courseopt{$seclevelr})) { return [$$courseopt{$seclevelr},'resource']; }
         if (defined($$courseopt{$seclevelm})) { return $$courseopt{$seclevelm}; }          if (defined($$courseopt{$seclevelm})) { return [$$courseopt{$seclevelm},'map']; }
         if (defined($$courseopt{$seclevel})) { return $$courseopt{$seclevel}; }          if (defined($$courseopt{$seclevel})) { return [$$courseopt{$seclevel},'course']; }
     }      }
   
     if (defined($courseopt)) {      if (defined($courseopt)) {
         if (defined($$courseopt{$courselevelr})) { return $$courseopt{$courselevelr}; }          if (defined($$courseopt{$courselevelr})) { return [$$courseopt{$courselevelr},'resource']; }
     }      }
   
 # ----------------------------------------------------- third, check map parms  # ----------------------------------------------------- third, check map parms
   
     my $thisparm=$$parmhash{$symbparm};      my $thisparm=$$parmhash{$symbparm};
     if (defined($thisparm)) { return $thisparm; }      if (defined($thisparm)) { return [$thisparm,'map']; }
   
 # ----------------------------------------------------- fourth , check default  # ----------------------------------------------------- fourth , check default
   
     my $meta_rwhat=$rwhat;      my $meta_rwhat=$rwhat;
     $meta_rwhat=~s/\./_/g;      $meta_rwhat=~s/\./_/g;
     my $default=&Apache::lonnet::metadata($fn,$meta_rwhat);      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);      $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat);
     if (defined($default)) { return $default}      if (defined($default)) { return [$default,'resource']}
   
 # --------------------------------------------------- fifth, check more course  # --------------------------------------------------- fifth, check more course
     if (defined($courseopt)) {      if (defined($courseopt)) {
         if (defined($$courseopt{$courselevelm})) { return $$courseopt{$courselevelm}; }          if (defined($$courseopt{$courselevelm})) { return [$$courseopt{$courselevelm},'map']; }
         if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; }          if (defined($$courseopt{$courselevel})) {
              my $ret = [$$courseopt{$courselevel},'course'];
              return $ret;
          }
     }      }
   
 # --------------------------------------------------- sixth , cascade up parts  # --------------------------------------------------- sixth , cascade up parts
   
     my ($space,@qualifier)=split(/\./,$rwhat);      my ($space,@qualifier)=split(/\./,$rwhat);
Line 2587  sub parmval_real { Line 2554  sub parmval_real {
  my $id=pop(@parts);   my $id=pop(@parts);
  my $part=join('_',@parts);   my $part=join('_',@parts);
  if ($part eq '') { $part='0'; }   if ($part eq '') { $part='0'; }
  my $partgeneral=$self->parmval($part.".$qualifier",$symb,1);         my @partgeneral=$self->parmval($part.".$qualifier",$symb,1);
  if (defined($partgeneral)) { return $partgeneral; }         if (defined($partgeneral[0])) { return \@partgeneral; }
     }      }
     if ($recurse) { return undef; }      if ($recurse) { return []; }
     my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$what);      my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$rwhat);
     if (defined($pack_def)) { return $pack_def; }      if (defined($pack_def)) { return [$pack_def,'resource']; }
     return '';      return [''];
 }  }
   
 =pod  =pod
Line 2601  sub parmval_real { Line 2568  sub parmval_real {
 =item * B<getResourceByUrl>(url,multiple):  =item * B<getResourceByUrl>(url,multiple):
   
 Retrieves a resource object by URL of the resource, unless the optional  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    objects is returned. If passed a resource object, it will simply return  
 it, so it is safe to use this method in code like  it, so it is safe to use this method in code like
 "$res = $navmap->getResourceByUrl($res)"  "$res = $navmap->getResourceByUrl($res)"
Line 2636  all matching resources. Line 2603  all matching resources.
   
 =item * B<hasResource>(map, filterFunc, recursive, showall):  =item * B<hasResource>(map, filterFunc, recursive, showall):
   
 Convience method for  Convenience method for
   
  scalar(retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0   scalar(retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0
   
Line 2648  in the filter function. Line 2615  in the filter function.
 Retrieves version infomation for a url. Returns the version (a number, or   Retrieves version infomation for a url. Returns the version (a number, or 
 the string "mostrecent") for resources which have version information in    the string "mostrecent") for resources which have version information in  
 the big hash.  the big hash.
       
 =cut  =cut
   
   
Line 2714  sub retrieveResources { Line 2681  sub retrieveResources {
   
     my @resources = ();      my @resources = ();
   
       if (&$filterFunc($map)) {
    push(@resources, $map);
       }
   
     # Run down the iterator and collect the resources.      # Run down the iterator and collect the resources.
     my $curRes;      my $curRes;
   
Line 2723  sub retrieveResources { Line 2694  sub retrieveResources {
                 next;                  next;
             }              }
   
             push @resources, $curRes;              push(@resources, $curRes);
   
             if ($bailout) {              if ($bailout) {
                 return @resources;                  return @resources;
Line 2754  sub usedVersion { Line 2725  sub usedVersion {
 1;  1;
   
 package Apache::lonnavmaps::iterator;  package Apache::lonnavmaps::iterator;
 use WeakRef;  use Scalar::Util qw(weaken);
 use Apache::lonnet;  use Apache::lonnet;
   
 =pod  =pod
Line 2870  be the tokens described above. Line 2841  be the tokens described above.
   
 Also note there is some old code floating around that trys to track  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   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.  this. They should be migrated to this new style.
   
 =cut  =cut
Line 2899  sub new { Line 2870  sub new {
     weaken($self->{NAV_MAP} = shift);      weaken($self->{NAV_MAP} = shift);
     return undef unless ($self->{NAV_MAP});      return undef unless ($self->{NAV_MAP});
   
       $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME};
       $self->{DOMAIN}   = $self->{NAV_MAP}->{DOMAIN};
   
     # Handle the parameters      # Handle the parameters
     $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();      $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();
     $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();      $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();
Line 3054  sub next { Line 3028  sub next {
         $self->{HAVE_RETURNED_0} = 1;          $self->{HAVE_RETURNED_0} = 1;
         return $self->{NAV_MAP}->getById('0.0');          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}) {      if ($self->{RECURSIVE_ITERATOR_FLAG}) {
         # grab the next from the recursive iterator           # grab the next from the recursive iterator 
Line 3155  sub next { Line 3133  sub next {
     }      }
   
     # Is this the end of a branch? If so, all of the resources examined above      # 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.      # marker onto the stack so we don't forget.
     # Example: For the usual A(BC)(DE)F case, when the iterator goes down the      # 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      # BC branch and gets to C, it will see F as the only next resource, but it's
Line 3235  sub populateStack { Line 3213  sub populateStack {
 1;  1;
   
 package Apache::lonnavmaps::DFSiterator;  package Apache::lonnavmaps::DFSiterator;
 use WeakRef;  use Scalar::Util qw(weaken);
 use Apache::lonnet;  use Apache::lonnet;
   
 # Not documented in the perldoc: This is a simple iterator that just walks  # Not documented in the perldoc: This is a simple iterator that just walks
Line 3268  sub new { Line 3246  sub new {
     weaken($self->{NAV_MAP} = shift);      weaken($self->{NAV_MAP} = shift);
     return undef unless ($self->{NAV_MAP});      return undef unless ($self->{NAV_MAP});
   
       $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME};
       $self->{DOMAIN}   = $self->{NAV_MAP}->{DOMAIN};
   
     $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();      $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();
     $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();      $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();
   
Line 3364  sub next { Line 3345  sub next {
   
     # filter the next possibilities to remove things we've       # filter the next possibilities to remove things we've 
     # already seen.      # already seen.
     foreach (@$nextUnfiltered) {      foreach my $item (@$nextUnfiltered) {
         if (!defined($self->{ALREADY_SEEN}->{$_->{ID}})) {          if (!defined($self->{ALREADY_SEEN}->{$item->{ID}})) {
             push @$next, $_;              push @$next, $item;
         }          }
     }      }
   
Line 3419  sub populateStack { Line 3400  sub populateStack {
 1;  1;
   
 package Apache::lonnavmaps::resource;  package Apache::lonnavmaps::resource;
 use WeakRef;  use Scalar::Util qw(weaken);
 use Apache::lonnet;  use Apache::lonnet;
   
 =pod  =pod
Line 3491  X<symb> X<resource, symb> Line 3472  X<symb> X<resource, symb>
 All resources also have B<symb>s, which uniquely identify a resource  All resources also have B<symb>s, which uniquely identify a resource
 in a course. Many internal LON-CAPA functions expect a symb. A symb  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  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  =cut
   
Line 3504  sub new { Line 3485  sub new {
     weaken($self->{NAV_MAP} = shift);      weaken($self->{NAV_MAP} = shift);
     $self->{ID} = shift;      $self->{ID} = shift;
   
       $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME};
       $self->{DOMAIN}   = $self->{NAV_MAP}->{DOMAIN};
   
     # Store this new resource in the parent nav map's cache.      # Store this new resource in the parent nav map's cache.
     $self->{NAV_MAP}->{RESOURCE_CACHE}->{$self->{ID}} = $self;      $self->{NAV_MAP}->{RESOURCE_CACHE}->{$self->{ID}} = $self;
     $self->{RESOURCE_ERROR} = 0;      $self->{RESOURCE_ERROR} = 0;
Line 3525  sub navHash { Line 3509  sub navHash {
     my $self = shift;      my $self = shift;
     my $param = shift;      my $param = shift;
     my $id = shift;      my $id = shift;
     return $self->{NAV_MAP}->navhash($param . ($id?$self->{ID}:""));      my $arg = $param . ($id?$self->{ID}:"");
       if (ref($self) && ref($self->{NAV_MAP}) && defined($arg)) {
           return $self->{NAV_MAP}->navhash($arg);
       }
       return;
 }  }
   
 =pod  =pod
Line 3567  false. Line 3555  false.
   
 =item * B<randompick>:  =item * B<randompick>:
   
 Returns true for a map if the randompick feature is being used on the  Returns the number of randomly picked items for a map if the randompick
 map. (?)  feature is being used on the map. 
   
   =item * B<randomorder>:
   
   Returns true for a map if the randomorder feature is being used on the
   map.
   
 =item * B<src>:  =item * B<src>:
   
Line 3598  sub kind { my $self=shift; return $self- Line 3591  sub kind { my $self=shift; return $self-
 sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }  sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }
 sub randompick {   sub randompick { 
     my $self = shift;      my $self = shift;
     return $self->{NAV_MAP}->{PARM_HASH}->{$self->symb .      my $randompick = $self->parmval('randompick');
                                                '.0.parameter_randompick'};      return $randompick;
   }
   sub randomorder { 
       my $self = shift;
       my $randomorder = $self->parmval('randomorder');
       return ($randomorder =~ /^yes$/i);
 }  }
 sub link {  sub link {
     my $self=shift;      my $self=shift;
Line 3657  sub condition { Line 3655  sub condition {
 }  }
 sub condval {  sub condval {
     my $self=shift;      my $self=shift;
     my $uri=&Apache::lonnet::deversion(&Apache::lonnet::declutter($self->src()));      my ($pathname,$filename) = 
     my ($pathname,$filename)=($uri=~m|(.*)/([^/]*)|);   &Apache::lonnet::split_uri_for_cond($self->src());
     $pathname=~s/^adm\/wrapper\///;      
   
     my $match=($env{'acc.res.'.$env{'request.course.id'}.'.'.$pathname}=~      my $match=($env{'acc.res.'.$env{'request.course.id'}.'.'.$pathname}=~
        /\&\Q$filename\E\:([\d\|]+)\&/);         /\&\Q$filename\E\:([\d\|]+)\&/);
Line 3678  sub compTitle { Line 3675  sub compTitle {
     }      }
     return $title;      return $title;
 }  }
   
 =pod  =pod
   
 B<Predicate Testing the Resource>  B<Predicate Testing the Resource>
Line 3718  sub retrieveResources { Line 3716  sub retrieveResources {
    return $self->{NAV_MAP}->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 {  sub is_html {
     my $self=shift;      my $self=shift;
     my $src = $self->src();      my $src = $self->src();
Line 3730  sub is_page { Line 3739  sub is_page {
     return $self->navHash("is_map_", 1) &&       return $self->navHash("is_map_", 1) && 
  $self->navHash("map_type_" . $self->map_pc()) eq 'page';   $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 {  sub is_problem {
     my $self=shift;      my $self=shift;
     my $src = $self->src();      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;
   }
   #
   #  The has below is the set of status that are considered 'incomplete'
   #
   my %incomplete_hash = 
   (
    TRIES_LEFT()     => 1,
    OPEN()           => 1,
    ATTEMPTED()      => 1
   
    );
   #
   #  Return tru if a problem is incomplete... for now incomplete means that
   #  any part of the problem is incomplete. 
   #  Note that if the resources is not a problem, 0 is returned.
   #
   sub is_incomplete {
       my $self = shift;
       if ($self->is_problem()) {
    foreach my $part (@{$self->parts()}) {
       if (exists($incomplete_hash{$self->status($part)})) {
    return 1;
       }
    }
       }
       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 {  sub contains_problem {
     my $self=shift;      my $self=shift;
     if ($self->is_page()) {      if ($self->is_page()) {
Line 3743  sub contains_problem { Line 3800  sub contains_problem {
     }      }
     return 0;      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 {  sub is_sequence {
     my $self=shift;      my $self=shift;
     my $src = $self->src();  
     return $self->navHash("is_map_", 1) &&       return $self->navHash("is_map_", 1) && 
  $self->navHash("map_type_" . $self->map_pc()) eq 'sequence';      $self->navHash("map_type_" . $self->map_pc()) eq 'sequence';
 }  }
 sub is_survey {  sub is_survey {
     my $self = shift();      my $self = shift();
     my $part = shift();      my $part = shift();
     if ($self->parmval('type',$part) eq 'survey') {      my $type = $self->parmval('type',$part);
       if (($type eq 'survey') || ($type eq 'surveycred')) {
         return 1;          return 1;
     }      }
     if ($self->src() =~ /\.(survey)$/) {      if ($self->src() =~ /\.(survey)$/) {
Line 3760  sub is_survey { Line 3826  sub is_survey {
     }      }
     return 0;      return 0;
 }  }
   sub is_anonsurvey {
       my $self = shift();
       my $part = shift();
       my $type = $self->parmval('type',$part);
       if (($type eq 'anonsurvey') || ($type eq 'anonsurveycred')) {
           return 1;
       }
       return 0;
   }
   sub is_task {
       my $self=shift;
       my $src = $self->src();
       return ($src =~ /\.(task)$/)
   }
   
 sub is_empty_sequence {  sub is_empty_sequence {
     my $self=shift;      my $self=shift;
Line 3808  resource of the map. Line 3888  resource of the map.
   
 Returns a string with the type of the map in it.  Returns a string with the type of the map in it.
   
   =item *B<map_hierarchy>:
   
   Returns a string with a comma-separated ordered list of map_pc IDs
   for the hierarchy of maps containing a map, with the top level
   map first, then descending to deeper levels, with the enclosing map last.
   
 =back  =back
   
 =cut  =cut
Line 3815  Returns a string with the type of the ma Line 3901  Returns a string with the type of the ma
 sub map_finish {  sub map_finish {
     my $self = shift;      my $self = shift;
     my $src = $self->src();      my $src = $self->src();
     $src = Apache::lonnet::clutter($src);      $src = &Apache::lonnet::clutter($src);
     my $res = $self->navHash("map_finish_$src", 0);      my $res = $self->navHash("map_finish_$src", 0);
     $res = $self->{NAV_MAP}->getById($res);      $res = $self->{NAV_MAP}->getById($res);
     return $res;      return $res;
Line 3828  sub map_pc { Line 3914  sub map_pc {
 sub map_start {  sub map_start {
     my $self = shift;      my $self = shift;
     my $src = $self->src();      my $src = $self->src();
     $src = Apache::lonnet::clutter($src);      $src = &Apache::lonnet::clutter($src);
     my $res = $self->navHash("map_start_$src", 0);      my $res = $self->navHash("map_start_$src", 0);
     $res = $self->{NAV_MAP}->getById($res);      $res = $self->{NAV_MAP}->getById($res);
     return $res;      return $res;
Line 3838  sub map_type { Line 3924  sub map_type {
     my $pc = $self->map_pc();      my $pc = $self->map_pc();
     return $self->navHash("map_type_$pc", 0);      return $self->navHash("map_type_$pc", 0);
 }  }
   sub map_hierarchy {
       my $self = shift;
       my $pc = $self->map_pc();
       return $self->navHash("map_hierarchy_$pc", 0);
   }
   
 #####  #####
 # Property queries  # Property queries
Line 3845  sub map_type { Line 3936  sub map_type {
   
 # These functions will be responsible for returning the CORRECT  # These functions will be responsible for returning the CORRECT
 # VALUE for the parameter, no matter what. So while they may look  # 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"  # 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  =pod
   
Line 3868  their code.) Line 3959  their code.)
   
 =over 4  =over 4
   
 =item * B<acc>:  =item * B<printable>
   
   returns true if the current date is such that the 
   specified resource part is printable.
   
   =item * B<resprintable>
   
   Returns true if all parts in the resource are printable making the
   entire resource printable.
   
   =item * B<acc>
   
 Get the Client IP/Name Access Control information.  Get the Client IP/Name Access Control information.
   
Line 3921  Get the weight for the problem. Line 4022  Get the weight for the problem.
   
 =cut  =cut
   
   sub printable {
   
       my ($self, $part) = @_;
   
       # Get the print open/close dates for the resource.
   
       my $start = $self->parmval("prinstartdate", $part);
       my $end   = $self->parmval("printenddate", $part);
   
       #  The following cases apply:
       #  - No dates set: Printable.
       #  - Start date set but no end date: Printable if now >= start date.
       #  - End date set but no start date: Printable if now <= end date.
       #  - both defined: printable if start <= now <= end
       #
       my $now  = time();
   
       my $startok = 1;
       my $endok   = 1;
   
       if ((defined $start) && ($start ne '')) {
    $startok = $start <= $now;
       }
       if ((defined $end) && ($end != '')) {
    $endok = $end >= $now;
       }
       return $startok && $endok;
   }
   
   sub resprintable {
       my $self = shift;
   
       # get parts...or realize there are no parts.
   
       my $partsref = $self->parts();
       my @parts    = @$partsref;
   
       if ((!defined(@parts)) || (scalar(@parts) == 0)) {
    return $self->printable(0);
       } else {
    foreach my $part  (@parts) {
       if (!$self->printable($part)) { 
    return 0; 
       }
    }
    return 1;
       }
   }
   
 sub acc {  sub acc {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("acc", $part);      my $acc = $self->parmval("acc", $part);
       return $acc;
 }  }
 sub answerdate {  sub answerdate {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     # Handle intervals      # Handle intervals
     if ($self->parmval("answerdate.type", $part) eq 'date_interval') {      my $answerdatetype = $self->parmval("answerdate.type", $part);
         return $self->duedate($part) +       my $answerdate = $self->parmval("answerdate", $part);
             $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 {   sub awarded { 
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
Line 3940  sub awarded { Line 4093  sub awarded {
     if (!defined($part)) { $part = '0'; }      if (!defined($part)) { $part = '0'; }
     return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.awarded'};      return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.awarded'};
 }  }
   sub taskversion {
       my $self = shift; my $part = shift;
       $self->{NAV_MAP}->get_user_data();
       if (!defined($part)) { $part = '0'; }
       return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.version'};
   }
   sub taskstatus {
       my $self = shift; my $part = shift;
       $self->{NAV_MAP}->get_user_data();
       if (!defined($part)) { $part = '0'; }
       return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$self->taskversion($part).'.'.$part.'.status'};
   }
   sub solved {
       my $self = shift; my $part = shift;
       $self->{NAV_MAP}->get_user_data();
       if (!defined($part)) { $part = '0'; }
       return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.solved'};
   }
   sub checkedin {
       my $self = shift; my $part = shift;
       $self->{NAV_MAP}->get_user_data();
       if (!defined($part)) { $part = '0'; }
       if ($self->is_task()) {
           my $version = $self->taskversion($part);
           return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$version .'.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$version .'.'.$part.'.checkedin.slot'});
       } else {
           return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.checkedin.slot'});
       }
   }
   # this should work exactly like the copy in lonhomework.pm
   # Why is there a copy in lonhomework?  Why not centralized?
   #
   #  TODO: Centralize duedate.
   #
   
 sub duedate {  sub duedate {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     my $interval=$self->parmval("interval", $part);      my $date;
     if ($interval) {      my @interval=$self->parmval("interval", $part);
  my $first_access=&Apache::lonnet::get_first_access('map',$self->symb);      my $due_date=$self->parmval("duedate", $part);
  if ($first_access) { return ($first_access+$interval); }      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 {  sub handgrade {
     (my $self, my $part) = @_;      (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 {  sub maxtries {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("maxtries", $part);      my $maxtries = $self->parmval("maxtries", $part);
       return $maxtries;
 }  }
 sub opendate {  sub opendate {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     if ($self->parmval("opendate.type", $part) eq 'date_interval') {      my $opendatetype = $self->parmval("opendate.type", $part);
         return $self->duedate($part) -      my $opendate = $self->parmval("opendate", $part); 
             $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 {  sub problemstatus {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return lc $self->parmval("problemstatus", $part);      my $problemstatus = $self->parmval("problemstatus", $part);
       return lc($problemstatus);
 }  }
 sub sig {  sub sig {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("sig", $part);      my $sig = $self->parmval("sig", $part);
       return $sig;
 }  }
 sub tol {  sub tol {
     (my $self, my $part) = @_;      (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 $self = shift; 
     my $tries = $self->queryRestoreHash('tries', shift);      my $tries = $self->queryRestoreHash('tries', shift);
     if (!defined($tries)) { return '0';}      if (!defined($tries)) { return '0';}
Line 3985  sub tries { Line 4200  sub tries {
 }  }
 sub type {  sub type {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("type", $part);      my $type = $self->parmval("type", $part);
       return $type;
 }  }
 sub weight {   sub weight { 
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
     if (!defined($part)) { $part = '0'; }      if (!defined($part)) { $part = '0'; }
     return &Apache::lonnet::EXT('resource.'.$part.'.weight',      my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight',
  $self->symb(), $env{'user.domain'},                                  $self->symb(), $self->{DOMAIN},
  $env{'user.name'},                                   $self->{USERNAME},
  $env{'request.course.sec'});                                  $env{'request.course.sec'});
       return $weight;
 }  }
 sub part_display {  sub part_display {
     my $self= shift(); my $partID = shift();      my $self= shift(); my $partID = shift();
Line 4005  sub part_display { Line 4222  sub part_display {
     }      }
     return $display;      return $display;
 }  }
   sub slot_control {
       my $self=shift(); my $part = shift();
       if (!defined($part)) { $part = '0'; }
       my $useslots = $self->parmval("useslots", $part);
       my $availablestudent = $self->parmval("availablestudent", $part);
       my $available = $self->parmval("available", $part); 
       return ($useslots,$availablestudent,$available);
   }
   
 # Multiple things need this  # Multiple things need this
 sub getReturnHash {  sub getReturnHash {
     my $self = shift;      my $self = shift;
           
     if (!defined($self->{RETURN_HASH})) {      if (!defined($self->{RETURN_HASH})) {
         my %tmpHash  = &Apache::lonnet::restore($self->symb());          my %tmpHash  = &Apache::lonnet::restore($self->symb(),undef,$self->{DOMAIN},$self->{USERNAME});
         $self->{RETURN_HASH} = \%tmpHash;          $self->{RETURN_HASH} = \%tmpHash;
     }      }
 }         }       
Line 4044  Returns a false value if there has been Line 4269  Returns a false value if there has been
 logged in, true if there has. Always returns false if the discussion  logged in, true if there has. Always returns false if the discussion
 data was not extracted when the nav map was constructed.  data was not extracted when the nav map was constructed.
   
   =item * B<last_post_time>:
   
   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<discussion_info>:
   
   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<getFeedback>:  =item * B<getFeedback>:
   
 Gets the feedback for the resource and returns the raw feedback string  Gets the feedback for the resource and returns the raw feedback string
Line 4051  for the resource, or the null string if Line 4291  for the resource, or the null string if
 email data was not extracted when the nav map was constructed. Usually  email data was not extracted when the nav map was constructed. Usually
 used like this:  used like this:
   
  for (split(/\,/, $res->getFeedback())) {   for my $url (split(/\,/, $res->getFeedback())) {
     my $link = &Apache::lonnet::escape($_);      my $link = &escape($url);
     ...      ...
   
 and use the link as appropriate.  and use the link as appropriate.
Line 4064  sub hasDiscussion { Line 4304  sub hasDiscussion {
     return $self->{NAV_MAP}->hasDiscussion($self->symb());      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 {  sub getFeedback {
     my $self = shift;      my $self = shift;
     my $source = $self->src();      my $source = $self->src();
       my $symb = $self->symb();
     if ($source =~ /^\/res\//) { $source = substr $source, 5; }      if ($source =~ /^\/res\//) { $source = substr $source, 5; }
     return $self->{NAV_MAP}->getFeedback($source);      return $self->{NAV_MAP}->getFeedback($symb,$source);
 }  }
   
 sub getErrors {  sub getErrors {
     my $self = shift;      my $self = shift;
     my $source = $self->src();      my $source = $self->src();
       my $symb = $self->symb();
     if ($source =~ /^\/res\//) { $source = substr $source, 5; }      if ($source =~ /^\/res\//) { $source = substr $source, 5; }
     return $self->{NAV_MAP}->getErrors($source);      return $self->{NAV_MAP}->getErrors($symb,$source);
 }  }
   
 =pod  =pod
Line 4161  sub countResponses { Line 4413  sub countResponses {
 sub responseTypes {  sub responseTypes {
     my $self = shift;      my $self = shift;
     my %responses;      my %responses;
     foreach my $part ($self->parts()) {      foreach my $part (@{$self->parts()}) {
         foreach my $responsetype ($self->responseType($part)) {          foreach my $responsetype ($self->responseType($part)) {
             $responses{$responsetype}++ if (defined($responsetype));              $responses{$responsetype}++ if (defined($responsetype));
         }          }
Line 4236  sub extractParts { Line 4488  sub extractParts {
  $self->{PART_TYPE} = {};   $self->{PART_TYPE} = {};
  return;   return;
     }      }
     foreach (split(/\,/,$metadata)) {      foreach my $entry (split(/\,/,$metadata)) {
  if ($_ =~ /^(?:part|Task)_(.*)$/) {   if ($entry =~ /^(?:part|Task)_(.*)$/) {
     my $part = $1;      my $part = $1;
     # This floods the logs if it blows up      # This floods the logs if it blows up
     if (defined($parts{$part})) {      if (defined($parts{$part})) {
Line 4262  sub extractParts { Line 4514  sub extractParts {
   
   
         # Init the responseIdHash          # Init the responseIdHash
         foreach (@{$self->{PARTS}}) {          foreach my $part (@{$self->{PARTS}}) {
             $responseIdHash{$_} = [];              $responseIdHash{$part} = [];
         }          }
   
         # Now, the unfortunate thing about this is that parts, part name, and          # Now, the unfortunate thing about this is that parts, part name, and
Line 4272  sub extractParts { Line 4524  sub extractParts {
         # So we have to use our knowlege of part names to figure out           # 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          # where the part names begin and end, and even then, it is possible
         # to construct ambiguous situations.          # to construct ambiguous situations.
         foreach (split /,/, $metadata) {          foreach my $data (split /,/, $metadata) {
             if ($_ =~ /^([a-zA-Z]+)response_(.*)/) {              if ($data =~ /^([a-zA-Z]+)response_(.*)/
    || $data =~ /^(Task)_(.*)/) {
                 my $responseType = $1;                  my $responseType = $1;
                 my $partStuff = $2;                  my $partStuff = $2;
                 my $partIdSoFar = '';                  my $partIdSoFar = '';
Line 4285  sub extractParts { Line 4538  sub extractParts {
                     if ($parts{$partIdSoFar}) {                      if ($parts{$partIdSoFar}) {
                         my @otherChunks = @partChunks[$i+1..$#partChunks];                          my @otherChunks = @partChunks[$i+1..$#partChunks];
                         my $responseId = join('_', @otherChunks);                          my $responseId = join('_', @otherChunks);
                         push @{$responseIdHash{$partIdSoFar}}, $responseId;   if ($self->is_task()) {
                         push @{$responseTypeHash{$partIdSoFar}}, $responseType;      push(@{$responseIdHash{$partIdSoFar}},
    $partIdSoFar);
    } else {
       push(@{$responseIdHash{$partIdSoFar}},
    $responseId);
    }
                           push(@{$responseTypeHash{$partIdSoFar}},
        $responseType);
                     }                      }
                 }                  }
             }              }
Line 4335  the completion information. Line 4595  the completion information.
 Idiomatic usage of these two methods would probably look something  Idiomatic usage of these two methods would probably look something
 like  like
   
  foreach ($resource->parts()) {   foreach my $part ($resource->parts()) {
     my $dateStatus = $resource->getDateStatus($_);      my $dateStatus = $resource->getDateStatus($part);
     my $completionStatus = $resource->getCompletionStatus($_);      my $completionStatus = $resource->getCompletionStatus($part);
   
     or      or
   
     my $status = $resource->status($_);      my $status = $resource->status($part);
   
     ... use it here ...      ... use it here ...
  }   }
Line 4402  sub OPEN                   { return 1; } Line 4662  sub OPEN                   { return 1; }
 sub PAST_DUE_NO_ANSWER     { return 2; }  sub PAST_DUE_NO_ANSWER     { return 2; }
 sub PAST_DUE_ANSWER_LATER  { return 3; }  sub PAST_DUE_ANSWER_LATER  { return 3; }
 sub ANSWER_OPEN            { return 4; }  sub ANSWER_OPEN            { return 4; }
 sub NOTHING_SET            { return 5; }   sub NOTHING_SET            { return 5; }
 sub NETWORK_FAILURE        { return 100; }  sub NETWORK_FAILURE        { return 100; }
   
 # getDateStatus gets the date status for a given problem part.   # getDateStatus gets the date status for a given problem part. 
Line 4488  Information not available due to network Line 4748  Information not available due to network
   
 Attempted, and not yet graded.  Attempted, and not yet graded.
   
   =item * B<CREDIT_ATTEMPTED>:
   
   Attempted, and credit received for attempt (survey and anonymous survey only).
   
 =back  =back
   
 =cut  =cut
Line 4499  sub CORRECT               { return 13; } Line 4763  sub CORRECT               { return 13; }
 sub CORRECT_BY_OVERRIDE   { return 14; }  sub CORRECT_BY_OVERRIDE   { return 14; }
 sub EXCUSED               { return 15; }  sub EXCUSED               { return 15; }
 sub ATTEMPTED             { return 16; }  sub ATTEMPTED             { return 16; }
   sub CREDIT_ATTEMPTED      { return 17; }
   
 sub getCompletionStatus {  sub getCompletionStatus {
     my $self = shift;      my $self = shift;
Line 4517  sub getCompletionStatus { Line 4782  sub getCompletionStatus {
     if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; }      if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; }
     if ($status eq 'excused') {return $self->EXCUSED; }      if ($status eq 'excused') {return $self->EXCUSED; }
     if ($status eq 'ungraded_attempted') {return $self->ATTEMPTED; }      if ($status eq 'ungraded_attempted') {return $self->ATTEMPTED; }
       if ($status eq 'credit_attempted') {
           if ($self->is_anonsurvey($part) || $self->is_survey($part)) {
               return $self->CREDIT_ATTEMPTED;
           } else {
               return $self->ATTEMPTED;
           }
       }
     return $self->NOT_ATTEMPTED;      return $self->NOT_ATTEMPTED;
 }  }
   
Line 4606  The item is open and not yet tried. Line 4878  The item is open and not yet tried.
   
 The problem has been attempted.  The problem has been attempted.
   
   =item * B<CREDIT_ATTEMPTED>:
   
   The problem has been attempted, and credit given for the attempt (survey and anonymous survey only).
   
 =item * B<ANSWER_SUBMITTED>:  =item * B<ANSWER_SUBMITTED>:
   
 An answer has been submitted, but the student should not see it.  An answer has been submitted, but the student should not see it.
Line 4614  An answer has been submitted, but the st Line 4890  An answer has been submitted, but the st
   
 =cut  =cut
   
 sub TRIES_LEFT       { return 20; }  sub TRIES_LEFT        { return 20; }
 sub ANSWER_SUBMITTED { return 21; }  sub ANSWER_SUBMITTED  { return 21; }
 sub PARTIALLY_CORRECT{ return 22; }  sub PARTIALLY_CORRECT { return 22; }
   
   sub RESERVED_LATER    { return 30; }
   sub RESERVED          { return 31; }
   sub RESERVED_LOCATION { return 32; }
   sub RESERVABLE        { return 33; }
   sub RESERVABLE_LATER  { return 34; }
   sub NOTRESERVABLE     { return 35; }
   sub NOT_IN_A_SLOT     { return 36; }
   sub NEEDS_CHECKIN     { return 37; }
   sub WAITING_FOR_GRADE { return 38; }
   sub UNKNOWN           { return 39; }
   
 sub status {  sub status {
     my $self = shift;      my $self = shift;
Line 4632  sub status { Line 4919  sub status {
     #if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; }      #if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; }
     if ($completionStatus == NETWORK_FAILURE) { 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      # If there's an answer date and we're past it, don't
     # suppress the feedback; student should know      # suppress the feedback; student should know
     if ($self->duedate($part) && $self->duedate($part) < time() &&      if ($self->duedate($part) && $self->duedate($part) < time() &&
Line 4664  sub status { Line 4955  sub status {
         return ATTEMPTED;          return ATTEMPTED;
     }      }
   
       if ($completionStatus == CREDIT_ATTEMPTED) {
           return CREDIT_ATTEMPTED;
       }
   
     # If it's EXCUSED, then return that no matter what      # If it's EXCUSED, then return that no matter what
     if ($completionStatus == EXCUSED) {      if ($completionStatus == EXCUSED) {
         return EXCUSED;           return EXCUSED; 
Line 4705  sub status { Line 5000  sub status {
     return OPEN;       return OPEN; 
 }  }
   
   sub check_for_slot {
       my $self = shift;
       my $part = shift;
       my ($use_slots,$available,$availablestudent) = $self->slot_control($part);
       if (($use_slots ne '') && ($use_slots !~ /^\s*no\s*$/i)) {
           my @slots = (split(/:/,$availablestudent),split(/:/,$available));
           my $cid=$env{'request.course.id'};
           my $cdom=$env{'course.'.$cid.'.domain'};
           my $cnum=$env{'course.'.$cid.'.num'};
           my $now = time;
           if (@slots > 0) {
               my %slots=&Apache::lonnet::get('slots',[@slots],$cdom,$cnum);
               if (&Apache::lonnet::error(%slots)) {
                   return (UNKNOWN);
               }
               my @sorted_slots = &Apache::loncommon::sorted_slots(\@slots,\%slots);
               my ($checkedin,$checkedinslot);
               foreach my $slot_name (@sorted_slots) {
                   next if (!defined($slots{$slot_name}) ||
                            !ref($slots{$slot_name}));
                   my $end = $slots{$slot_name}->{'endtime'};
                   my $start = $slots{$slot_name}->{'starttime'};
                   my $ip = $slots{$slot_name}->{'ip'};
                   if ($self->simpleStatus() == OPEN) {
                       my $startreserve = $slots{$slot_name}->{'startreserve'};
                       my @proctors;
                       if ($slots{$slot_name}->{'proctor'} ne '') {
                           @proctors = split(',',$slots{$slot_name}->{'proctor'});
                       }
                       if ($end > $now) {
                           ($checkedin,$checkedinslot) = $self->checkedin();
                           if ($startreserve < $now) {
                               if ($start > $now) {
                                   return (RESERVED_LATER,$start,$slot_name);
                               } else {
                                   if ($ip ne '') {
                                       if (!&Apache::loncommon::check_ip_acc($ip)) {
                                           return (RESERVED_LOCATION,$ip,$slot_name);
                                       }
                                   } 
                                   if (@proctors > 0) {
                                       unless ((grep(/^\Q$checkedin\E/,@proctors)) &&
                                           ($checkedinslot eq $slot_name)) {
                                           return (NEEDS_CHECKIN,undef,$slot_name); 
                                       }
                                   }
                                   return (RESERVED,$end,$slot_name);
                               }
                           } else {
                               if ($start > $now) {
                                   return (RESERVABLE,$startreserve,$slot_name);
                               }
                           }
                       }
                   }
               }
               my ($is_correct,$got_grade);
               if ($self->is_task()) {
                   my $taskstatus = $self->taskstatus();
                   $is_correct = (($taskstatus eq 'pass') || 
                                  ($self->solved() =~ /^correct_/));
                   $got_grade = ($taskstatus =~ /^(?:pass|fail)$/);
               } else {
                   $got_grade = 1;
                   $is_correct = ($self->solved() =~ /^correct_/);   
               }
               ($checkedin,$checkedinslot) = $self->checkedin();
               if ($checkedin) {
                   if (!$got_grade) {
                       return (WAITING_FOR_GRADE);
                   } elsif ($is_correct) {
                       return (CORRECT); 
                   }
               }
               return(NOT_IN_A_SLOT);
           } else {
               if (!$future_slots_checked) {
                   $future_slots = &get_future_slots($cdom,$cnum,$now);
                   $future_slots_checked = 1;
               }
               if ($future_slots) {
                   return(NOT_IN_A_SLOT);
               }
               return(NOTRESERVABLE);
           }
       }
       return;
   }
   
   sub get_future_slots {
       my ($cdom,$cnum,$now) = @_;
       my %slots=&Apache::lonnet::dump('slots',$cdom,$cnum);
       my $future_slots = 0;
       foreach my $slot (keys(%slots)) {
           if (($slots{$slot}->{'starttime'} > $now) &&
               ($slots{$slot}->{'endtime'} > $now)) {
               $future_slots ++;
           }
       }
       return $future_slots;
   }
   
 sub CLOSED { return 23; }  sub CLOSED { return 23; }
 sub ERROR { return 24; }  sub ERROR { return 24; }
   
Line 4754  my %compositeToSimple = Line 5151  my %compositeToSimple =
       INCORRECT()             => INCORRECT,        INCORRECT()             => INCORRECT,
       OPEN()                  => OPEN,        OPEN()                  => OPEN,
       ATTEMPTED()             => ATTEMPTED,        ATTEMPTED()             => ATTEMPTED,
         CREDIT_ATTEMPTED()      => CORRECT,
       ANSWER_SUBMITTED()      => ATTEMPTED        ANSWER_SUBMITTED()      => ATTEMPTED
      );       );
   
Line 4828  sub completable { Line 5226  sub completable {
         # and it is not "attempted" (manually graded problem), it is          # and it is not "attempted" (manually graded problem), it is
         # not "complete"          # not "complete"
  if ($self->getCompletionStatus($part) == ATTEMPTED() ||   if ($self->getCompletionStatus($part) == ATTEMPTED() ||
               $self->getCompletionStatus($part) == CREDIT_ATTEMPTED() ||
     $status == ANSWER_SUBMITTED() ) {      $status == ANSWER_SUBMITTED() ) {
     # did this part already, as well as we can      # did this part already, as well as we can
     next;      next;

Removed from v.1.357  
changed lines
  Added in v.1.464


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>