Diff for /loncom/interface/lonnavmaps.pm between versions 1.216 and 1.333

version 1.216, 2003/07/17 18:40:49 version 1.333, 2005/07/06 18:07:02
Line 25 Line 25
 #  #
 # http://www.lon-capa.org/  # http://www.lon-capa.org/
 #  #
 # (Page Handler  ###
 #  
 # (TeX Content Handler  
 #  
 # 05/29/00,05/30 Gerd Kortemeyer)  
 # 08/30,08/31,09/06,09/14,09/15,09/16,09/19,09/20,09/21,09/23,  
 # 10/02,10/10,10/14,10/16,10/18,10/19,10/31,11/6,11/14,11/16 Gerd Kortemeyer)  
 #  
 # 3/1/1,6/1,17/1,29/1,30/1,2/8,9/21,9/24,9/25 Gerd Kortemeyer  
 # YEAR=2002  
 # 1/1 Gerd Kortemeyer  
 # Oct-Nov Jeremy Bowers  
 # YEAR=2003  
 # Jeremy Bowers ... lots of days  
   
 package Apache::lonnavmaps;  package Apache::lonnavmaps;
   
 use strict;  use strict;
   use GDBM_File;
 use Apache::Constants qw(:common :http);  use Apache::Constants qw(:common :http);
 use Apache::loncommon();  use Apache::loncommon();
 use Apache::lonmenu();  use Apache::lonmenu();
   use Apache::lonenc();
   use Apache::lonlocal;
   use Apache::lonnet;
 use POSIX qw (floor strftime);  use POSIX qw (floor strftime);
 use Data::Dumper; # for debugging, not always used  use Data::Dumper; # for debugging, not always 
   use Time::HiRes qw( gettimeofday tv_interval );
   
 # symbolic constants  # symbolic constants
 sub SYMB { return 1; }  sub SYMB { return 1; }
Line 61  my $resObj = "Apache::lonnavmaps::resour Line 53  my $resObj = "Apache::lonnavmaps::resour
 # Keep these mappings in sync with lonquickgrades, which uses the colors  # Keep these mappings in sync with lonquickgrades, which uses the colors
 # instead of the icons.  # instead of the icons.
 my %statusIconMap =   my %statusIconMap = 
     ( $resObj->NETWORK_FAILURE    => '',      (
       $resObj->NOTHING_SET        => '',       $resObj->CLOSED       => '',
       $resObj->CORRECT            => 'navmap.correct.gif',       $resObj->OPEN         => 'navmap.open.gif',
       $resObj->EXCUSED            => 'navmap.correct.gif',       $resObj->CORRECT      => 'navmap.correct.gif',
       $resObj->PAST_DUE_NO_ANSWER => 'navmap.wrong.gif',       $resObj->PARTIALLY_CORRECT      => 'navmap.ellipsis.gif',
       $resObj->PAST_DUE_ANSWER_LATER => 'navmap.wrong.gif',       $resObj->INCORRECT    => 'navmap.wrong.gif',
       $resObj->ANSWER_OPEN        => 'navmap.wrong.gif',       $resObj->ATTEMPTED    => 'navmap.ellipsis.gif',
       $resObj->OPEN_LATER         => '',       $resObj->ERROR        => ''
       $resObj->TRIES_LEFT         => 'navmap.open.gif',       );
       $resObj->INCORRECT          => 'navmap.wrong.gif',  
       $resObj->OPEN               => 'navmap.open.gif',  
       $resObj->ATTEMPTED          => 'navmap.ellipsis.gif',  
       $resObj->ANSWER_SUBMITTED   => 'navmap.ellipsis.gif' );  
   
 my %iconAltTags =   my %iconAltTags = 
     ( 'navmap.correct.gif' => 'Correct',      ( 'navmap.correct.gif' => 'Correct',
Line 94  my %colormap = Line 82  my %colormap =
       $resObj->OPEN                   => '',        $resObj->OPEN                   => '',
       $resObj->NOTHING_SET            => '',        $resObj->NOTHING_SET            => '',
       $resObj->ATTEMPTED              => '',        $resObj->ATTEMPTED              => '',
       $resObj->ANSWER_SUBMITTED       => ''        $resObj->ANSWER_SUBMITTED       => '',
         $resObj->PARTIALLY_CORRECT      => '#006600'
       );        );
 # And a special case in the nav map; what to do when the assignment  # And a special case in the nav map; what to do when the assignment
 # is not yet done and due in less then 24 hours  # is not yet done and due in less then 24 hours
 my $hurryUpColor = "#FF0000";  my $hurryUpColor = "#FF0000";
   
   sub launch_win {
       my ($mode,$script,$toplinkitems)=@_;
       my $result;
       if ($script ne 'no') {
    $result.='<script type="text/javascript">';
       }
       $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 {  sub handler {
     my $r = shift;      my $r = shift;
     real_handler($r);      real_handler($r);
Line 107  sub handler { Line 146  sub handler {
   
 sub real_handler {  sub real_handler {
     my $r = shift;      my $r = shift;
       #my $t0=[&gettimeofday()];
     # Handle header-only request      # Handle header-only request
     if ($r->header_only) {      if ($r->header_only) {
         if ($ENV{'browser.mathml'}) {          if ($env{'browser.mathml'}) {
             $r->content_type('text/xml');              &Apache::loncommon::content_type($r,'text/xml');
         } else {          } else {
             $r->content_type('text/html');              &Apache::loncommon::content_type($r,'text/html');
         }          }
         $r->send_http_header;          $r->send_http_header;
         return OK;          return OK;
     }      }
   
     # Send header, don't cache this page      # Send header, don't cache this page
     if ($ENV{'browser.mathml'}) {      if ($env{'browser.mathml'}) {
         $r->content_type('text/xml');          &Apache::loncommon::content_type($r,'text/xml');
     } else {      } else {
         $r->content_type('text/html');          &Apache::loncommon::content_type($r,'text/html');
     }      }
     &Apache::loncommon::no_cache($r);      &Apache::loncommon::no_cache($r);
     $r->send_http_header;      $r->send_http_header;
   
     # Create the nav map      my %toplinkitems=();
     my $navmap = Apache::lonnavmaps::navmap->new(      &add_linkitem(\%toplinkitems,'blank','',"Select Action");
                         $ENV{"request.course.fn"}.".db",      if ($ENV{QUERY_STRING} eq 'collapseExternal') {
                         $ENV{"request.course.fn"}."_parms.db", 1, 1);   &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
    }
    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;
       }
       if ($ENV{QUERY_STRING} eq 'launchExternal') {
    &Apache::lonnet::put('environment',{'remotenavmap' => 'on'});
    &Apache::lonnet::appenv('environment.remotenavmap' => 'on');
       }
   
       # Create the nav map
       my $navmap = Apache::lonnavmaps::navmap->new();
   
     if (!defined($navmap)) {      if (!defined($navmap)) {
         my $requrl = $r->uri;          my $requrl = $r->uri;
         $ENV{'user.error.msg'} = "$requrl:bre:0:0:Course not initialized";          $env{'user.error.msg'} = "$requrl:bre:0:0:Course not initialized";
         return HTTP_NOT_ACCEPTABLE;          return HTTP_NOT_ACCEPTABLE;
     }      }
       my $html=&Apache::lonxml::xmlbegin();
     $r->print("<html><head>\n");      $r->print("$html<head>\n");
     $r->print("<title>Navigate Course Contents</title>");      $r->print("<title>".&mt('Navigate Course Contents')."</title>");
 # ------------------------------------------------------------ Get query string  # ------------------------------------------------------------ Get query string
     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register']);      &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register','sort','showOnlyHomework','postsymb']);
           
 # ----------------------------------------------------- Force menu registration  # ----------------------------------------------------- Force menu registration
     my $addentries='';      my $addentries='';
     if ($ENV{'form.register'}) {      my $more_unload;
        $addentries=' onLoad="'.&Apache::lonmenu::loadevents().      my $body_only='';
    '" onUnload="'.&Apache::lonmenu::unloadevents().'"';      if ($env{'environment.remotenavmap'} eq 'on') {
        $r->print(&Apache::lonmenu::registerurl(1));   $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      # Header
     $r->print('</head>'.      $r->print('</head>'.
               &Apache::loncommon::bodytag('Navigate Course Contents','',                &Apache::loncommon::bodytag('Navigate Course Contents','',
                                     $addentries,'','',$ENV{'form.register'}));    $addentries,$body_only,'',
     $env{'form.register'}));
     $r->print('<script>window.focus();</script>');      $r->print('<script>window.focus();</script>');
        
     $r->rflush();  
   
     # Now that we've displayed some stuff to the user, init the navmap  
     $navmap->init();  
   
     $r->rflush();      $r->rflush();
   
     # Check that it's defined      # Check that it's defined
     if (!($navmap->courseMapDefined())) {      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>' .          $r->print('<font size="+2" color="red">Coursemap undefined.</font>' .
                   '</body></html>');                    '</body></html>');
         return OK;          return OK;
Line 175  sub real_handler { Line 266  sub real_handler {
   
     # See if there's only one map in the top-level, if we don't      # See if there's only one map in the top-level, if we don't
     # already have a filter... if so, automatically display it      # already have a filter... if so, automatically display it
       # (older code; should use retrieveResources)
     if ($ENV{QUERY_STRING} !~ /filter/) {      if ($ENV{QUERY_STRING} !~ /filter/) {
         my $iterator = $navmap->getIterator(undef, undef, undef, 0);          my $iterator = $navmap->getIterator(undef, undef, undef, 0);
         my $depth = 1;          my $curRes;
         $iterator->next();  
         my $curRes = $iterator->next();  
         my $sequenceCount = 0;          my $sequenceCount = 0;
         my $sequenceId;          my $sequenceId;
         while ($depth > 0) {          while ($curRes = $iterator->next()) {
             if ($curRes == $iterator->BEGIN_MAP()) { $depth++; }  
             if ($curRes == $iterator->END_MAP()) { $depth--; }  
               
             if (ref($curRes) && $curRes->is_sequence()) {              if (ref($curRes) && $curRes->is_sequence()) {
                 $sequenceCount++;                  $sequenceCount++;
                 $sequenceId = $curRes->map_pc();                  $sequenceId = $curRes->map_pc();
             }              }
               
             $curRes = $iterator->next();  
         }          }
                   
         if ($sequenceCount == 1) {          if ($sequenceCount == 1) {
             # The automatic iterator creation in the render call               # The automatic iterator creation in the render call 
             # will pick this up. We know the condition because              # will pick this up. We know the condition because
             # the defined($ENV{'form.filter'}) also ensures this              # the defined($env{'form.filter'}) also ensures this
             # is a fresh call.              # is a fresh call.
             $ENV{'form.filter'} = "$sequenceId";              $env{'form.filter'} = "$sequenceId";
         }          }
     }      }
   
       if ($ENV{QUERY_STRING} eq 'launchExternal') {
    $r->print('
             <form name="returnwin" action="/adm/flip?postdata=return%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;      my $jumpToFirstHomework = 0;
     # Check to see if the student is jumping to next open, do-able problem      # Check to see if the student is jumping to next open, do-able problem
     if ($ENV{QUERY_STRING} eq 'jumpToFirstHomework') {      if ($ENV{QUERY_STRING} =~ /^jumpToFirstHomework/) {
         $jumpToFirstHomework = 1;          $jumpToFirstHomework = 1;
         # Find the next homework problem that they can do.          # Find the next homework problem that they can do.
         my $iterator = $navmap->getIterator(undef, undef, undef, 1);          my $iterator = $navmap->getIterator(undef, undef, undef, 1);
         my $depth = 1;          my $curRes;
         $iterator->next();  
         my $curRes = $iterator->next();  
         my $foundDoableProblem = 0;          my $foundDoableProblem = 0;
         my $problemRes;          my $problemRes;
                   
         while ($depth > 0 && !$foundDoableProblem) {          while (($curRes = $iterator->next()) && !$foundDoableProblem) {
             if ($curRes == $iterator->BEGIN_MAP()) { $depth++; }  
             if ($curRes == $iterator->END_MAP()) { $depth--; }  
   
             if (ref($curRes) && $curRes->is_problem()) {              if (ref($curRes) && $curRes->is_problem()) {
                 my $status = $curRes->status();                  my $status = $curRes->status();
                 if ($curRes->completable()) {                  if ($curRes->completable()) {
Line 230  sub real_handler { Line 329  sub real_handler {
                     pop @$stack; # last resource in the stack is the problem                      pop @$stack; # last resource in the stack is the problem
                                  # itself, which we don't need in the map stack                                   # itself, which we don't need in the map stack
                     my @mapPcs = map {$_->map_pc()} @$stack;                      my @mapPcs = map {$_->map_pc()} @$stack;
                     $ENV{'form.filter'} = join(',', @mapPcs);                      $env{'form.filter'} = join(',', @mapPcs);
   
                     # Mark as both "here" and "jump"                      # Mark as both "here" and "jump"
                     $ENV{'form.postsymb'} = $curRes->symb();                      $env{'form.postsymb'} = $curRes->symb();
                 }                  }
             }              }
         } continue {  
             $curRes = $iterator->next();  
         }          }
   
         # If we found no problems, print a note to that effect.          # If we found no problems, print a note to that effect.
Line 245  sub real_handler { Line 342  sub real_handler {
             $r->print("<font size='+2'>All homework assignments have been completed.</font><br /><br />");              $r->print("<font size='+2'>All homework assignments have been completed.</font><br /><br />");
         }          }
     } else {      } else {
         $r->print("<a href='navmaps?jumpToFirstHomework'>" .   &add_linkitem(\%toplinkitems,'firsthomework',
                   "Go To My First Homework Problem</a>&nbsp;&nbsp;&nbsp;&nbsp;");        'location.href="navmaps?jumpToFirstHomework"',
         "Show Me My First Homework Problem");
     }      }
   
     my $suppressEmptySequences = 0;      my $suppressEmptySequences = 0;
Line 255  sub real_handler { Line 353  sub real_handler {
   
     # Display only due homework.      # Display only due homework.
     my $showOnlyHomework = 0;      my $showOnlyHomework = 0;
     if ($ENV{QUERY_STRING} eq 'showOnlyHomework') {      if ($env{'form.showOnlyHomework'} eq "1") {
         $showOnlyHomework = 1;          $showOnlyHomework = 1;
         $suppressEmptySequences = 1;          $suppressEmptySequences = 1;
         $filterFunc = sub { my $res = shift;           $filterFunc = sub { my $res = shift; 
                             return $res->completable() || $res->is_map();                              return $res->completable() || $res->is_map();
                         };                          };
         $r->print("<p><font size='+2'>Uncompleted Homework</font></p>");   &add_linkitem(\%toplinkitems,'everything',
         $ENV{'form.filter'} = '';       'location.href="navmaps?sort='.$env{'form.sort'}.'"',
         $ENV{'form.condition'} = 1;        "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;   $resource_no_folder_link = 1;
     } else {      } else {
         $r->print("<a href='navmaps?showOnlyHomework'>" .   &add_linkitem(\%toplinkitems,'uncompleted',
                   "Show Only Uncompleted Homework</a>&nbsp;&nbsp;&nbsp;&nbsp;");        '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      # renderer call
     my $renderArgs = { 'cols' => [0,1,2,3],      my $renderArgs = { 'cols' => [0,1,2,3],
          'sort' => $env{'form.sort'},
                        'url' => '/adm/navmaps',                         'url' => '/adm/navmaps',
                        'navmap' => $navmap,                         'navmap' => $navmap,
                        'suppressNavmap' => 1,                         'suppressNavmap' => 1,
                        'suppressEmptySequences' => $suppressEmptySequences,                         'suppressEmptySequences' => $suppressEmptySequences,
                        'filterFunc' => $filterFunc,                         'filterFunc' => $filterFunc,
        'resource_no_folder_link' => $resource_no_folder_link,         'resource_no_folder_link' => $resource_no_folder_link,
                        'r' => $r};         'sort_html'=> $sort_html,
                          'r' => $r,
                          'caller' => 'navmapsdisplay',
                          'linkitems' => \%toplinkitems};
     my $render = render($renderArgs);      my $render = render($renderArgs);
     $navmap->untieHashes();  
   
     # If no resources were printed, print a reassuring message so the      # If no resources were printed, print a reassuring message so the
     # user knows there was no error.      # user knows there was no error.
     if ($renderArgs->{'counter'} == 0) {      if ($renderArgs->{'counter'} == 0) {
         if ($showOnlyHomework) {          if ($showOnlyHomework) {
             $r->print("<p><font size='+1'>All homework is currently completed.</font></p>");              $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          } else { # both jumpToFirstHomework and normal use the same: course must be empty
             $r->print("<p><font size='+1'>This course is empty.</font></p>");              $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->print("</body></html>");
     $r->rflush();      $r->rflush();
Line 321  sub removeFromFilter { Line 442  sub removeFromFilter {
   
 # Convenience function: Given a stack returned from getStack on the iterator,  # Convenience function: Given a stack returned from getStack on the iterator,
 # return the correct src() value.  # return the correct src() value.
 # Later, this should add an anchor when we start putting anchors in pages.  
 sub getLinkForResource {  sub getLinkForResource {
     my $stack = shift;      my $stack = shift;
     my $res;      my $res;
   
     # Check to see if there are any pages in the stack      # Check to see if there are any pages in the stack
     foreach $res (@$stack) {      foreach $res (@$stack) {
         if (defined($res) && $res->is_page()) {          if (defined($res)) {
             return $res->src();      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);
       }
         }          }
     }      }
   
Line 341  sub getLinkForResource { Line 473  sub getLinkForResource {
         if (defined($_)) { $res = $_; }          if (defined($_)) { $res = $_; }
     }      }
   
     return $res->src();      return ($res->link(),$res->shown_symb());
 }  }
   
 # Convenience function: This seperates the logic of how to create  # Convenience function: This separates the logic of how to create
 # the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned",  # the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned",
 # etc.) into a seperate function. It takes a resource object as the  # etc.) into a separate function. It takes a resource object as the
 # first parameter, and the part number of the resource as the second.  # 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.  # It's basically a big switch statement on the status of the resource.
   
Line 356  sub getDescription { Line 488  sub getDescription {
     my $status = $res->status($part);      my $status = $res->status($part);
   
     if ($status == $res->NETWORK_FAILURE) {       if ($status == $res->NETWORK_FAILURE) { 
         return "Having technical difficulties; please check status later";           return &mt("Having technical difficulties; please check status later"); 
     }      }
     if ($status == $res->NOTHING_SET) {      if ($status == $res->NOTHING_SET) {
         return "Not currently assigned.";          return &mt("Not currently assigned.");
     }      }
     if ($status == $res->OPEN_LATER) {      if ($status == $res->OPEN_LATER) {
         return "Open " . timeToHumanString($res->opendate($part));          return "Open " . timeToHumanString($res->opendate($part));
     }      }
     if ($status == $res->OPEN) {      if ($status == $res->OPEN) {
         if ($res->duedate($part)) {          if ($res->duedate($part)) {
             return "Due " . timeToHumanString($res->duedate($part));              return &mt("Due")."  " .timeToHumanString($res->duedate($part));
         } else {          } else {
             return "Open, no due date";              return &mt("Open, no due date");
         }          }
     }      }
     if ($status == $res->PAST_DUE_ANSWER_LATER) {      if ($status == $res->PAST_DUE_ANSWER_LATER) {
         return "Answer open " . timeToHumanString($res->answerdate($part));          return &mt("Answer open")." " . timeToHumanString($res->answerdate($part));
     }      }
     if ($status == $res->PAST_DUE_NO_ANSWER) {      if ($status == $res->PAST_DUE_NO_ANSWER) {
         return "Was due " . timeToHumanString($res->duedate($part));          return &mt("Was due")." " . timeToHumanString($res->duedate($part));
     }      }
     if ($status == $res->ANSWER_OPEN) {      if ($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT) {
         return "Answer available";          return &mt("Answer available");
     }      }
     if ($status == $res->EXCUSED) {      if ($status == $res->EXCUSED) {
         return "Excused by instructor";          return &mt("Excused by instructor");
     }      }
     if ($status == $res->ATTEMPTED) {      if ($status == $res->ATTEMPTED) {
         return "Answer submitted, not yet graded.";          return &mt("Answer submitted, not yet graded");
     }      }
     if ($status == $res->TRIES_LEFT) {      if ($status == $res->TRIES_LEFT) {
         my $tries = $res->tries($part);          my $tries = $res->tries($part);
Line 396  sub getDescription { Line 528  sub getDescription {
                 $triesString = "<b>$triesString</b>";                  $triesString = "<b>$triesString</b>";
             }              }
         }          }
         if ($res->duedate()) {          if ($res->duedate($part)) {
             return "Due " . timeToHumanString($res->duedate($part)) .              return &mt("Due")." " . timeToHumanString($res->duedate($part)) .
                 " $triesString";                  " $triesString";
         } else {          } else {
             return "No due date $triesString";              return &mt("No due date")." $triesString";
         }          }
     }      }
     if ($status == $res->ANSWER_SUBMITTED) {      if ($status == $res->ANSWER_SUBMITTED) {
         return 'Answer submitted';          return &mt('Answer submitted');
     }      }
 }  }
   
 # Convenience function, so others can use it: Is the problem due in less then  # Convenience function, so others can use it: Is the problem due in less then
 # 24 hours, and still can be done?  # 24 hours, and still can be done?
   
 sub dueInLessThen24Hours {  sub dueInLessThan24Hours {
     my $res = shift;      my $res = shift;
     my $part = shift;      my $part = shift;
     my $status = $res->status($part);      my $status = $res->status($part);
   
     return ($status == $res->OPEN() ||      return ($status == $res->OPEN() ||
             $status == $res->TRIES_LEFT()) &&              $status == $res->TRIES_LEFT()) &&
            $res->duedate() && $res->duedate() < time()+(24*60*60) &&      $res->duedate($part) && $res->duedate($part) < time()+(24*60*60) &&
            $res->duedate() > time();      $res->duedate($part) > time();
 }  }
   
 # Convenience function, so others can use it: Is there only one try remaining for the  # Convenience function, so others can use it: Is there only one try remaining for the
Line 431  sub lastTry { Line 563  sub lastTry {
     my $tries = $res->tries($part);      my $tries = $res->tries($part);
     my $maxtries = $res->maxtries($part);      my $maxtries = $res->maxtries($part);
     return $tries && $maxtries && $maxtries > 1 &&      return $tries && $maxtries && $maxtries > 1 &&
         $maxtries - $tries == 1 && $res->duedate() &&          $maxtries - $tries == 1 && $res->duedate($part) &&
         $res->duedate() > time();          $res->duedate($part) > time();
 }  }
   
 # This puts a human-readable name on the ENV variable.  # This puts a human-readable name on the env variable.
   
 sub advancedUser {  sub advancedUser {
     return $ENV{'request.role.adv'};      return $env{'request.role.adv'};
 }  }
   
   
Line 454  sub timeToHumanString { Line 586  sub timeToHumanString {
     my ($time) = @_;      my ($time) = @_;
     # zero, '0' and blank are bad times      # zero, '0' and blank are bad times
     if (!$time) {      if (!$time) {
         return 'never';          return &mt('never');
     }      }
       unless (&Apache::lonlocal::current_language()=~/^en/) {
    return &Apache::lonlocal::locallocaltime($time);
       } 
     my $now = time();      my $now = time();
   
     my @time = localtime($time);      my @time = localtime($time);
Line 524  sub timeToHumanString { Line 658  sub timeToHumanString {
         # HH:MM          # HH:MM
         if ( $delta < $day * 5 ) {          if ( $delta < $day * 5 ) {
             my $timeStr = strftime("%A, %b %e at %I:%M %P", localtime($time));              my $timeStr = strftime("%A, %b %e at %I:%M %P", localtime($time));
             $timeStr =~ s/12:00 am/midnight/;              $timeStr =~ s/12:00 am/00:00/;
             $timeStr =~ s/12:00 pm/noon/;              $timeStr =~ s/12:00 pm/noon/;
             return ($inPast ? "last " : "next ") .              return ($inPast ? "last " : "this ") .
                 $timeStr;                  $timeStr;
         }          }
                   
Line 534  sub timeToHumanString { Line 668  sub timeToHumanString {
         if ( $time[5] == $now[5]) {          if ( $time[5] == $now[5]) {
             # Return on Month Day, HH:MM meridian              # Return on Month Day, HH:MM meridian
             my $timeStr = strftime("on %A, %b %e at %I:%M %P", localtime($time));              my $timeStr = strftime("on %A, %b %e at %I:%M %P", localtime($time));
             $timeStr =~ s/12:00 am/midnight/;              $timeStr =~ s/12:00 am/00:00/;
             $timeStr =~ s/12:00 pm/noon/;              $timeStr =~ s/12:00 pm/noon/;
             return $timeStr;              return $timeStr;
         }          }
   
         # Not this year, so show the year          # Not this year, so show the year
         my $timeStr = strftime("on %A, %b %e %G at %I:%M %P", localtime($time));          my $timeStr = strftime("on %A, %b %e %Y at %I:%M %P", localtime($time));
         $timeStr =~ s/12:00 am/midnight/;          $timeStr =~ s/12:00 am/00:00/;
         $timeStr =~ s/12:00 pm/noon/;          $timeStr =~ s/12:00 pm/noon/;
         return $timeStr;          return $timeStr;
     }      }
Line 552  sub timeToHumanString { Line 686  sub timeToHumanString {
   
 =head1 NAME  =head1 NAME
   
 Apache::lonnavmap - Subroutines to handle and render the navigation maps  Apache::lonnavmap - Subroutines to handle and render the navigation
       maps
   
 =head1 SYNOPSIS  =head1 SYNOPSIS
   
Line 562  other modules. Line 697  other modules.
   
 =head1 OVERVIEW  =head1 OVERVIEW
   
 When a user enters a course, LON-CAPA examines the course structure  X<lonnavmaps, overview> When a user enters a course, LON-CAPA examines the
 and caches it in what is often referred to as the "big hash". You  course structure and caches it in what is often referred to as the
 can see it if you are logged into LON-CAPA, in a course, by going  "big hash" X<big hash>. You can see it if you are logged into
 to /adm/test. (You may need to tweak the /home/httpd/lonTabs/htpasswd  LON-CAPA, in a course, by going to /adm/test. (You may need to
 file to view it.) The content of the hash will be under the heading  tweak the /home/httpd/lonTabs/htpasswd file to view it.) The
 "Big Hash".  content of the hash will be under the heading "Big Hash".
   
 Big Hash contains, among other things, how resources are related  Big Hash contains, among other things, how resources are related
 to each other (next/previous), what resources are maps, which   to each other (next/previous), what resources are maps, which 
Line 679  can't close or open folders when this is Line 814  can't close or open folders when this is
   
 =back  =back
   
 =item B<Apache::lonnavmaps::communication_status>:  =item * B<Apache::lonnavmaps::communication_status>:
   
 Whether there is discussion on the resource, email for the user, or  Whether there is discussion on the resource, email for the user, or
 (lumped in here) perl errors in the execution of the problem. This is  (lumped in here) perl errors in the execution of the problem. This is
 the second column in the main nav map.  the second column in the main nav map.
   
 =item B<Apache::lonnavmaps::quick_status>:  =item * B<Apache::lonnavmaps::quick_status>:
   
 An icon for the status of a problem, with five possible states:  An icon for the status of a problem, with five possible states:
 Correct, incorrect, open, awaiting grading (for a problem where the  Correct, incorrect, open, awaiting grading (for a problem where the
Line 693  computer's grade is suppressed, or the c Line 828  computer's grade is suppressed, or the c
 essay problem), or none (not open yet, not a problem). The  essay problem), or none (not open yet, not a problem). The
 third column of the standard navmap.  third column of the standard navmap.
   
 =item B<Apache::lonnavmaps::long_status>:  =item * B<Apache::lonnavmaps::long_status>:
   
 A text readout of the details of the current status of the problem,  A text readout of the details of the current status of the problem,
 such as "Due in 22 hours". The fourth column of the standard navmap.  such as "Due in 22 hours". The fourth column of the standard navmap.
   
   =item * B<Apache::lonnavmaps::part_status_summary>:
   
   A text readout summarizing the status of the problem. If it is a
   single part problem, will display "Correct", "Incorrect", 
   "Not yet open", "Open", "Attempted", or "Error". If there are
   multiple parts, this will output a string that in HTML will show a
   status of how many parts are in each status, in color coding, trying
   to match the colors of the icons within reason.
   
   Note this only makes sense if you are I<not> showing parts. If 
   C<showParts> is true (see below), this column will not output
   anything. 
   
 =back  =back
   
 If you add any others please be sure to document them here.  If you add any others please be sure to document them here.
Line 716  to override vertical and horizontal alig Line 864  to override vertical and horizontal alig
   
 =head2 Parameters  =head2 Parameters
   
 Most of these parameters are only useful if you are *not* using the  Minimally, you should be
 folder interface (i.e., the default first column), which is probably  
 the common case. If you are using this interface, then you should be  
 able to get away with just using 'cols' (to specify the columns  able to get away with just using 'cols' (to specify the columns
 shown), 'url' (necessary for the folders to link to the current screen  shown), 'url' (necessary for the folders to link to the current screen
 correctly), and possibly 'queryString' if your app calls for it. In  correctly), and possibly 'queryString' if your app calls for it. In
Line 727  automatically. Line 873  automatically.
   
 =over 4  =over 4
   
 =item * B<iterator>: default: constructs one from %ENV  =item * B<iterator>: default: constructs one from %env
   
 A reference to a fresh ::iterator to use from the navmaps. The  A reference to a fresh ::iterator to use from the navmaps. The
 rendering will reflect the options passed to the iterator, so you can  rendering will reflect the options passed to the iterator, so you can
 use that to just render a certain part of the course, if you like. If  use that to just render a certain part of the course, if you like. If
 one is not passed, the renderer will attempt to construct one from  one is not passed, the renderer will attempt to construct one from
 ENV{'form.filter'} and ENV{'form.condition'} information, plus the  env{'form.filter'} and env{'form.condition'} information, plus the
 'iterator_map' parameter if any.  'iterator_map' parameter if any.
   
 =item * B<iterator_map>: default: not used  =item * B<iterator_map>: default: not used
Line 743  instruct the renderer to render only a p Line 889  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<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
 this is necessary to make an iterator but it is not passed in, a new  this is necessary to make an iterator but it is not passed in, a new
 one will be constructed based on ENV info. This is useful to do basic  one will be constructed based on env info. This is useful to do basic
 error checking before passing it off to render.  error checking before passing it off to render.
   
 =item * B<r>: default: must be passed in  =item * B<r>: default: must be passed in
Line 773  then only one line will be displayed for Line 919  then only one line will be displayed for
 all parts will always be displayed. If showParts is 0, this is  all parts will always be displayed. If showParts is 0, this is
 ignored.  ignored.
   
 =item * B<jumpCount>: default: determined from %ENV  =item * B<jumpCount>: default: determined from %env
   
 A string identifying the URL to place the anchor 'curloc' at.  A string identifying the URL to place the anchor 'curloc' at.
 It is the responsibility of the renderer user to  It is the responsibility of the renderer user to
 ensure that the #curloc is in the URL. By default, determined through  ensure that the #curloc is in the URL. By default, determined through
 the use of the ENV{} 'jump' information, and should normally "just  the use of the env{} 'jump' information, and should normally "just
 work" correctly.  work" correctly.
   
 =item * B<here>: default: empty string  =item * B<here>: default: empty string
Line 806  are allowing the user to open and close Line 952  are allowing the user to open and close
   
 Describes the currently-open row number to cause the browser to jump  Describes the currently-open row number to cause the browser to jump
 to, because the user just opened that folder. By default, pulled from  to, because the user just opened that folder. By default, pulled from
 the Jump information in the ENV{'form.*'}.  the Jump information in the env{'form.*'}.
   
 =item * B<printKey>: default: false  =item * B<printKey>: default: false
   
Line 867  sub resource { return 0; } Line 1013  sub resource { return 0; }
 sub communication_status { return 1; }  sub communication_status { return 1; }
 sub quick_status { return 2; }  sub quick_status { return 2; }
 sub long_status { return 3; }  sub long_status { return 3; }
   sub part_status_summary { return 4; }
 # Data for render_resource  
   
 sub render_resource {  sub render_resource {
     my ($resource, $part, $params) = @_;      my ($resource, $part, $params) = @_;
Line 876  sub render_resource { Line 1021  sub render_resource {
     my $nonLinkedText = ''; # stuff after resource title not in link      my $nonLinkedText = ''; # stuff after resource title not in link
   
     my $link = $params->{"resourceLink"};      my $link = $params->{"resourceLink"};
   
       #  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);
       $left =~ s/'/\\'/g;
       $link = $left.'?'.$right;
   
     my $src = $resource->src();      my $src = $resource->src();
     my $it = $params->{"iterator"};      my $it = $params->{"iterator"};
     my $filter = $it->{FILTER};      my $filter = $it->{FILTER};
   
     my $title = $resource->compTitle();      my $title = $resource->compTitle();
     if ($src =~ /^\/uploaded\//) {  
         $nonLinkedText=$title;  
         $title = '';  
     }  
     my $partLabel = "";      my $partLabel = "";
     my $newBranchText = "";      my $newBranchText = "";
           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='/adm/lonIcons/branch.gif' border='0' />";          $newBranchText = "<img src='$location/branch.gif' border='0' alt='Branch' />";
     }      }
   
     # links to open and close the folder      # links to open and close the folder
   
       
     my $linkopen = "<a href='$link'>";      my $linkopen = "<a href='$link'>";
   
   
     my $linkclose = "</a>";      my $linkclose = "</a>";
   
     # Default icon: unknown page      # Default icon: unknown page
     my $icon = "<img src='/adm/lonIcons/unknown.gif' alt='' border='0' />";      my $icon = "<img src='$location/unknown.gif' alt='' border='0' alt='&nbsp;&nbsp;' ' />";
           
     if ($resource->is_problem()) {      if ($resource->is_problem()) {
         if ($part eq '0' || $params->{'condensed'}) {          if ($part eq '0' || $params->{'condensed'}) {
             $icon = '<img src="/adm/lonIcons/problem.gif" alt="" border="0" />';              $icon ='<img src="'.$location.'/problem.gif" alt="&nbsp;&nbsp;" border="0" />';
         } else {          } else {
             $icon = $params->{'indentString'};              $icon = $params->{'indentString'};
         }          }
     } else {      } else {
  my $curfext= (split (/\./,$resource->src))[-1];   $icon = "<img src='".&Apache::loncommon::icon($resource->src)."' alt='&nbsp;&nbsp;' border='0' />";
  my $embstyle = &Apache::loncommon::fileembstyle($curfext);  
  # The unless conditional that follows is a bit of overkill  
  if (!(!defined($embstyle) || $embstyle eq 'unk' || $embstyle eq 'hdn')) {  
     $icon = "<img src='/adm/lonIcons/$curfext.gif' alt='' border='0' />";  
  }  
     }      }
   
     # Display the correct map icon to open or shut map      # Display the correct map icon to open or shut map
Line 927  sub render_resource { Line 1077  sub render_resource {
   
         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='/adm/lonIcons/$icon' alt='' border='0' />";      $icon = "<img src='$location/$icon' alt='".
    ($nowOpen ? 'Open Folder' : 'Close Folder')."' border='0' />";
   
             $linkopen = "<a href='" . $params->{'url'} . '?' .               $linkopen = "<a href='" . $params->{'url'} . '?' . 
                 $params->{'queryString'} . '&filter=';                  $params->{'queryString'} . '&filter=';
Line 940  sub render_resource { Line 1091  sub render_resource {
                 '&jump=' .                  '&jump=' .
                 &Apache::lonnet::escape($resource->symb()) .                   &Apache::lonnet::escape($resource->symb()) . 
                 "&folderManip=1'>";                  "&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') .
                 '.nomanip.gif';                  '.nomanip.gif';
             $icon = "<img src='/adm/lonIcons/$icon' alt='' border='0' />";              $icon = "<img src='$location/$icon' alt='".
    ($nowOpen ? 'Open Folder' : 'Close Folder')."' border='0' />";
   
             $linkopen = "";              $linkopen = "";
             $linkclose = "";              $linkclose = "";
Line 967  sub render_resource { Line 1120  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 982  sub render_resource { Line 1136  sub render_resource {
   
     if ($resource->is_problem() && $part ne '0' &&       if ($resource->is_problem() && $part ne '0' && 
         !$params->{'condensed'}) {          !$params->{'condensed'}) {
         $partLabel = " (Part $part)";   my $displaypart=$resource->part_display($part);
           $partLabel = " (Part: $displaypart)";
    if ($link!~/\#/) { $link.='#'.&Apache::lonnet::escape($part); }
         $title = "";          $title = "";
     }      }
   
Line 990  sub render_resource { Line 1146  sub render_resource {
         $nonLinkedText .= ' (' . $resource->countParts() . ' parts)';          $nonLinkedText .= ' (' . $resource->countParts() . ' parts)';
     }      }
   
     if (!$params->{'resource_nolink'} && $src !~ /^\/uploaded\// &&      my $target;
  !$resource->is_sequence()) {      if ($env{'environment.remotenavmap'} eq 'on') {
         $result .= "  $curMarkerBegin<a href='$link'>$title$partLabel</a>$curMarkerEnd $nonLinkedText</td>";   $target=' target="loncapaclient" ';
       }
       if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) {
           $result .= "  $curMarkerBegin<a $target href='$link'>$title$partLabel</a>$curMarkerEnd $nonLinkedText</td>";
     } else {      } else {
         $result .= "  $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText</td>";          $result .= "  $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText</td>";
     }      }
Line 1007  sub render_communication_status { Line 1166  sub render_communication_status {
     my $link = $params->{"resourceLink"};      my $link = $params->{"resourceLink"};
     my $linkopen = "<a href='$link'>";      my $linkopen = "<a href='$link'>";
     my $linkclose = "</a>";      my $linkclose = "</a>";
       my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");
     if ($resource->hasDiscussion()) {      if ($resource->hasDiscussion()) {
         $discussionHTML = $linkopen .          $discussionHTML = $linkopen .
             '<img border="0" src="/adm/lonMisc/chat.gif" />' .              '<img border="0" src="'.$location.'/chat.gif" />' .
             $linkclose;              $linkclose;
     }      }
           
Line 1020  sub render_communication_status { Line 1179  sub render_communication_status {
             if ($_) {              if ($_) {
                 $feedbackHTML .= '&nbsp;<a href="/adm/email?display='                  $feedbackHTML .= '&nbsp;<a href="/adm/email?display='
                     . &Apache::lonnet::escape($_) . '">'                      . &Apache::lonnet::escape($_) . '">'
                     . '<img src="/adm/lonMisc/feedback.gif" '                      . '<img src="'.$location.'/feedback.gif" '
                     . 'border="0" /></a>';                      . 'border="0" /></a>';
             }              }
         }          }
Line 1028  sub render_communication_status { Line 1187  sub render_communication_status {
           
     if ($resource->getErrors()) {      if ($resource->getErrors()) {
         my $errors = $resource->getErrors();          my $errors = $resource->getErrors();
           my $errorcount = 0;
         foreach (split(/,/, $errors)) {          foreach (split(/,/, $errors)) {
               last if ($errorcount>=10); # Only output 10 bombs maximum
             if ($_) {              if ($_) {
                   $errorcount++;
                 $errorHTML .= '&nbsp;<a href="/adm/email?display='                  $errorHTML .= '&nbsp;<a href="/adm/email?display='
                     . &Apache::lonnet::escape($_) . '">'                      . &Apache::lonnet::escape($_) . '">'
                     . '<img src="/adm/lonMisc/bomb.gif" '                      . '<img src="'.$location.'/bomb.gif" '
                     . 'border="0" /></a>';                      . 'border="0" /></a>';
             }              }
         }          }
Line 1057  sub render_quick_status { Line 1219  sub render_quick_status {
   
     if ($resource->is_problem() &&      if ($resource->is_problem() &&
         !$firstDisplayed) {          !$firstDisplayed) {
         my $icon = $statusIconMap{$resource->status($part)};  
           my $icon = $statusIconMap{$resource->simpleStatus($part)};
         my $alt = $iconAltTags{$icon};          my $alt = $iconAltTags{$icon};
         if ($icon) {          if ($icon) {
             $result .= "<td width='30' valign='center' width='50' align='right'>$linkopen<img width='25' height='25' src='/adm/lonIcons/$icon' border='0' alt='$alt' />$linkclose</td>\n";      my $location=
    &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";
         } else {          } else {
             $result .= "<td width='30'>&nbsp;</td>\n";              $result .= "<td width='30'>&nbsp;</td>\n";
         }          }
Line 1080  sub render_long_status { Line 1245  sub render_long_status {
     if ($resource->is_problem()) {      if ($resource->is_problem()) {
         $color = $colormap{$resource->status};          $color = $colormap{$resource->status};
                   
         if (dueInLessThen24Hours($resource, $part) ||          if (dueInLessThan24Hours($resource, $part) ||
             lastTry($resource, $part)) {              lastTry($resource, $part)) {
             $color = $hurryUpColor;              $color = $hurryUpColor;
         }          }
Line 1106  sub render_long_status { Line 1271  sub render_long_status {
     return $result;      return $result;
 }  }
   
   # Colors obtained by taking the icons, matching the colors, and
   # possibly reducing the Value (HSV) of the color, if it's too bright
   # for text, generally by one third or so.
   my %statusColors = 
       (
        $resObj->CLOSED => '#000000',
        $resObj->OPEN   => '#998b13',
        $resObj->CORRECT => '#26933f',
        $resObj->INCORRECT => '#c48207',
        $resObj->ATTEMPTED => '#a87510',
        $resObj->ERROR => '#000000'
        );
   my %statusStrings = 
       (
        $resObj->CLOSED => 'Not yet open',
        $resObj->OPEN   => 'Open',
        $resObj->CORRECT => 'Correct',
        $resObj->INCORRECT => 'Incorrect',
        $resObj->ATTEMPTED => 'Attempted',
        $resObj->ERROR => 'Network Error'
        );
   my @statuses = ($resObj->CORRECT, $resObj->ATTEMPTED, $resObj->INCORRECT, $resObj->OPEN, $resObj->CLOSED, $resObj->ERROR);
   
   use Data::Dumper;
   sub render_parts_summary_status {
       my ($resource, $part, $params) = @_;
       if (!$resource->is_problem() && !$resource->contains_problem) { return '<td></td>'; }
       if ($params->{showParts}) { 
    return '<td></td>';
       }
   
       my $td = "<td align='right'>\n";
       my $endtd = "</td>\n";
       my @probs;
   
       if ($resource->contains_problem) {
    @probs=$resource->retrieveResources($resource,sub { $_[0]->is_problem() },1,0);
       } else {
    @probs=($resource);
       }
       my $return;
       my %overallstatus;
       my $totalParts;
       foreach my $resource (@probs) {
    # If there is a single part, just show the simple status
    if ($resource->singlepart()) {
       my $status = $resource->simpleStatus(${$resource->parts}[0]);
       $overallstatus{$status}++;
       $totalParts++;
       next;
    }
    # Now we can be sure the $part doesn't really matter.
    my $statusCount = $resource->simpleStatusCount();
    my @counts;
    foreach my $status (@statuses) {
       # decouple display order from the simpleStatusCount order
       my $slot = Apache::lonnavmaps::resource::statusToSlot($status);
       if ($statusCount->[$slot]) {
    $overallstatus{$status}+=$statusCount->[$slot];
    $totalParts+=$statusCount->[$slot];
       }
    }
       }
       $return.= $td . $totalParts . ' parts: ';
       foreach my $status (@statuses) {
    if ($overallstatus{$status}) {
       $return.="<font color='" . $statusColors{$status} .
    "'>" . $overallstatus{$status} . ' '
    . $statusStrings{$status} . "</font>";
    }
       }
       $return.= $endtd;
       return $return;
   }
   
 my @preparedColumns = (\&render_resource, \&render_communication_status,  my @preparedColumns = (\&render_resource, \&render_communication_status,
                        \&render_quick_status, \&render_long_status);                         \&render_quick_status, \&render_long_status,
          \&render_parts_summary_status);
   
 sub setDefault {  sub setDefault {
     my ($val, $default) = @_;      my ($val, $default) = @_;
Line 1115  sub setDefault { Line 1356  sub setDefault {
     return $val;      return $val;
 }  }
   
   sub cmp_title {
       my ($atitle,$btitle) = (lc($_[0]->compTitle),lc($_[1]->compTitle));
       $atitle=~s/^\s*//;
       $btitle=~s/^\s*//;
       return $atitle cmp $btitle;
   }
   
 sub render {  sub render {
     my $args = shift;      my $args = shift;
     &Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});      &Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});
     my $result = '';      my $result = '';
   
     # Configure the renderer.      # Configure the renderer.
     my $cols = $args->{'cols'};      my $cols = $args->{'cols'};
     if (!defined($cols)) {      if (!defined($cols)) {
Line 1137  sub render { Line 1384  sub render {
     my $jump = $args->{'jump'};      my $jump = $args->{'jump'};
     my $here = $args->{'here'};      my $here = $args->{'here'};
     my $suppressNavmap = setDefault($args->{'suppressNavmap'}, 0);      my $suppressNavmap = setDefault($args->{'suppressNavmap'}, 0);
       my $closeAllPages = setDefault($args->{'closeAllPages'}, 0);
     my $currentJumpDelta = 2; # change this to change how many resources are displayed      my $currentJumpDelta = 2; # change this to change how many resources are displayed
                              # before the current resource when using #current                               # before the current resource when using #current
   
Line 1146  sub render { Line 1394  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 (split(/\,/, $env{"form.filter"})) {
         if ($_) {          if ($_) {
             $filterHash->{$_} = "1";              $filterHash->{$_} = "1";
         }          }
Line 1166  sub render { Line 1414  sub render {
     }      }
   
     my $condition = 0;      my $condition = 0;
     if ($ENV{'form.condition'}) {      if ($env{'form.condition'}) {
         $condition = 1;          $condition = 1;
     }      }
   
     if (!$ENV{'form.folderManip'} && !defined($args->{'iterator'})) {      if (!$env{'form.folderManip'} && !defined($args->{'iterator'})) {
         # 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();
                         $ENV{"request.course.fn"}.".db",  
                         $ENV{"request.course.fn"}."_parms.db", 1, 1);  
             $mustCloseNavMap = 1;              $mustCloseNavMap = 1;
         }          }
         $navmap->init();  
   
         # Step two: Locate what kind of here marker is necessary          # Step two: Locate what kind of here marker is necessary
         # Determine where the "here" marker is and where the screen jumps to.          # Determine where the "here" marker is and where the screen jumps to.
   
         if ($ENV{'form.postsymb'}) {          if ($env{'form.postsymb'} ne '') {
             $here = $jump = $ENV{'form.postsymb'};              $here = $jump = &Apache::lonnet::symbclean($env{'form.postsymb'});
         } elsif ($ENV{'form.postdata'}) {          } elsif ($env{'form.postdata'} ne '') {
             # couldn't find a symb, is there a URL?              # couldn't find a symb, is there a URL?
             my $currenturl = $ENV{'form.postdata'};              my $currenturl = $env{'form.postdata'};
             #$currenturl=~s/^http\:\/\///;              #$currenturl=~s/^http\:\/\///;
             #$currenturl=~s/^[^\/]+//;              #$currenturl=~s/^[^\/]+//;
                           
             $here = $jump = &Apache::lonnet::symbread($currenturl);              $here = $jump = &Apache::lonnet::symbread($currenturl);
         }   }
    if ($here eq '') {
       my $last;
       if (tie(my %hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db',
                       &GDBM_READER(),0640)) {
    $last=$hash{'last_known'};
    untie(%hash);
       }
       if ($last) { $here = $jump = $last; }
    }
   
         # Step three: Ensure the folders are open          # Step three: Ensure the folders are open
         my $mapIterator = $navmap->getIterator(undef, undef, undef, 1);          my $mapIterator = $navmap->getIterator(undef, undef, undef, 1);
         my $depth = 1;          my $curRes;
         $mapIterator->next(); # discard the first BEGIN_MAP  
         my $curRes = $mapIterator->next();  
         my $found = 0;          my $found = 0;
                   
         # We only need to do this if we need to open the maps to show the          # We only need to do this if we need to open the maps to show the
         # current position. This will change the counter so we can't count          # current position. This will change the counter so we can't count
         # for the jump marker with this loop.          # for the jump marker with this loop.
         while ($depth > 0 && !$found) {          while ($here && ($curRes = $mapIterator->next()) && !$found) {
             if ($curRes == $mapIterator->BEGIN_MAP()) { $depth++; }  
             if ($curRes == $mapIterator->END_MAP()) { $depth--; }  
               
             if (ref($curRes) && $curRes->symb() eq $here) {              if (ref($curRes) && $curRes->symb() eq $here) {
                 my $mapStack = $mapIterator->getStack();                  my $mapStack = $mapIterator->getStack();
                                   
Line 1221  sub render { Line 1470  sub render {
                 }                  }
                 $found = 1;                  $found = 1;
             }              }
               
             $curRes = $mapIterator->next();  
         }                      }            
     }              }        
   
     if ( !defined($args->{'iterator'}) && $ENV{'form.folderManip'} ) { # we came from a user's manipulation of the nav page      if ( !defined($args->{'iterator'}) && $env{'form.folderManip'} ) { # we came from a user's manipulation of the nav page
         # If this is a click on a folder or something, we want to preserve the "here"          # If this is a click on a folder or something, we want to preserve the "here"
         # from the querystring, and get the new "jump" marker          # from the querystring, and get the new "jump" marker
         $here = $ENV{'form.here'};          $here = $env{'form.here'};
         $jump = $ENV{'form.jump'};          $jump = $env{'form.jump'};
     }       } 
           
     my $it = $args->{'iterator'};      my $it = $args->{'iterator'};
     if (!defined($it)) {      if (!defined($it)) {
         # Construct a default iterator based on $ENV{'form.'} information          # Construct a default iterator based on $env{'form.'} information
                   
         # 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($r,               $navmap = Apache::lonnavmaps::navmap->new();
                         $ENV{"request.course.fn"}.".db",  
                         $ENV{"request.course.fn"}."_parms.db", 1, 1);  
             $mustCloseNavMap = 1;              $mustCloseNavMap = 1;
         }          }
         # Paranoia: Make sure it's ready  
         $navmap->init();  
   
         # See if we're being passed a specific map          # See if we're being passed a specific map
         if ($args->{'iterator_map'}) {          if ($args->{'iterator_map'}) {
Line 1259  sub render { Line 1502  sub render {
             $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition);              $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition);
         }          }
     }      }
       
     # (re-)Locate the jump point, if any      # (re-)Locate the jump point, if any
     # Note this does not take filtering or hidden into account... need      # Note this does not take filtering or hidden into account... need
     # to be fixed?      # to be fixed?
     my $mapIterator = $navmap->getIterator(undef, undef, $filterHash, 0);      my $mapIterator = $navmap->getIterator(undef, undef, $filterHash, 0);
     my $depth = 1;      my $curRes;
     $mapIterator->next();  
     my $curRes = $mapIterator->next();  
     my $foundJump = 0;      my $foundJump = 0;
     my $counter = 0;      my $counter = 0;
           
     while ($depth > 0 && !$foundJump) {      while (($curRes = $mapIterator->next()) && !$foundJump) {
         if ($curRes == $mapIterator->BEGIN_MAP()) { $depth++; }  
         if ($curRes == $mapIterator->END_MAP()) { $depth--; }  
         if (ref($curRes)) { $counter++; }          if (ref($curRes)) { $counter++; }
                   
         if (ref($curRes) && $jump eq $curRes->symb()) {          if (ref($curRes) && $jump eq $curRes->symb()) {
Line 1283  sub render { Line 1522  sub render {
             $args->{'currentJumpIndex'} = $counter;              $args->{'currentJumpIndex'} = $counter;
             $foundJump = 1;              $foundJump = 1;
         }          }
           
         $curRes = $mapIterator->next();  
     }      }
   
     my $showParts = setDefault($args->{'showParts'}, 1);      my $showParts = setDefault($args->{'showParts'}, 1);
Line 1295  sub render { Line 1532  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;          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");
         if ($navmap->{LAST_CHECK}) {          if ($navmap->{LAST_CHECK}) {
             $result .=               $result .= 
                 '<img src="/adm/lonMisc/chat.gif"> New discussion since '.                  '<img src="'.$location.'/chat.gif"> '.&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="/adm/lonMisc/feedback.gif"> New message (click to open)<p>'.                  '<img src="'.$location.'/feedback.gif"> '.&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="/adm/lonMisc/chat.gif"> Discussions</td><td align="center" valign="bottom">'.                  '<img src="'.$location.'/chat.gif"> '.&mt('Discussions').'</td><td align="center" valign="bottom">'.
                 '&nbsp;&nbsp;<img src="/adm/lonMisc/feedback.gif"> New message (click to open)'.                  '&nbsp;&nbsp;<img src="'.$location.'/feedback.gif"> '.&mt('New message (click to open)').
                 '</td>';                   '</td>'; 
         }          }
   
Line 1319  sub render { Line 1557  sub render {
     }      }
   
     if ($printCloseAll && !$args->{'resource_no_folder_link'}) {      if ($printCloseAll && !$args->{'resource_no_folder_link'}) {
    my ($link,$text);
         if ($condition) {          if ($condition) {
             $result.="<a href=\"navmaps?condition=0&filter=&$queryString" .      $link='"navmaps?condition=0&filter=&'.$queryString.
                 "&here=" . Apache::lonnet::escape($here) .   '&here='.&Apache::lonnet::escape($here).'"';
                 "\">Close All Folders</a>";      $text='Close All Folders';
           } else {
       $link='"navmaps?condition=1&filter=&'.$queryString.
    '&here='.&Apache::lonnet::escape($here).'"';
       $text='Open All Folders';
           }
    if ($args->{'caller'} eq 'navmapsdisplay') {
       &add_linkitem($args->{'linkitems'},'changefolder',
     'location.href='.$link,$text);
    } else {
       $result.='<a href='.$link.'>'.&mt($text).'</a>';
    }
           $result .= "\n";
       }
   
       # Check for any unread discussions in all resources.
       if ($args->{'caller'} eq 'navmapsdisplay') {
    &add_linkitem($args->{'linkitems'},'clearbubbles',
         'document.clearbubbles.submit()',
         'Mark all posts read');
    my $time=time;
    $result .= (<<END);
       <form name="clearbubbles" method="post" action="/adm/feedback">
    <input type="hidden" name="navurl" value="$ENV{'QUERY_STRING'}" />
    <input type="hidden" name="navtime" value="$time" />
   END
           if ($args->{'sort'} eq 'discussion') { 
       my $totdisc = 0;
       my $haveDisc = '';
       my @allres=$navmap->retrieveResources();
       foreach my $resource (@allres) {
    if ($resource->hasDiscussion()) {
       $haveDisc .= $resource->wrap_symb().':';
       $totdisc ++;
    }
       }
       if ($totdisc > 0) {
    $haveDisc =~ s/:$//;
    $result .= (<<END);
    <input type="hidden" name="navmaps" value="$haveDisc" />
       </form>
   END
               }
    }
    $result.='</form>';
       }
   
       if ($args->{'caller'} eq 'navmapsdisplay') {
           $result .= '<table><tr><td>'.
                      &Apache::loncommon::help_open_menu('','Navigation Screen','Navigation_Screen','',undef,'RAT').'</td>';
    if ($env{'environment.remotenavmap'} ne 'on') {
       $result .= '<td>&nbsp;</td>'; 
         } else {          } else {
             $result.="<a href=\"navmaps?condition=1&filter=&$queryString" .      $result .= '</tr><tr>'; 
                 "&here=" . Apache::lonnet::escape($here) .   
                 "\">Open All Folders</a>";  
         }          }
         $result .= "<br /><br />\n";   $result.=&show_linkitems($args->{'linkitems'});
     }              if ($args->{'sort_html'}) {
       if ($env{'environment.remotenavmap'} ne 'on') {
    $result.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>'.
       '<td align="right">'.$args->{'sort_html'}.'</td></tr>';
       } else {
    $result.='</tr><tr><td align="left"><br />'.
       $args->{'sort_html'}.'</td></tr>';
       }
    }
           $result .= '</table>';
       } elsif ($args->{'sort_html'}) { 
           $result.=$args->{'sort_html'}; 
       }
   
       $result .= "<br />\n";
     if ($r) {      if ($r) {
         $r->print($result);          $r->print($result);
         $r->rflush();          $r->rflush();
Line 1352  sub render { Line 1653  sub render {
     $args->{'indentLevel'} = 0;      $args->{'indentLevel'} = 0;
     $args->{'isNewBranch'} = 0;      $args->{'isNewBranch'} = 0;
     $args->{'condensed'} = 0;          $args->{'condensed'} = 0;    
     $args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='/adm/lonIcons/whitespace1.gif' width='25' height='1' alt='' border='0' />");      my $location=
    &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace1.gif");
       $args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='$location' width='25' height='1' alt='&nbsp;&nbsp;' border='0' />");
     $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 1362  sub render { Line 1665  sub render {
                                                          $it->{FIRST_RESOURCE},                                                           $it->{FIRST_RESOURCE},
                                                          $it->{FINISH_RESOURCE},                                                           $it->{FINISH_RESOURCE},
                                                          {}, undef, 1);                                                           {}, undef, 1);
         $depth = 0;          my $depth = 0;
         $dfsit->next();          $dfsit->next();
         my $curRes = $dfsit->next();          my $curRes = $dfsit->next();
         while ($depth > -1) {          while ($depth > -1) {
Line 1394  sub render { Line 1697  sub render {
   
     my $displayedJumpMarker = 0;      my $displayedJumpMarker = 0;
     # Set up iteration.      # Set up iteration.
     $depth = 1;  
     $it->next(); # discard initial BEGIN_MAP  
     $curRes = $it->next();  
     my $now = time();      my $now = time();
     my $in24Hours = $now + 24 * 60 * 60;      my $in24Hours = $now + 24 * 60 * 60;
     my $rownum = 0;      my $rownum = 0;
Line 1404  sub render { Line 1704  sub render {
     # export "here" marker information      # export "here" marker information
     $args->{'here'} = $here;      $args->{'here'} = $here;
   
     while ($depth > 0) {      $args->{'indentLevel'} = -1; # first BEGIN_MAP takes this to 0
         if ($curRes == $it->BEGIN_MAP()) { $depth++; }      my @resources;
         if ($curRes == $it->END_MAP()) { $depth--; }      my $code='';# sub { !(shift->is_map();) };
       if ($args->{'sort'} eq 'title') {
           my $oldFilterFunc = $filterFunc;
    my $filterFunc= 
       sub {
    my ($res)=@_;
    if ($res->is_map()) { return 0;}
    return &$oldFilterFunc($res);
       };
    @resources=$navmap->retrieveResources(undef,$filterFunc);
    @resources= sort { &cmp_title($a,$b) } @resources;
       } elsif ($args->{'sort'} eq 'duedate') {
    my $oldFilterFunc = $filterFunc;
    my $filterFunc= 
       sub {
    my ($res)=@_;
    if (!$res->is_problem()) { return 0;}
    return &$oldFilterFunc($res);
       };
    @resources=$navmap->retrieveResources(undef,$filterFunc);
    @resources= sort {
       if ($a->duedate ne $b->duedate) {
           return $a->duedate cmp $b->duedate;
       }
       my $value=&cmp_title($a,$b);
       return $value;
    } @resources;
       } elsif ($args->{'sort'} eq 'discussion') {
    my $oldFilterFunc = $filterFunc;
    my $filterFunc= 
       sub {
    my ($res)=@_;
    if (!$res->hasDiscussion() &&
       !$res->getFeedback() &&
       !$res->getErrors()) { return 0;}
    return &$oldFilterFunc($res);
       };
    @resources=$navmap->retrieveResources(undef,$filterFunc);
    @resources= sort { &cmp_title($a,$b) } @resources;
       } else {
    #unknow sort mechanism or default
    undef($args->{'sort'});
       }
   
   
       while (1) {
    if ($args->{'sort'}) {
       $curRes = shift(@resources);
    } else {
       $curRes = $it->next($closeAllPages);
    }
    if (!$curRes) { last; }
   
         # Maintain indentation level.          # Maintain indentation level.
         if ($curRes == $it->BEGIN_MAP() ||          if ($curRes == $it->BEGIN_MAP() ||
Line 1510  sub render { Line 1861  sub render {
             # Add part 0 so we display it correctly.              # Add part 0 so we display it correctly.
             unshift @parts, '0';              unshift @parts, '0';
         }          }
   
    {
       my ($src,$symb,$anchor,$stack);
       if ($args->{'sort'}) {
    my $it = $navmap->getIterator(undef, undef, undef, 1);
    while ( my $res=$it->next()) {
       if (ref($res) &&
    $res->symb() eq  $curRes->symb()) { last; }
    }
    $stack=$it->getStack();
       } else {
    $stack=$it->getStack();
       }
       ($src,$symb,$anchor)=getLinkForResource($stack);
       if (defined($anchor)) { $anchor='#'.$anchor; }
       my $srcHasQuestion = $src =~ /\?/;
       $args->{"resourceLink"} = $src.
    ($srcHasQuestion?'&':'?') .
    'symb=' . &Apache::lonnet::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) {
Line 1521  sub render { Line 1891  sub render {
   
             # 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};
             my $stack = $it->getStack();  
             my $src = getLinkForResource($stack);  
               
             my $srcHasQuestion = $src =~ /\?/;  
             $args->{"resourceLink"} = $src.  
                 ($srcHasQuestion?'&':'?') .  
                 'symb=' . &Apache::lonnet::escape($curRes->symb());  
               
             # Now, display each column.              # Now, display each column.
             foreach my $col (@$cols) {              foreach my $col (@$cols) {
                 my $colHTML = '';                  my $colHTML = '';
Line 1560  sub render { Line 1923  sub render {
             $r->rflush();              $r->rflush();
         }          }
     } continue {      } continue {
         $curRes = $it->next();  
   
  if ($r) {   if ($r) {
     # If we have the connection, make sure the user is still connected      # If we have the connection, make sure the user is still connected
     my $c = $r->connection;      my $c = $r->connection;
     if ($c->aborted()) {      if ($c->aborted()) {
  Apache::lonnet::logthis("navmaps aborted");  
  # Who cares what we do, nobody will see it anyhow.   # Who cares what we do, nobody will see it anyhow.
  return '';   return '';
     }      }
Line 1581  sub render { Line 1941  sub render {
     # 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 .= "<script>setTimeout(\"location += '#curloc';\", 0)</script>\n";          $result .= "
   <script>
   if (location.href.indexOf('#curloc')==-1) {
       setTimeout(\"location += '#curloc';\", 0)
   }
   </script>";
     }      }
   
     $result .= "</table>";      $result .= "</table>";
Line 1592  sub render { Line 1957  sub render {
         $r->rflush();          $r->rflush();
     }      }
                   
     if ($mustCloseNavMap) { $navmap->untieHashes(); }       return $result;
   }
   
   sub add_linkitem {
       my ($linkitems,$name,$cmd,$text)=@_;
       $$linkitems{$name}{'cmd'}=$cmd;
       $$linkitems{$name}{'text'}=&mt($text);
   }
   
   sub show_linkitems {
       my ($linkitems)=@_;
       my @linkorder = ("blank","launchnav","closenav","firsthomework",
        "everything","uncompleted","changefolder","clearbubbles");
       
       my $result .= (<<ENDBLOCK);
                 <td align="left">
   <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) {
    if (defined($linkitems->{$link})) {
       if ($linkitems->{$link}{'text'} ne '') {
    $result .= ' <option value="'.$link.'">'.
       $linkitems->{$link}{'text'}."</option>\n";
       }
    }
       }
       $result .= '</select>&nbsp;<input type="button" name="chgnav"
                      value="Go" onClick="javascript:changeNavDisplay()" />
                   </nobr></form></td>'."\n";
   
     return $result;      return $result;
 }  }
   
Line 1605  package Apache::lonnavmaps::navmap; Line 2008  package Apache::lonnavmaps::navmap;
   
 =head1 Object: Apache::lonnavmaps::navmap  =head1 Object: Apache::lonnavmaps::navmap
   
 You must obtain resource objects through the navmap object.  =head2 Overview
   
   The navmap object's job is to provide access to the resources
   in the course as Apache::lonnavmaps::resource objects, and to
   query and manage the relationship between those resource objects.
   
 =head2 Creation  Generally, you'll use the navmap object in one of three basic ways.
   In order of increasing complexity and power:
   
 =over 4  =over 4
   
 =item * B<new>(navHashFile, parmHashFile, genCourseAndUserOptions,  =item * C<$navmap-E<gt>getByX>, where X is B<Id>, B<Symb>, B<Url> or B<MapPc>. This provides
   genMailDiscussStatus, getUserData):      various ways to obtain resource objects, based on various identifiers.
       Use this when you want to request information about one object or 
       a handful of resources you already know the identities of, from some
       other source. For more about Ids, Symbs, and MapPcs, see the
       Resource documentation. Note that Url should be a B<last resort>,
       not your first choice; it only works when there is only one
       instance of the resource in the course, which only applies to
       maps, and even that may change in the future.
   
   =item * C<my @resources = $navmap-E<gt>retrieveResources(args)>. This
       retrieves resources matching some criterion and returns them
       in a flat array, with no structure information. Use this when
       you are manipulating a series of resources, based on what map
       the are in, but do not care about branching, or exactly how
       the maps and resources are related. This is the most common case.
   
   =item * C<$it = $navmap-E<gt>getIterator(args)>. This allows you traverse
       the course's navmap in various ways without writing the traversal
       code yourself. See iterator documentation below. Use this when
       you need to know absolutely everything about the course, including
       branches and the precise relationship between maps and resources.
   
 Binds a new navmap object to the compiled nav map hash and parm hash  =back
 given as filenames. genCourseAndUserOptions is a flag saying whether  
 the course options and user options hash should be generated. This is  =head2 Creation And Destruction
 for when you are using the parameters of the resources that require  
 them; see documentation in resource object  To create a navmap object, use the following function:
 documentation. genMailDiscussStatus causes the nav map to retreive  
 information about the email and discussion status of  =over 4
 resources. Returns the navmap object if this is successful, or  
 B<undef> if not. You must check for undef; errors will occur when you  =item * B<Apache::lonnavmaps::navmap-E<gt>new>():
 try to use the other methods otherwise. getUserData, if true, will   
 retreive the user's performance data for various problems.  Creates a new navmap object. Returns the navmap object if this is
   successful, or B<undef> if not.
   
 =back  =back
   
Line 1640  See iterator documentation below. Line 2069  See iterator documentation below.
   
 use strict;  use strict;
 use GDBM_File;  use GDBM_File;
   use Apache::lonnet;
   
 sub new {  sub new {
     # magic invocation to create a class instance      # magic invocation to create a class instance
Line 1647  sub new { Line 2077  sub new {
     my $class = ref($proto) || $proto;      my $class = ref($proto) || $proto;
     my $self = {};      my $self = {};
   
     $self->{NAV_HASH_FILE} = shift;  
     $self->{PARM_HASH_FILE} = shift;  
     $self->{GENERATE_COURSE_USER_OPT} = shift;  
     $self->{GENERATE_EMAIL_DISCUSS_STATUS} = shift;  
     $self->{GET_USER_DATA} = shift;  
   
     # 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 1665  sub new { Line 2089  sub new {
   
     my %navmaphash;      my %navmaphash;
     my %parmhash;      my %parmhash;
     if (!(tie(%navmaphash, 'GDBM_File', $self->{NAV_HASH_FILE},      my $courseFn = $env{"request.course.fn"};
       if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db",
               &GDBM_READER(), 0640))) {                &GDBM_READER(), 0640))) {
         return undef;          return undef;
     }      }
           
     if (!(tie(%parmhash, 'GDBM_File', $self->{PARM_HASH_FILE},      if (!(tie(%parmhash, 'GDBM_File', "${courseFn}_parms.db",
               &GDBM_READER(), 0640)))                &GDBM_READER(), 0640)))
     {      {
         untie %{$self->{PARM_HASH}};          untie %{$self->{PARM_HASH}};
Line 1679  sub new { Line 2104  sub new {
   
     $self->{NAV_HASH} = \%navmaphash;      $self->{NAV_HASH} = \%navmaphash;
     $self->{PARM_HASH} = \%parmhash;      $self->{PARM_HASH} = \%parmhash;
     $self->{INITED} = 0;      $self->{PARM_CACHE} = {};
   
     bless($self);      bless($self);
                   
     return $self;      return $self;
 }  }
   
 sub init {  sub generate_course_user_opt {
     my $self = shift;      my $self = shift;
     if ($self->{INITED}) { return; }      if ($self->{COURSE_USER_OPT_GENERATED}) { return; }
   
     # If the course opt hash and the user opt hash should be generated,      my $uname=$env{'user.name'};
     # generate them      my $udom=$env{'user.domain'};
     if ($self->{GENERATE_COURSE_USER_OPT}) {      my $cid=$env{'request.course.id'};
         my $uname=$ENV{'user.name'};      my $cdom=$env{'course.'.$cid.'.domain'};
         my $udom=$ENV{'user.domain'};      my $cnum=$env{'course.'.$cid.'.num'};
         my $uhome=$ENV{'user.home'};      
         my $cid=$ENV{'request.course.id'};  
         my $chome=$ENV{'course.'.$cid.'.home'};  
         my ($cdom,$cnum)=split(/\_/,$cid);  
           
         my $userprefix=$uname.'_'.$udom.'_';  
           
         my %courserdatas; my %useropt; my %courseopt; my %userrdatas;  
         unless ($uhome eq 'no_host') {   
 # ------------------------------------------------- Get coursedata (if present)  # ------------------------------------------------- Get coursedata (if present)
             unless ((time-$courserdatas{$cid.'.last_cache'})<240) {      my $courseopt=&Apache::lonnet::get_courseresdata($cnum,$cdom);
                 my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.      # Check for network failure
                                                  ':resourcedata',$chome);      if (!ref($courseopt)) {
                 # Check for network failure   if ( $courseopt =~ /no.such.host/i || $courseopt =~ /con_lost/i) {
                 if ( $reply =~ /no.such.host/i || $reply =~ /con_lost/i) {      $self->{NETWORK_FAILURE} = 1;
                     $self->{NETWORK_FAILURE} = 1;   }
                 } elsif ($reply!~/^error\:/) {   undef($courseopt);
                     $courserdatas{$cid}=$reply;      }
                     $courserdatas{$cid.'.last_cache'}=time;  
                 }  
             }  
             foreach (split(/\&/,$courserdatas{$cid})) {  
                 my ($name,$value)=split(/\=/,$_);  
                 $courseopt{$userprefix.&Apache::lonnet::unescape($name)}=  
                     &Apache::lonnet::unescape($value);  
             }  
 # --------------------------------------------------- Get userdata (if present)  
             unless ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) {  
                 my $reply=&Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome);  
                 if ($reply!~/^error\:/) {  
                     $userrdatas{$uname.'___'.$udom}=$reply;  
                     $userrdatas{$uname.'___'.$udom.'.last_cache'}=time;  
                 }  
                 # check to see if network failed  
                 elsif ( $reply=~/no.such.host/i || $reply=~/con.*lost/i )  
                 {  
                     $self->{NETWORK_FAILURE} = 1;  
                 }  
             }  
             foreach (split(/\&/,$userrdatas{$uname.'___'.$udom})) {  
                 my ($name,$value)=split(/\=/,$_);  
                 $useropt{$userprefix.&Apache::lonnet::unescape($name)}=  
                     &Apache::lonnet::unescape($value);  
             }  
             $self->{COURSE_OPT} = \%courseopt;  
             $self->{USER_OPT} = \%useropt;  
         }  
     }     
   
     if ($self->{GENERATE_EMAIL_DISCUSS_STATUS}) {  # --------------------------------------------------- Get userdata (if present)
         my $cid=$ENV{'request.course.id'};  
         my ($cdom,$cnum)=split(/\_/,$cid);      my $useropt=&Apache::lonnet::get_userresdata($uname,$udom);
               # Check for network failure
         my %emailstatus = &Apache::lonnet::dump('email_status');      if (!ref($useropt)) {
         my $logoutTime = $emailstatus{'logout'};   if ( $useropt =~ /no.such.host/i || $useropt =~ /con_lost/i) {
         my $courseLeaveTime = $emailstatus{'logout_'.$ENV{'request.course.id'}};      $self->{NETWORK_FAILURE} = 1;
         $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?   }
                                $courseLeaveTime : $logoutTime);   undef($useropt);
         my %discussiontime = &Apache::lonnet::dump('discussiontimes',   
                                                    $cdom, $cnum);  
         my %feedback=();  
         my %error=();  
         my $keys = &Apache::lonnet::reply('keys:'.  
                                           $ENV{'user.domain'}.':'.  
                                           $ENV{'user.name'}.':nohist_email',  
                                           $ENV{'user.home'});  
   
         foreach my $msgid (split(/\&/, $keys)) {  
             $msgid=&Apache::lonnet::unescape($msgid);  
             my $plain=&Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid));  
             if ($plain=~/(Error|Feedback) \[([^\]]+)\]/) {  
                 my ($what,$url)=($1,$2);  
                 my %status=  
                     &Apache::lonnet::get('email_status',[$msgid]);  
                 if ($status{$msgid}=~/^error\:/) {   
                     $status{$msgid}='';   
                 }  
                   
                 if (($status{$msgid} eq 'new') ||   
                     (!$status{$msgid})) {   
                     if ($what eq 'Error') {  
                         $error{$url}.=','.$msgid;   
                     } else {  
                         $feedback{$url}.=','.$msgid;  
                     }  
                 }  
             }  
         }  
           
         $self->{FEEDBACK} = \%feedback;  
         $self->{ERROR_MSG} = \%error; # what is this? JB  
         $self->{DISCUSSION_TIME} = \%discussiontime;  
         $self->{EMAIL_STATUS} = \%emailstatus;  
           
     }      }
   
     if ($self->{GET_USER_DATA}) {      $self->{COURSE_OPT} = $courseopt;
  # Retreive performance data on problems      $self->{USER_OPT} = $useropt;
  my %student_data = Apache::lonnet::currentdump($ENV{'request.course.id'},  
        $ENV{'user.domain'},      $self->{COURSE_USER_OPT_GENERATED} = 1;
        $ENV{'user.name'});      
  $self->{STUDENT_DATA} = \%student_data;      return;
   }
   
   sub generate_email_discuss_status {
       my $self = shift;
       my $symb = shift;
       if ($self->{EMAIL_DISCUSS_GENERATED}) { return; }
   
       my $cid=$env{'request.course.id'};
       my $cdom=$env{'course.'.$cid.'.domain'};
       my $cnum=$env{'course.'.$cid.'.num'};
       
       my %emailstatus = &Apache::lonnet::dump('email_status');
       my $logoutTime = $emailstatus{'logout'};
       my $courseLeaveTime = $emailstatus{'logout_'.$env{'request.course.id'}};
       $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?
      $courseLeaveTime : $logoutTime);
       my %discussiontime = &Apache::lonnet::dump('discussiontimes', 
          $cdom, $cnum);
       my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',
                                           $env{'user.domain'},$env{'user.name'},'lastread');
       my %lastreadtime = ();
       foreach (keys %lastread) {
           my $key = $_;
           $key =~ s/_lastread$//;
           $lastreadtime{$key} = $lastread{$_};
       }
   
       my %feedback=();
       my %error=();
       my @keys = &Apache::lonnet::getkeys('nohist_email',$env{'user.domain'},
    $env{'user.name'});
       
       foreach my $msgid (@keys) {
    if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {
       my $plain=
    &Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid));
       if ($plain=~/(Error|Feedback) \[([^\]]+)\]/) {
    my ($what,$url)=($1,$2);
    if ($what eq 'Error') {
       $error{$url}.=','.$msgid; 
    } else {
       $feedback{$url}.=','.$msgid;
    }
       }
    }
     }      }
       
       #url's of resources that have feedbacks
       $self->{FEEDBACK} = \%feedback;
       #or errors
       $self->{ERROR_MSG} = \%error;
       $self->{DISCUSSION_TIME} = \%discussiontime;
       $self->{EMAIL_STATUS} = \%emailstatus;
       $self->{LAST_READ} = \%lastreadtime;
       
       $self->{EMAIL_DISCUSS_GENERATED} = 1;
   }
   
     $self->{PARM_CACHE} = {};  sub get_user_data {
     $self->{INITED} = 1;      my $self = shift;
       if ($self->{RETRIEVED_USER_DATA}) { return; }
   
       # Retrieve performance data on problems
       my %student_data = Apache::lonnet::currentdump($env{'request.course.id'},
      $env{'user.domain'},
      $env{'user.name'});
       $self->{STUDENT_DATA} = \%student_data;
   
       $self->{RETRIEVED_USER_DATA} = 1;
 }  }
   
 # Internal function: Takes a key to look up in the nav hash and implements internal  # Internal function: Takes a key to look up in the nav hash and implements internal
Line 1810  sub navhash { Line 2226  sub navhash {
     return $self->{NAV_HASH}->{$key};      return $self->{NAV_HASH}->{$key};
 }  }
   
   =pod
   
   =item * B<courseMapDefined>(): Returns true if the course map is defined, 
       false otherwise. Undefined course maps indicate an error somewhere in
       LON-CAPA, and you will not be able to proceed with using the navmap.
       See the B<NAV> screen for an example of using this.
   
   =cut
   
 # Checks to see if coursemap is defined, matching test in old lonnavmaps  # Checks to see if coursemap is defined, matching test in old lonnavmaps
 sub courseMapDefined {  sub courseMapDefined {
     my $self = shift;      my $self = shift;
     my $uri = &Apache::lonnet::clutter($ENV{'request.course.uri'});      my $uri = &Apache::lonnet::clutter($env{'request.course.uri'});
   
     my $firstres = $self->navhash("map_start_$uri");      my $firstres = $self->navhash("map_start_$uri");
     my $lastres = $self->navhash("map_finish_$uri");      my $lastres = $self->navhash("map_finish_$uri");
Line 1823  sub courseMapDefined { Line 2248  sub courseMapDefined {
 sub getIterator {  sub getIterator {
     my $self = shift;      my $self = shift;
     my $iterator = Apache::lonnavmaps::iterator->new($self, shift, shift,      my $iterator = Apache::lonnavmaps::iterator->new($self, shift, shift,
                                                      shift, undef, shift);                                                       shift, undef, shift,
        shift, shift);
     return $iterator;      return $iterator;
 }  }
   
 # unties the hash when done  
 sub untieHashes {  
     my $self = shift;  
     untie %{$self->{NAV_HASH}};  
     untie %{$self->{PARM_HASH}};  
 }  
   
 # Private method: Does the given resource (as a symb string) have  # Private method: Does the given resource (as a symb string) have
 # current discussion? Returns 0 if chat/mail data not extracted.  # current discussion? Returns 0 if chat/mail data not extracted.
 sub hasDiscussion {  sub hasDiscussion {
     my $self = shift;      my $self = shift;
     my $symb = shift;      my $symb = shift;
       $self->generate_email_discuss_status();
   
     if (!defined($self->{DISCUSSION_TIME})) { return 0; }      if (!defined($self->{DISCUSSION_TIME})) { return 0; }
   
     #return defined($self->{DISCUSSION_TIME}->{$symb});      #return defined($self->{DISCUSSION_TIME}->{$symb});
     return $self->{DISCUSSION_TIME}->{$symb} >  
            $self->{LAST_CHECK};      # backward compatibility (bulletin boards used to be 'wrapped')
       my $ressymb = $self->wrap_symb($symb);
       if ( defined ( $self->{LAST_READ}->{$ressymb} ) ) {
           return $self->{DISCUSSION_TIME}->{$ressymb} > $self->{LAST_READ}->{$ressymb};
       } else {
   #        return $self->{DISCUSSION_TIME}->{$ressymb} >  $self->{LAST_CHECK}; # v.1.1 behavior 
           return $self->{DISCUSSION_TIME}->{$ressymb} >  0; # in 1.2 will display speech bubble icons for all items with posts until marked as read (even if read in v 1.1).
       }
   }
   
   sub wrap_symb {
       my $self = shift;
       my $symb = shift;
       if ($symb =~ m-___(adm/\w+/\w+/)(\d+)(/bulletinboard)$-) {
           unless ($symb =~ m|adm/wrapper/adm|) {
               $symb = 'bulletin___'.$2.'___adm/wrapper/'.$1.$2.$3;
           }
       }
       return $symb;
 }  }
   
 # 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.
   
 sub getFeedback {   sub getFeedback { 
     my $self = shift;      my $self = shift;
     my $symb = shift;      my $symb = shift;
   
       $self->generate_email_discuss_status();
   
     if (!defined($self->{FEEDBACK})) { return ""; }      if (!defined($self->{FEEDBACK})) { return ""; }
           
     return $self->{FEEDBACK}->{$symb};      return $self->{FEEDBACK}->{$symb};
Line 1862  sub getFeedback { Line 2304  sub getFeedback {
 sub getErrors {   sub getErrors { 
     my $self = shift;      my $self = shift;
     my $src = shift;      my $src = shift;
       
       $self->generate_email_discuss_status();
   
     if (!defined($self->{ERROR_MSG})) { return ""; }      if (!defined($self->{ERROR_MSG})) { return ""; }
     return $self->{ERROR_MSG}->{$src};      return $self->{ERROR_MSG}->{$src};
 }  }
Line 1891  the given map. This is one of the proper Line 2335  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 then we have to, which should hopefully alleviate speed problems.
 # Caching is just an incidental detail I throw in because it makes sense.  
   
 sub getById {  sub getById {
     my $self = shift;      my $self = shift;
Line 1911  sub getById { Line 2354  sub getById {
 sub getBySymb {  sub getBySymb {
     my $self = shift;      my $self = shift;
     my $symb = shift;      my $symb = shift;
     my ($mapUrl, $id, $filename) = split (/___/, $symb);  
       my ($mapUrl, $id, $filename) = &Apache::lonnet::decode_symb($symb);
     my $map = $self->getResourceByUrl($mapUrl);      my $map = $self->getResourceByUrl($mapUrl);
     return $self->getById($map->map_pc() . '.' . $id);      my $returnvalue = undef;
       if (ref($map)) {
           $returnvalue = $self->getById($map->map_pc() .'.'.$id);
       }
       return $returnvalue;
 }  }
   
 sub getByMapPc {  sub getByMapPc {
Line 1936  resource in the navmap. Line 2384  resource in the navmap.
 sub firstResource {  sub firstResource {
     my $self = shift;      my $self = shift;
     my $firstResource = $self->navhash('map_start_' .      my $firstResource = $self->navhash('map_start_' .
                      &Apache::lonnet::clutter($ENV{'request.course.uri'}));                       &Apache::lonnet::clutter($env{'request.course.uri'}));
     return $self->getById($firstResource);      return $self->getById($firstResource);
 }  }
   
Line 1952  in the navmap. Line 2400  in the navmap.
 sub finishResource {  sub finishResource {
     my $self = shift;      my $self = shift;
     my $firstResource = $self->navhash('map_finish_' .      my $firstResource = $self->navhash('map_finish_' .
                      &Apache::lonnet::clutter($ENV{'request.course.uri'}));                       &Apache::lonnet::clutter($env{'request.course.uri'}));
     return $self->getById($firstResource);      return $self->getById($firstResource);
 }  }
   
Line 1974  sub parmval { Line 2422  sub parmval {
   
 sub parmval_real {  sub parmval_real {
     my $self = shift;      my $self = shift;
     my ($what,$symb) = @_;      my ($what,$symb,$recurse) = @_;
   
     my $cid=$ENV{'request.course.id'};      # Make sure the {USER_OPT} and {COURSE_OPT} hashes are populated
     my $csec=$ENV{'request.course.sec'};      $self->generate_course_user_opt();
     my $uname=$ENV{'user.name'};  
     my $udom=$ENV{'user.domain'};      my $cid=$env{'request.course.id'};
       my $csec=$env{'request.course.sec'};
       my $uname=$env{'user.name'};
       my $udom=$env{'user.domain'};
   
     unless ($symb) { return ''; }      unless ($symb) { return ''; }
     my $result='';      my $result='';
   
     my ($mapname,$id,$fn)=split(/\_\_\_/,$symb);      my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
   
 # ----------------------------------------------------- Cascading lookup scheme  # ----------------------------------------------------- Cascading lookup scheme
     my $rwhat=$what;      my $rwhat=$what;
Line 1993  sub parmval_real { Line 2444  sub parmval_real {
   
     my $symbparm=$symb.'.'.$what;      my $symbparm=$symb.'.'.$what;
     my $mapparm=$mapname.'___(all).'.$what;      my $mapparm=$mapname.'___(all).'.$what;
     my $usercourseprefix=$uname.'_'.$udom.'_'.$cid;      my $usercourseprefix=$cid;
   
     my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what;      my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what;
     my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm;      my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm;
Line 2023  sub parmval_real { Line 2474  sub parmval_real {
   
     if (defined($courseopt)) {      if (defined($courseopt)) {
         if (defined($$courseopt{$courselevelr})) { return $$courseopt{$courselevelr}; }          if (defined($$courseopt{$courselevelr})) { return $$courseopt{$courselevelr}; }
         if (defined($$courseopt{$courselevelm})) { return $$courseopt{$courselevelm}; }  
         if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; }  
     }      }
   
 # ----------------------------------------------------- third, check map parms  # ----------------------------------------------------- third, check map parms
Line 2034  sub parmval_real { Line 2483  sub parmval_real {
   
 # ----------------------------------------------------- fourth , check default  # ----------------------------------------------------- fourth , check default
   
     my $default=&Apache::lonnet::metadata($fn,$rwhat.'.default');      my $meta_rwhat=$rwhat;
       $meta_rwhat=~s/\./_/g;
       my $default=&Apache::lonnet::metadata($fn,$meta_rwhat);
     if (defined($default)) { return $default}      if (defined($default)) { return $default}
       $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat);
       if (defined($default)) { return $default}
   
   # --------------------------------------------------- fifth, check more course
       if (defined($courseopt)) {
           if (defined($$courseopt{$courselevelm})) { return $$courseopt{$courselevelm}; }
           if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; }
       }
   
 # --------------------------------------------------- fifth , cascade up parts  # --------------------------------------------------- sixth , cascade up parts
   
     my ($space,@qualifier)=split(/\./,$rwhat);      my ($space,@qualifier)=split(/\./,$rwhat);
     my $qualifier=join('.',@qualifier);      my $qualifier=join('.',@qualifier);
Line 2046  sub parmval_real { Line 2505  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);   my $partgeneral=$self->parmval($part.".$qualifier",$symb,1);
  if (defined($partgeneral)) { return $partgeneral; }   if (defined($partgeneral)) { return $partgeneral; }
     }      }
       if ($recurse) { return undef; }
       my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$what);
       if (defined($pack_def)) { return $pack_def; }
     return '';      return '';
 }  }
   
Line 2063  you're not sure if $res is already an ob Line 2525  you're not sure if $res is already an ob
 resource appears multiple times in the course, only the first instance  resource appears multiple times in the course, only the first instance
 will be returned. As a result, this is probably useful only for maps.  will be returned. As a result, this is probably useful only for maps.
   
 =item * B<retrieveResources>(map, filterFunc, recursive, bailout):  =item * B<retrieveResources>(map, filterFunc, recursive, bailout, showall):
   
 The map is a specification of a map to retreive the resources from,  The map is a specification of a map to retreive the resources from,
 either as a url or as an object. The filterFunc is a reference to a  either as a url or as an object. The filterFunc is a reference to a
Line 2071  function that takes a resource object as Line 2533  function that takes a resource object as
 true if the resource should be included, or false if it should not  true if the resource should be included, or false if it should not
 be. If recursive is true, the map will be recursively examined,  be. If recursive is true, the map will be recursively examined,
 otherwise it will not be. If bailout is true, the function will return  otherwise it will not be. If bailout is true, the function will return
 as soon as it finds a resource, if false it will finish. By default,  as soon as it finds a resource, if false it will finish. If showall is
 the map is the top-level map of the course, filterFunc is a function  true it will not hide maps that contain nothing but one other map. By
 that always returns 1, recursive is true, bailout is false. The  default, the map is the top-level map of the course, filterFunc is a
 resources will be returned in a list containing the resource objects  function that always returns 1, recursive is true, bailout is false,
 for the corresponding resources, with B<no structure information> in  showall is false. The resources will be returned in a list containing
 the list; regardless of branching, recursion, etc., it will be a flat  the resource objects for the corresponding resources, with B<no
 list.  structure information> in the list; regardless of branching,
   recursion, etc., it will be a flat list.
   
 Thus, this is suitable for cases where you don't want the structure,  Thus, this is suitable for cases where you don't want the structure,
 just a list of all resources. It is also suitable for finding out how  just a list of all resources. It is also suitable for finding out how
Line 2086  want to know is if I<any> resources matc Line 2549  want to know is if I<any> resources matc
 parameter will allow you to avoid potentially expensive enumeration of  parameter will allow you to avoid potentially expensive enumeration of
 all matching resources.  all matching resources.
   
 =item * B<hasResources>(map, filterFunc, recursive):  =item * B<hasResource>(map, filterFunc, recursive, showall):
   
 Convience method for  Convience method for
   
  scalar(retrieveResources($map, $filterFunc, $recursive, 1)) > 0   scalar(retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0
   
 which will tell whether the map has resources matching the description  which will tell whether the map has resources matching the description
 in the filter function.  in the filter function.
   
 =cut  =cut
   
   
 sub getResourceByUrl {  sub getResourceByUrl {
     my $self = shift;      my $self = shift;
     my $resUrl = shift;      my $resUrl = shift;
Line 2123  sub retrieveResources { Line 2587  sub retrieveResources {
     if (!defined($recursive)) { $recursive = 1; }      if (!defined($recursive)) { $recursive = 1; }
     my $bailout = shift;      my $bailout = shift;
     if (!defined($bailout)) { $bailout = 0; }      if (!defined($bailout)) { $bailout = 0; }
       my $showall = shift;
     # Create the necessary iterator.      # Create the necessary iterator.
     if (!ref($map)) { # assume it's a url of a map.      if (!ref($map)) { # assume it's a url of a map.
         $map = $self->getResourceByUrl($map);          $map = $self->getResourceByUrl($map);
Line 2142  sub retrieveResources { Line 2606  sub retrieveResources {
   
     # Get an iterator.      # Get an iterator.
     my $it = $self->getIterator($map->map_start(), $map->map_finish(),      my $it = $self->getIterator($map->map_start(), $map->map_finish(),
                                 undef, $recursive);                                  undef, $recursive, $showall);
   
     my @resources = ();      my @resources = ();
   
     # Run down the iterator and collect the resources.      # Run down the iterator and collect the resources.
     my $depth = 1;      my $curRes;
     $it->next();  
     my $curRes = $it->next();      while ($curRes = $it->next()) {
   
     while ($depth > 0) {  
         if ($curRes == $it->BEGIN_MAP()) {  
             $depth++;  
         }  
         if ($curRes == $it->END_MAP()) {  
             $depth--;  
         }  
           
         if (ref($curRes)) {          if (ref($curRes)) {
             if (!&$filterFunc($curRes)) {              if (!&$filterFunc($curRes)) {
                 next;                  next;
Line 2171  sub retrieveResources { Line 2626  sub retrieveResources {
             }              }
         }          }
   
     } continue {  
         $curRes = $it->next();  
     }      }
   
     return @resources;      return @resources;
Line 2183  sub hasResource { Line 2636  sub hasResource {
     my $map = shift;      my $map = shift;
     my $filterFunc = shift;      my $filterFunc = shift;
     my $recursive = shift;      my $recursive = shift;
       my $showall = shift;
           
     return scalar($self->retrieveResources($map, $filterFunc, $recursive, 1)) > 0;      return scalar($self->retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0;
 }  }
   
 1;  1;
   
 package Apache::lonnavmaps::iterator;  package Apache::lonnavmaps::iterator;
   use WeakRef;
   use Apache::lonnet;
   
 =pod  =pod
   
Line 2248  new branch. The possible tokens are: Line 2704  new branch. The possible tokens are:
   
 =over 4  =over 4
   
 =item * BEGIN_MAP:  =item * B<END_ITERATOR>:
   
   The iterator has returned all that it's going to. Further calls to the
   iterator will just produce more of these. This is a "false" value, and
   is the only false value the iterator which will be returned, so it can
   be used as a loop sentinel.
   
   =item * B<BEGIN_MAP>:
   
 A new map is being recursed into. This is returned I<after> the map  A new map is being recursed into. This is returned I<after> the map
 resource itself is returned.  resource itself is returned.
   
 =item * END_MAP:  =item * B<END_MAP>:
   
 The map is now done.  The map is now done.
   
 =item * BEGIN_BRANCH:  =item * B<BEGIN_BRANCH>:
   
 A branch is now starting. The next resource returned will be the first  A branch is now starting. The next resource returned will be the first
 in that branch.  in that branch.
   
 =item * END_BRANCH:  =item * B<END_BRANCH>:
   
 The branch is now done.  The branch is now done.
   
Line 2280  but only one resource will be returned. Line 2743  but only one resource will be returned.
   
 =back  =back
   
   =head2 Normal Usage
   
   Normal usage of the iterator object is to do the following:
   
    my $it = $navmap->getIterator([your params here]);
    my $curRes;
    while ($curRes = $it->next()) {
      [your logic here]
    }
   
   Note that inside of the loop, it's frequently useful to check if
   "$curRes" is a reference or not with the reference function; only
   resource objects will be references, and any non-references will 
   be the tokens described above.
   
   Also note there is some old code floating around that trys to track
   the depth of the iterator to see when it's done; do not copy that 
   code. It is difficult to get right and harder to understand then
   this. They should be migrated to this new style.
   
 =cut  =cut
   
 # Here are the tokens for the iterator:  # Here are the tokens for the iterator:
   
   sub END_ITERATOR { return 0; }
 sub BEGIN_MAP { return 1; }    # begining of a new map  sub BEGIN_MAP { return 1; }    # begining of a new map
 sub END_MAP { return 2; }      # end of the map  sub END_MAP { return 2; }      # end of the map
 sub BEGIN_BRANCH { return 3; } # beginning of a branch  sub BEGIN_BRANCH { return 3; } # beginning of a branch
Line 2302  sub new { Line 2786  sub new {
     my $class = ref($proto) || $proto;      my $class = ref($proto) || $proto;
     my $self = {};      my $self = {};
   
     $self->{NAV_MAP} = shift;      weaken($self->{NAV_MAP} = shift);
     return undef unless ($self->{NAV_MAP});      return undef unless ($self->{NAV_MAP});
   
     # Handle the parameters      # Handle the parameters
Line 2369  sub new { Line 2853  sub new {
           
         # prime the recursion          # prime the recursion
         $self->{$firstResourceName}->{DATA}->{$valName} = 0;          $self->{$firstResourceName}->{DATA}->{$valName} = 0;
         my $depth = 0;   $iterator->next();
         $iterator->next();  
         my $curRes = $iterator->next();          my $curRes = $iterator->next();
         while ($depth > -1) {   my $depth = 1;
             if ($curRes == $iterator->BEGIN_MAP()) { $depth++; }          while ($depth > 0) {
             if ($curRes == $iterator->END_MAP()) { $depth--; }      if ($curRes == $iterator->BEGIN_MAP()) { $depth++; }
               if ($curRes == $iterator->END_MAP()) { $depth--; }
   
             if (ref($curRes)) {              if (ref($curRes)) {
                 # If there's only one resource, this will save it                  # If there's only one resource, this will save it
                 # we have to filter empty resources from consideration here,                  # we have to filter empty resources from consideration here,
Line 2409  sub new { Line 2893  sub new {
                 $curRes->{DATA}->{DISPLAY_DEPTH} = $finalDepth;                  $curRes->{DATA}->{DISPLAY_DEPTH} = $finalDepth;
                 if ($finalDepth > $maxDepth) {$maxDepth = $finalDepth;}                  if ($finalDepth > $maxDepth) {$maxDepth = $finalDepth;}
             }              }
         } continue {  
             $curRes = $iterator->next();      $curRes = $iterator->next();
         }          }
     }      }
   
     # Check: Was this only one resource, a map?      # Check: Was this only one resource, a map?
     if ($resourceCount == 1 && $resource->is_map() && !$self->{FORCE_TOP}) {       if ($resourceCount == 1 && $resource->is_sequence() && !$self->{FORCE_TOP}) { 
         my $firstResource = $resource->map_start();          my $firstResource = $resource->map_start();
         my $finishResource = $resource->map_finish();          my $finishResource = $resource->map_finish();
         return           return 
             Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,              Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
                                               $finishResource, $self->{FILTER},                                                $finishResource, $self->{FILTER},
                                               $self->{ALREADY_SEEN},                                                 $self->{ALREADY_SEEN}, 
                                               $self->{CONDITION}, 0);                                                $self->{CONDITION},
         $self->{FORCE_TOP});
                   
     }      }
   
Line 2431  sub new { Line 2916  sub new {
     $self->{MAX_DEPTH} = $maxDepth;      $self->{MAX_DEPTH} = $maxDepth;
     $self->{STACK} = [];      $self->{STACK} = [];
     $self->{RECURSIVE_ITERATOR_FLAG} = 0;      $self->{RECURSIVE_ITERATOR_FLAG} = 0;
       $self->{FINISHED} = 0; # When true, the iterator has finished
   
     for (my $i = 0; $i <= $self->{MAX_DEPTH}; $i++) {      for (my $i = 0; $i <= $self->{MAX_DEPTH}; $i++) {
         push @{$self->{STACK}}, [];          push @{$self->{STACK}}, [];
Line 2447  sub new { Line 2933  sub new {
   
 sub next {  sub next {
     my $self = shift;      my $self = shift;
       my $closeAllPages=shift;
       if ($self->{FINISHED}) {
    return END_ITERATOR();
       }
   
     # If we want to return the top-level map object, and haven't yet,      # If we want to return the top-level map object, and haven't yet,
     # do so.      # do so.
Line 2457  sub next { Line 2947  sub next {
   
     if ($self->{RECURSIVE_ITERATOR_FLAG}) {      if ($self->{RECURSIVE_ITERATOR_FLAG}) {
         # grab the next from the recursive iterator           # grab the next from the recursive iterator 
         my $next = $self->{RECURSIVE_ITERATOR}->next();          my $next = $self->{RECURSIVE_ITERATOR}->next($closeAllPages);
   
         # is it a begin or end map? If so, update the depth          # is it a begin or end map? If so, update the depth
         if ($next == BEGIN_MAP() ) { $self->{RECURSIVE_DEPTH}++; }          if ($next == BEGIN_MAP() ) { $self->{RECURSIVE_DEPTH}++; }
Line 2507  sub next { Line 2997  sub next {
             $self->{CURRENT_DEPTH}--;              $self->{CURRENT_DEPTH}--;
             return END_BRANCH();              return END_BRANCH();
         } else {          } else {
       $self->{FINISHED} = 1;
             return END_MAP();              return END_MAP();
         }          }
     }      }
Line 2570  sub next { Line 3061  sub next {
   
     # That ends the main iterator logic. Now, do we want to recurse      # That ends the main iterator logic. Now, do we want to recurse
     # down this map (if this resource is a map)?      # down this map (if this resource is a map)?
     if ($self->{HERE}->is_map() &&      if ( ($self->{HERE}->is_sequence() || (!$closeAllPages && $self->{HERE}->is_page())) &&
         (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) {          (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) {
         $self->{RECURSIVE_ITERATOR_FLAG} = 1;          $self->{RECURSIVE_ITERATOR_FLAG} = 1;
         my $firstResource = $self->{HERE}->map_start();          my $firstResource = $self->{HERE}->map_start();
Line 2579  sub next { Line 3070  sub next {
         $self->{RECURSIVE_ITERATOR} =           $self->{RECURSIVE_ITERATOR} = 
             Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,              Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
                                               $finishResource, $self->{FILTER},                                                $finishResource, $self->{FILTER},
                                               $self->{ALREADY_SEEN}, $self->{CONDITION});                                                $self->{ALREADY_SEEN},
         $self->{CONDITION},
         $self->{FORCE_TOP});
     }      }
   
     # If this is a blank resource, don't actually return it.      # If this is a blank resource, don't actually return it.
Line 2588  sub next { Line 3081  sub next {
     my $browsePriv = $self->{HERE}->browsePriv();      my $browsePriv = $self->{HERE}->browsePriv();
     if (!$self->{HERE}->src() ||       if (!$self->{HERE}->src() || 
         (!($browsePriv eq 'F') && !($browsePriv eq '2')) ) {          (!($browsePriv eq 'F') && !($browsePriv eq '2')) ) {
         return $self->next();          return $self->next($closeAllPages);
     }      }
   
     return $self->{HERE};      return $self->{HERE};
Line 2632  sub populateStack { Line 3125  sub populateStack {
 1;  1;
   
 package Apache::lonnavmaps::DFSiterator;  package Apache::lonnavmaps::DFSiterator;
   use WeakRef;
   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
 #  through the nav map and presents the resources in a depth-first search  #  through the nav map and presents the resources in a depth-first search
Line 2640  package Apache::lonnavmaps::DFSiterator; Line 3135  package Apache::lonnavmaps::DFSiterator;
 #  useful for pre-processing of some kind, and is in fact used by the main  #  useful for pre-processing of some kind, and is in fact used by the main
 #  iterator that way, but that's about it.  #  iterator that way, but that's about it.
 # One could imagine merging this into the init routine of the main iterator,  # One could imagine merging this into the init routine of the main iterator,
 #  but this might as well be left seperate, since it is possible some other  #  but this might as well be left separate, since it is possible some other
 #  use might be found for it. - Jeremy  #  use might be found for it. - Jeremy
   
 # Unlike the main iterator, this DOES return all resources, even blank ones.  # Unlike the main iterator, this DOES return all resources, even blank ones.
Line 2660  sub new { Line 3155  sub new {
     my $class = ref($proto) || $proto;      my $class = ref($proto) || $proto;
     my $self = {};      my $self = {};
   
     $self->{NAV_MAP} = shift;      weaken($self->{NAV_MAP} = shift);
     return undef unless ($self->{NAV_MAP});      return undef unless ($self->{NAV_MAP});
   
     $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();      $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();
Line 2814  sub populateStack { Line 3309  sub populateStack {
 1;  1;
   
 package Apache::lonnavmaps::resource;  package Apache::lonnavmaps::resource;
   use WeakRef;
 use Apache::lonnet;  use Apache::lonnet;
   
 =pod  =pod
   
 =head1 Object: resource  =head1 Object: resource 
   
   X<resource, navmap object>
 A resource object encapsulates a resource in a resource map, allowing  A resource object encapsulates a resource in a resource map, allowing
 easy manipulation of the resource, querying the properties of the  easy manipulation of the resource, querying the properties of the
 resource (including user properties), and represents a reference that  resource (including user properties), and represents a reference that
Line 2840  writing, there is no way to override thi Line 3336  writing, there is no way to override thi
 parts will never be returned, nor will their response types or ids be  parts will never be returned, nor will their response types or ids be
 stored.  stored.
   
 =head2 Public Members  =head2 Overview
   
 resource objects have a hash called DATA ($resourceRef->{DATA}) that  A B<Resource> is the most granular type of object in LON-CAPA that can
 you can store whatever you want in. This allows you to easily do  be included in a course. It can either be a particular resource, like
 two-pass algorithms without worrying about managing your own  an HTML page, external resource, problem, etc., or it can be a
 resource->data hash.  container sequence, such as a "page" or a "map".
   
 =head2 Methods  To see a sequence from the user's point of view, please see the
   B<Creating a Course: Maps and Sequences> chapter of the Author's
 =over 4  Manual.
   
 =item * B<new>($navmapRef, $idString):  A Resource Object, once obtained from a navmap object via a B<getBy*>
   method of the navmap, or from an iterator, allows you to query
 The first arg is a reference to the parent navmap object. The second  information about that resource.
 is the idString of the resource itself. Very rarely, if ever, called  
 directly. Use the nav map->getByID() method.  Generally, you do not ever want to create a resource object yourself,
   so creation has been left undocumented. Always retrieve resources
 =back  from navmap objects.
   
   =head3 Identifying Resources
   
   X<big hash>Every resource is identified by a Resource ID in the big hash that is
   unique to that resource for a given course. X<resource ID, in big hash>
   The Resource ID has the form #.#, where the first number is the same
   for every resource in a map, and the second is unique. For instance,
   for a course laid out like this:
   
    * Problem 1
    * Map
      * Resource 2
      * Resource 3
   
   C<Problem 1> and C<Map> will share a first number, and C<Resource 2>
   C<Resource 3> will share a first number. The second number may end up
   re-used between the two groups.
   
   The resource ID is only used in the big hash, but can be used in the
   context of a course to identify a resource easily. (For instance, the
   printing system uses it to record which resources from a sequence you 
   wish to print.)
   
   X<symb> X<resource, symb>
   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
   carries along with it the URL of the resource, and the map it appears
   in. Symbs are much larger then resource IDs.
   
 =cut  =cut
   
Line 2867  sub new { Line 3391  sub new {
     my $class = ref($proto) || $proto;      my $class = ref($proto) || $proto;
     my $self = {};      my $self = {};
   
     $self->{NAV_MAP} = shift;      weaken($self->{NAV_MAP} = shift);
     $self->{ID} = shift;      $self->{ID} = shift;
   
     # Store this new resource in the parent nav map's cache.      # Store this new resource in the parent nav map's cache.
Line 2896  sub navHash { Line 3420  sub navHash {
   
 =pod  =pod
   
 B<Metadata Retreival>  =head2 Methods
   
   Once you have a resource object, here's what you can do with it:
   
   =head3 Attribute Retrieval
   
 These are methods that help you retrieve metadata about the resource:  Every resource has certain attributes that can be retrieved and used:
 Method names are based on the fields in the compiled course  
 representation.  
   
 =over 4  =over 4
   
   =item * B<ID>: Every resource has an ID that is unique for that
       resource in the course it is in. The ID is actually in the hash
       representing the resource, so for a resource object $res, obtain
       it via C<$res->{ID}).
   
 =item * B<compTitle>:  =item * B<compTitle>:
   
 Returns a "composite title", that is equal to $res->title() if the  Returns a "composite title", that is equal to $res->title() if the
Line 2914  resource has a title, and is otherwise t Line 3445  resource has a title, and is otherwise t
   
 Returns true if the resource is external.  Returns true if the resource is external.
   
 =item * B<goesto>:  
   
 Returns the "goesto" value from the compiled nav map. (It is likely  
 you want to use B<getNext> instead.)  
   
 =item * B<kind>:  =item * B<kind>:
   
 Returns the kind of the resource from the compiled nav map.  Returns the kind of the resource from the compiled nav map.
Line 2946  Returns the symb for the resource. Line 3472  Returns the symb for the resource.
   
 Returns the title of the resource.  Returns the title of the resource.
   
 =item * B<to>:  
   
 Returns the "to" value from the compiled nav map. (It is likely you  
 want to use B<getNext> instead.)  
   
 =back  =back
   
 =cut  =cut
Line 2958  want to use B<getNext> instead.) Line 3479  want to use B<getNext> instead.)
 # These info functions can be used directly, as they don't return  # These info functions can be used directly, as they don't return
 # resource information.  # resource information.
 sub comesfrom { my $self=shift; return $self->navHash("comesfrom_", 1); }  sub comesfrom { my $self=shift; return $self->navHash("comesfrom_", 1); }
   sub encrypted { my $self=shift; return $self->navHash("encrypted_", 1); }
 sub ext { my $self=shift; return $self->navHash("ext_", 1) eq 'true:'; }  sub ext { my $self=shift; return $self->navHash("ext_", 1) eq 'true:'; }
 sub from { my $self=shift; return $self->navHash("from_", 1); }  sub from { my $self=shift; return $self->navHash("from_", 1); }
   # considered private and undocumented
 sub goesto { my $self=shift; return $self->navHash("goesto_", 1); }  sub goesto { my $self=shift; return $self->navHash("goesto_", 1); }
 sub kind { my $self=shift; return $self->navHash("kind_", 1); }  sub kind { my $self=shift; return $self->navHash("kind_", 1); }
 sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }  sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }
Line 2968  sub randompick { Line 3491  sub randompick {
     return $self->{NAV_MAP}->{PARM_HASH}->{$self->symb .      return $self->{NAV_MAP}->{PARM_HASH}->{$self->symb .
                                                '.0.parameter_randompick'};                                                 '.0.parameter_randompick'};
 }  }
   sub link {
       my $self=shift;
       if ($self->encrypted()) { return &Apache::lonenc::encrypted($self->src); }
       return $self->src;
   }
 sub src {   sub src { 
     my $self=shift;      my $self=shift;
     return $self->navHash("src_", 1);      return $self->navHash("src_", 1);
 }  }
   sub shown_symb {
       my $self=shift;
       if ($self->encrypted()) {return &Apache::lonenc::encrypted($self->symb());}
       return $self->symb();
   }
   sub id {
       my $self=shift;
       return $self->{ID};
   }
   sub enclosing_map_src {
       my $self=shift;
       (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;
       return $self->navHash('map_id_'.$first);
   }
 sub symb {  sub symb {
     my $self=shift;      my $self=shift;
     (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;      (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;
     my $symbSrc = &Apache::lonnet::declutter($self->src());      my $symbSrc = &Apache::lonnet::declutter($self->src());
     return &Apache::lonnet::declutter(      my $symb = &Apache::lonnet::declutter($self->navHash('map_id_'.$first)) 
          $self->navHash('map_id_'.$first))   
         . '___' . $second . '___' . $symbSrc;          . '___' . $second . '___' . $symbSrc;
       return &Apache::lonnet::symbclean($symb);
   }
   sub wrap_symb {
       my $self = shift;
       return $self->{NAV_MAP}->wrap_symb($self->symb());
 }  }
 sub title {   sub title { 
     my $self=shift;       my $self=shift; 
     if ($self->{ID} eq '0.0') {      if ($self->{ID} eq '0.0') {
  # If this is the top-level map, return the title of the course   # If this is the top-level map, return the title of the course
  # since this map can not be titled otherwise.   # since this map can not be titled otherwise.
  return $ENV{'course.'.$ENV{'request.course.id'}.'.description'};   return $env{'course.'.$env{'request.course.id'}.'.description'};
     }      }
     return $self->navHash("title_", 1); }      return $self->navHash("title_", 1); }
   # considered private and undocumented
 sub to { my $self=shift; return $self->navHash("to_", 1); }  sub to { my $self=shift; return $self->navHash("to_", 1); }
   sub condition {
       my $self=shift;
       my $undercond=$self->navHash("undercond_", 1);
       if (!defined($undercond)) { return 1; };
       my $condid=$self->navHash("condid_$undercond");
       if (!defined($condid)) { return 1; };
       my $condition=&Apache::lonnet::directcondval($condid);
       return $condition;
   }
   
 sub compTitle {  sub compTitle {
     my $self = shift;      my $self = shift;
     my $title = $self->title();      my $title = $self->title();
Line 3029  Returns true if the resource is a sequen Line 3586  Returns true if the resource is a sequen
   
 =cut  =cut
   
   sub hasResource {
      my $self = shift;
      return $self->{NAV_MAP}->hasResource(@_);
   }
   
   sub retrieveResources {
      my $self = shift;
      return $self->{NAV_MAP}->retrieveResources(@_);
   }
   
 sub is_html {  sub is_html {
     my $self=shift;      my $self=shift;
Line 3045  sub is_page { Line 3611  sub is_page {
 sub is_problem {  sub is_problem {
     my $self=shift;      my $self=shift;
     my $src = $self->src();      my $src = $self->src();
     return ($src =~ /problem$/);      return ($src =~ /\.(problem|exam|quiz|assess|survey|form|library)$/)
   }
   sub contains_problem {
       my $self=shift;
       if ($self->is_page()) {
    my $hasProblem=$self->hasResource($self,sub { $_[0]->is_problem() },1);
    return $hasProblem;
       }
       return 0;
 }  }
 sub is_sequence {  sub is_sequence {
     my $self=shift;      my $self=shift;
Line 3053  sub is_sequence { Line 3627  sub is_sequence {
     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 {
       my $self = shift();
       my $part = shift();
       if ($self->parmval('type',$part) eq 'survey') {
           return 1;
       }
       if ($self->src() =~ /\.(survey)$/) {
           return 1;
       }
       return 0;
   }
   
   sub is_empty_sequence {
       my $self=shift;
       my $src = $self->src();
       return !$self->is_page() && $self->navHash("is_map_", 1) && !$self->navHash("map_type_" . $self->map_pc());
   }
   
 # Private method: Shells out to the parmval in the nav map, handler parts.  # Private method: Shells out to the parmval in the nav map, handler parts.
 sub parmval {  sub parmval {
Line 3223  sub answerdate { Line 3814  sub answerdate {
 }  }
 sub awarded {   sub awarded { 
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
       $self->{NAV_MAP}->get_user_data();
     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 duedate {  sub duedate {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
       my $interval=$self->parmval("interval", $part);
       if ($interval) {
    my $first_access=&Apache::lonnet::get_first_access('map',$self->symb);
    if ($first_access) { return ($first_access+$interval); }
       }
     return $self->parmval("duedate", $part);      return $self->parmval("duedate", $part);
 }  }
 sub maxtries {  sub maxtries {
Line 3244  sub opendate { Line 3841  sub opendate {
 }  }
 sub problemstatus {  sub problemstatus {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("problemstatus", $part);      return lc $self->parmval("problemstatus", $part);
 }  }
 sub sig {  sub sig {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
Line 3268  sub weight { Line 3865  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',      return &Apache::lonnet::EXT('resource.'.$part.'.weight',
  $self->symb(), $ENV{'user.domain'},   $self->symb(), $env{'user.domain'},
  $ENV{'user.name'},    $env{'user.name'}, 
  $ENV{'request.course.sec'});   $env{'request.course.sec'});
   }
   sub part_display {
       my $self= shift(); my $partID = shift();
       if (! defined($partID)) { $partID = '0'; }
       my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',
                                        $self->symb);
       if (! defined($display) || $display eq '') {
           $display = $partID;
       }
       return $display;
 }  }
   
 # Multiple things need this  # Multiple things need this
Line 3361  Returns the number of parts of the probl Line 3967  Returns the number of parts of the probl
 for single part problems, returns 1. For multipart, it returns the  for single part problems, returns 1. For multipart, it returns the
 number of parts in the problem, not including psuedo-part 0.   number of parts in the problem, not including psuedo-part 0. 
   
   =item * B<countResponses>():
   
   Returns the total number of responses in the problem a student can answer.
   
   =item * B<responseTypes>():
   
   Returns a hash whose keys are the response types.  The values are the number 
   of times each response type is used.  This is for the I<entire> problem, not 
   just a single part.
   
 =item * B<multipart>():  =item * B<multipart>():
   
 Returns true if the problem is multipart, false otherwise. Use this instead  Returns true if the problem is multipart, false otherwise. Use this instead
Line 3407  sub countParts { Line 4023  sub countParts {
     return scalar(@{$parts}); # + $delta;      return scalar(@{$parts}); # + $delta;
 }  }
   
   sub countResponses {
       my $self = shift;
       my $count;
       foreach my $part (@{$self->parts()}) {
           $count+= scalar($self->responseIds($part));
       }
       return $count;
   }
   
   sub responseTypes {
       my $self = shift;
       my %responses;
       foreach my $part ($self->parts()) {
           foreach my $responsetype ($self->responseType($part)) {
               $responses{$responsetype}++ if (defined($responsetype));
           }
       }
       return %responses;
   }
   
 sub multipart {  sub multipart {
     my $self = shift;      my $self = shift;
     return $self->countParts() > 1;      return $self->countParts() > 1;
 }  }
   
   sub singlepart {
       my $self = shift;
       return $self->countParts() == 1;
   }
   
 sub responseType {  sub responseType {
     my $self = shift;      my $self = shift;
     my $part = shift;      my $part = shift;
   
     $self->extractParts();      $self->extractParts();
     return $self->{RESPONSE_TYPES}->{$part};      if (defined($self->{RESPONSE_TYPES}->{$part})) {
    return @{$self->{RESPONSE_TYPES}->{$part}};
       } else {
    return undef;
       }
 }  }
   
 sub responseIds {  sub responseIds {
Line 3425  sub responseIds { Line 4070  sub responseIds {
     my $part = shift;      my $part = shift;
   
     $self->extractParts();      $self->extractParts();
     return $self->{RESPONSE_IDS}->{$part};      if (defined($self->{RESPONSE_IDS}->{$part})) {
    return @{$self->{RESPONSE_IDS}->{$part}};
       } else {
    return undef;
       }
 }  }
   
 # Private function: Extracts the parts information, both part names and  # Private function: Extracts the parts information, both part names and
Line 3442  sub extractParts { Line 4091  sub extractParts {
   
     # Retrieve part count, if this is a problem      # Retrieve part count, if this is a problem
     if ($self->is_problem()) {      if ($self->is_problem()) {
    my $partorder = &Apache::lonnet::metadata($self->src(), 'partorder');
         my $metadata = &Apache::lonnet::metadata($self->src(), 'packages');          my $metadata = &Apache::lonnet::metadata($self->src(), 'packages');
         if (!$metadata) {  
             $self->{RESOURCE_ERROR} = 1;  
             $self->{PARTS} = [];  
             $self->{PART_TYPE} = {};  
             return;  
         }  
         foreach (split(/\,/,$metadata)) {  
             if ($_ =~ /^part_(.*)$/) {  
                 my $part = $1;  
                 # This floods the logs if it blows up  
                 if (defined($parts{$part})) {  
                     Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->symb());  
                   }  
   
                 # check to see if part is turned off.  
   
                 if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) {   if ($partorder) {
                     $parts{$part} = 1;      my @parts;
                 }      for my $part (split (/,/,$partorder)) {
             }   if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) {
       push @parts, $part;
       $parts{$part} = 1;
    }
       }
       $self->{PARTS} = \@parts;
    } else {
       if (!$metadata) {
    $self->{RESOURCE_ERROR} = 1;
    $self->{PARTS} = [];
    $self->{PART_TYPE} = {};
    return;
       }
       foreach (split(/\,/,$metadata)) {
    if ($_ =~ /^part_(.*)$/) {
       my $part = $1;
       # This floods the logs if it blows up
       if (defined($parts{$part})) {
    &Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->symb());
       }
       
       # check to see if part is turned off.
       
       if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) {
    $parts{$part} = 1;
       }
    }
       }
       my @sortedParts = sort keys %parts;
       $self->{PARTS} = \@sortedParts;
         }          }
                   
           
         my @sortedParts = sort keys %parts;  
         $self->{PARTS} = \@sortedParts;  
   
           # These hashes probably do not need names that end with "Hash"....
         my %responseIdHash;          my %responseIdHash;
         my %responseTypeHash;          my %responseTypeHash;
   
Line 3479  sub extractParts { Line 4141  sub extractParts {
         }          }
   
         # Now, the unfortunate thing about this is that parts, part name, and          # Now, the unfortunate thing about this is that parts, part name, and
         # response if are delimited by underscores, but both the part          # response id are delimited by underscores, but both the part
         # name and response id can themselves have underscores in them.          # name and response id can themselves have underscores in them.
         # 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
Line 3491  sub extractParts { Line 4153  sub extractParts {
                 my $partIdSoFar = '';                  my $partIdSoFar = '';
                 my @partChunks = split /_/, $partStuff;                  my @partChunks = split /_/, $partStuff;
                 my $i = 0;                  my $i = 0;
   
                 for ($i = 0; $i < scalar(@partChunks); $i++) {                  for ($i = 0; $i < scalar(@partChunks); $i++) {
                     if ($partIdSoFar) { $partIdSoFar .= '_'; }                      if ($partIdSoFar) { $partIdSoFar .= '_'; }
                     $partIdSoFar .= $partChunks[$i];                      $partIdSoFar .= $partChunks[$i];
Line 3499  sub extractParts { Line 4160  sub extractParts {
                         my @otherChunks = @partChunks[$i+1..$#partChunks];                          my @otherChunks = @partChunks[$i+1..$#partChunks];
                         my $responseId = join('_', @otherChunks);                          my $responseId = join('_', @otherChunks);
                         push @{$responseIdHash{$partIdSoFar}}, $responseId;                          push @{$responseIdHash{$partIdSoFar}}, $responseId;
                         $responseTypeHash{$partIdSoFar} = $responseType;                          push @{$responseTypeHash{$partIdSoFar}}, $responseType;
                         last;  
                     }                      }
                 }                  }
             }              }
         }          }
    my $resorder = &Apache::lonnet::metadata($self->src(),'responseorder');
           #
           # Reorder the arrays in the %responseIdHash and %responseTypeHash
    if ($resorder) {
       my @resorder=split(/,/,$resorder);
       foreach my $part (keys(%responseIdHash)) {
    my $i=0;
    my %resids = map { ($_,$i++) } @{ $responseIdHash{$part} };
    my @neworder;
    foreach my $possibleid (@resorder) {
       if (exists($resids{$possibleid})) {
    push(@neworder,$resids{$possibleid});
       }
    }
    my @ids;
    my @type;
    foreach my $element (@neworder) {
       push (@ids,$responseIdHash{$part}->[$element]);
       push (@type,$responseTypeHash{$part}->[$element]);
    }
    $responseIdHash{$part}=\@ids;
    $responseTypeHash{$part}=\@type;
       }
    }
         $self->{RESPONSE_IDS} = \%responseIdHash;          $self->{RESPONSE_IDS} = \%responseIdHash;
         $self->{RESPONSE_TYPES} = \%responseTypeHash;          $self->{RESPONSE_TYPES} = \%responseTypeHash;
     }      }
Line 3693  sub ATTEMPTED             { return 16; } Line 4376  sub ATTEMPTED             { return 16; }
   
 sub getCompletionStatus {  sub getCompletionStatus {
     my $self = shift;      my $self = shift;
       my $part = shift;
     return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE});      return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE});
   
     my $status = $self->queryRestoreHash('solved', shift);      my $status = $self->queryRestoreHash('solved', $part);
   
     # Left as seperate if statements in case we ever do more with this      # Left as separate if statements in case we ever do more with this
     if ($status eq 'correct_by_student') {return $self->CORRECT;}      if ($status eq 'correct_by_student') {return $self->CORRECT;}
     if ($status eq 'correct_by_override') {return $self->CORRECT_BY_OVERRIDE; }      if ($status eq 'correct_by_scantron') {return $self->CORRECT;}
       if ($status eq 'correct_by_override') {
    return $self->CORRECT_BY_OVERRIDE;
       }
     if ($status eq 'incorrect_attempted') {return $self->INCORRECT; }      if ($status eq 'incorrect_attempted') {return $self->INCORRECT; }
     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; }
Line 3803  An answer has been submitted, but the st Line 4490  An answer has been submitted, but the st
   
 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 status {  sub status {
     my $self = shift;      my $self = shift;
Line 3815  sub status { Line 4503  sub status {
     # dimension and 5 entries on the other, which we want to colorize,      # dimension and 5 entries on the other, which we want to colorize,
     # plus network failure and "no date data at all".      # plus network failure and "no date data at all".
   
       #if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; }
     if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; }      if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; }
   
     my $suppressFeedback = lc($self->parmval("problemstatus", $part)) eq 'no';      my $suppressFeedback = $self->problemstatus($part) eq 'no';
       # If there's an answer date and we're past it, don't
       # suppress the feedback; student should know
       if ($self->duedate($part) && $self->duedate($part) < time() &&
    $self->answerdate($part) && $self->answerdate($part) < time()) {
    $suppressFeedback = 0;
       }
   
     # There are a few whole rows we can dispose of:      # There are a few whole rows we can dispose of:
     if ($completionStatus == CORRECT ||      if ($completionStatus == CORRECT ||
         $completionStatus == CORRECT_BY_OVERRIDE ) {          $completionStatus == CORRECT_BY_OVERRIDE ) {
         return $suppressFeedback? ANSWER_SUBMITTED : CORRECT;    if ( $suppressFeedback ) { return ANSWER_SUBMITTED }
    my $awarded=$self->awarded($part);
    if ($awarded < 1 && $awarded > 0) {
               return PARTIALLY_CORRECT;
    } elsif ($awarded<1) {
       return INCORRECT;
    }
    return CORRECT; 
     }      }
   
     if ($completionStatus == ATTEMPTED) {      if ($completionStatus == ATTEMPTED) {
Line 3843  sub status { Line 4545  sub status {
   
     if ($dateStatus == PAST_DUE_ANSWER_LATER ||      if ($dateStatus == PAST_DUE_ANSWER_LATER ||
         $dateStatus == PAST_DUE_NO_ANSWER ) {          $dateStatus == PAST_DUE_NO_ANSWER ) {
         return $dateStatus;           return $suppressFeedback ? ANSWER_SUBMITTED : $dateStatus; 
     }      }
   
     if ($dateStatus == ANSWER_OPEN) {      if ($dateStatus == ANSWER_OPEN) {
Line 3861  sub status { Line 4563  sub status {
     if ($completionStatus == INCORRECT || $completionStatus == INCORRECT_BY_OVERRIDE) {      if ($completionStatus == INCORRECT || $completionStatus == INCORRECT_BY_OVERRIDE) {
         # and there are TRIES LEFT:          # and there are TRIES LEFT:
         if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) {          if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) {
             return TRIES_LEFT;              return $suppressFeedback ? ANSWER_SUBMITTED : TRIES_LEFT;
         }          }
         return $suppressFeedback ? ANSWER_SUBMITTED : INCORRECT; # otherwise, return orange; student can't fix this          return $suppressFeedback ? ANSWER_SUBMITTED : INCORRECT; # otherwise, return orange; student can't fix this
     }      }
Line 3870  sub status { Line 4572  sub status {
     return OPEN;       return OPEN; 
 }  }
   
   sub CLOSED { return 23; }
   sub ERROR { return 24; }
   
   =pod
   
   B<Simple Status>
   
   Convenience method B<simpleStatus> provides a "simple status" for the resource.
   "Simple status" corresponds to "which icon is shown on the
   Navmaps". There are six "simple" statuses:
   
   =over 4
   
   =item * B<CLOSED>: The problem is currently closed. (No icon shown.)
   
   =item * B<OPEN>: The problem is open and unattempted.
   
   =item * B<CORRECT>: The problem is correct for any reason.
   
   =item * B<INCORRECT>: The problem is incorrect and can still be
   completed successfully.
   
   =item * B<ATTEMPTED>: The problem has been attempted, but the student
   does not know if they are correct. (The ellipsis icon.)
   
   =item * B<ERROR>: There is an error retrieving information about this
   problem.
   
   =back
   
   =cut
   
   # This hash maps the composite status to this simple status, and
   # can be used directly, if you like
   my %compositeToSimple = 
       (
         NETWORK_FAILURE()       => ERROR,
         NOTHING_SET()           => CLOSED,
         CORRECT()               => CORRECT,
         PARTIALLY_CORRECT()     => PARTIALLY_CORRECT,
         EXCUSED()               => CORRECT,
         PAST_DUE_NO_ANSWER()    => INCORRECT,
         PAST_DUE_ANSWER_LATER() => INCORRECT,
         ANSWER_OPEN()           => INCORRECT,
         OPEN_LATER()            => CLOSED,
         TRIES_LEFT()            => OPEN,
         INCORRECT()             => INCORRECT,
         OPEN()                  => OPEN,
         ATTEMPTED()             => ATTEMPTED,
         ANSWER_SUBMITTED()      => ATTEMPTED
        );
   
   sub simpleStatus {
       my $self = shift;
       my $part = shift;
       my $status = $self->status($part);
       return $compositeToSimple{$status};
   }
   
   =pod
   
   B<simpleStatusCount> will return an array reference containing, in
   this order, the number of OPEN, CLOSED, CORRECT, INCORRECT, ATTEMPTED,
   and ERROR parts the given problem has.
   
   =cut
       
   # This maps the status to the slot we want to increment
   my %statusToSlotMap = 
       (
        OPEN()      => 0,
        CLOSED()    => 1,
        CORRECT()   => 2,
        INCORRECT() => 3,
        ATTEMPTED() => 4,
        ERROR()     => 5
        );
   
   sub statusToSlot { return $statusToSlotMap{shift()}; }
   
   sub simpleStatusCount {
       my $self = shift;
   
       my @counts = (0, 0, 0, 0, 0, 0, 0);
       foreach my $part (@{$self->parts()}) {
    $counts[$statusToSlotMap{$self->simpleStatus($part)}]++;
       }
   
       return \@counts;
   }
   
 =pod  =pod
   
 B<Completable>  B<Completable>
Line 3939  sub getNext { Line 4732  sub getNext {
     my $to = $self->to();      my $to = $self->to();
     foreach my $branch ( split(/,/, $to) ) {      foreach my $branch ( split(/,/, $to) ) {
         my $choice = $self->{NAV_MAP}->getById($branch);          my $choice = $self->{NAV_MAP}->getById($branch);
           if (!$choice->condition()) { next; }
         my $next = $choice->goesto();          my $next = $choice->goesto();
         $next = $self->{NAV_MAP}->getById($next);          $next = $self->{NAV_MAP}->getById($next);
   
Line 3967  sub browsePriv { Line 4761  sub browsePriv {
         return $self->{BROWSE_PRIV};          return $self->{BROWSE_PRIV};
     }      }
   
     $self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre', $self->src());      $self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre',$self->src(),
       $self->symb());
 }  }
   
 =pod  =pod

Removed from v.1.216  
changed lines
  Added in v.1.333


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