Diff for /loncom/interface/lonnavmaps.pm between versions 1.205 and 1.255

version 1.205, 2003/06/16 15:08:22 version 1.255, 2004/03/22 22:17:40
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;
   
Line 46  use strict; Line 33  use strict;
 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::lonlocal;
 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 used
   
Line 61  my $resObj = "Apache::lonnavmaps::resour Line 49  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->INCORRECT    => 'navmap.wrong.gif',
       $resObj->PAST_DUE_ANSWER_LATER => 'navmap.wrong.gif',       $resObj->ATTEMPTED    => 'navmap.ellipsis.gif',
       $resObj->ANSWER_OPEN        => 'navmap.wrong.gif',       $resObj->ERROR        => ''
       $resObj->OPEN_LATER         => '',       );
       $resObj->TRIES_LEFT         => 'navmap.open.gif',  
       $resObj->INCORRECT          => 'navmap.wrong.gif',  
       $resObj->OPEN               => 'navmap.open.gif',  
       $resObj->ATTEMPTED          => 'navmap.ellipsis.gif',  
       $resObj->ANSWER_SUBMITTED   => '' );  
   
 my %iconAltTags =   my %iconAltTags = 
     ( 'navmap.correct.gif' => 'Correct',      ( 'navmap.correct.gif' => 'Correct',
Line 92  my %colormap = Line 75  my %colormap =
       $resObj->TRIES_LEFT             => '',        $resObj->TRIES_LEFT             => '',
       $resObj->INCORRECT              => '',        $resObj->INCORRECT              => '',
       $resObj->OPEN                   => '',        $resObj->OPEN                   => '',
       $resObj->NOTHING_SET            => '' );        $resObj->NOTHING_SET            => '',
         $resObj->ATTEMPTED              => '',
         $resObj->ANSWER_SUBMITTED       => ''
         );
 # 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";
Line 108  sub real_handler { Line 94  sub real_handler {
     # 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;
Line 118  sub real_handler { Line 104  sub real_handler {
   
     # 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      # Create the nav map
     my $navmap = Apache::lonnavmaps::navmap->new(      my $navmap = Apache::lonnavmaps::navmap->new();
                         $ENV{"request.course.fn"}.".db",  
                         $ENV{"request.course.fn"}."_parms.db", 1, 1);  
   
   
     if (!defined($navmap)) {      if (!defined($navmap)) {
         my $requrl = $r->uri;          my $requrl = $r->uri;
Line 138  sub real_handler { Line 121  sub real_handler {
     }      }
   
     $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']);
           
Line 158  sub real_handler { Line 141  sub real_handler {
   
     $r->rflush();      $r->rflush();
   
     # Now that we've displayed some stuff to the user, init the navmap  
     $navmap->init();  
   
     $r->print('<br>');  
     $r->rflush();  
   
     # Check that it's defined      # Check that it's defined
     if (!($navmap->courseMapDefined())) {      if (!($navmap->courseMapDefined())) {
         $r->print('<font size="+2" color="red">Coursemap undefined.</font>' .          $r->print('<font size="+2" color="red">Coursemap undefined.</font>' .
Line 173  sub real_handler { Line 150  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) {
Line 207  sub real_handler { Line 178  sub real_handler {
         $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 234  sub real_handler { Line 200  sub real_handler {
                     $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 244  sub real_handler { Line 208  sub real_handler {
         }          }
     } else {      } else {
         $r->print("<a href='navmaps?jumpToFirstHomework'>" .          $r->print("<a href='navmaps?jumpToFirstHomework'>" .
                   "Go To My First Homework Problem</a><br />");         &mt("Go To My First Homework Problem")."</a>&nbsp;&nbsp;&nbsp;&nbsp;");
     }      }
   
     my $suppressEmptySequences = 0;      my $suppressEmptySequences = 0;
Line 259  sub real_handler { Line 223  sub real_handler {
         $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>");          $r->print("<p><font size='+2'>".&mt("Uncompleted Homework")."</font></p>");
         $ENV{'form.filter'} = '';          $ENV{'form.filter'} = '';
         $ENV{'form.condition'} = 1;          $ENV{'form.condition'} = 1;
  $resource_no_folder_link = 1;   $resource_no_folder_link = 1;
     } else {      } else {
         $r->print("<a href='navmaps?showOnlyHomework'>" .          $r->print("<a href='navmaps?showOnlyHomework'>" .
                   "Show Only Uncompleted Homework</a><br />");         &mt("Show Only Uncompleted Homework")."</a>&nbsp;&nbsp;&nbsp;&nbsp;");
     }      }
   
     # renderer call      # renderer call
Line 284  sub real_handler { Line 248  sub real_handler {
     # 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>");
         }          }
Line 326  sub getLinkForResource { Line 290  sub getLinkForResource {
   
     # 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();      if ($res->is_page()) {
    return $res->src();
       }
               # in case folder was skipped over as "only sequence"
       my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb());
       if ($map=~/\.page$/) {
    return &Apache::lonnet::clutter($map).'#'.
       &Apache::lonnet::escape(&Apache::lonnet::declutter($src));
       }
         }          }
     }      }
   
Line 342  sub getLinkForResource { Line 314  sub getLinkForResource {
     return $res->src();      return $res->src();
 }  }
   
 # Convenience function: This seperates the logic of how to create  # Convenience function: This separates the logic of how to create
 # the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned",  # 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 354  sub getDescription { Line 326  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) {
         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 394  sub getDescription { Line 366  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() || $status == $res->ATTEMPTED() ||      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 429  sub lastTry { Line 401  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.
Line 452  sub timeToHumanString { Line 424  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 522  sub timeToHumanString { Line 496  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 " : "next ") .
                 $timeStr;                  $timeStr;
Line 532  sub timeToHumanString { Line 506  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 %G 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 550  sub timeToHumanString { Line 524  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 558  The main handler generates the navigatio Line 533  The main handler generates the navigatio
 the other objects export this information in a usable fashion for  the other objects export this information in a usable fashion for
 other modules.  other modules.
   
   =head1 OVERVIEW
   
   X<lonnavmaps, overview> When a user enters a course, LON-CAPA examines the
   course structure and caches it in what is often referred to as the
   "big hash" X<big hash>. You can see it if you are logged into
   LON-CAPA, in a course, by going to /adm/test. (You may need to
   tweak the /home/httpd/lonTabs/htpasswd file to view it.) The
   content of the hash will be under the heading "Big Hash".
   
   Big Hash contains, among other things, how resources are related
   to each other (next/previous), what resources are maps, which 
   resources are being chosen to not show to the student (for random
   selection), and a lot of other things that can take a lot of time
   to compute due to the amount of data that needs to be collected and
   processed.
   
   Apache::lonnavmaps provides an object model for manipulating this
   information in a higher-level fashion then directly manipulating 
   the hash. It also provides access to several auxilary functions 
   that aren't necessarily stored in the Big Hash, but are a per-
   resource sort of value, like whether there is any feedback on 
   a given resource.
   
   Apache::lonnavmaps also abstracts away branching, and someday, 
   conditions, for the times where you don't really care about those
   things.
   
   Apache::lonnavmaps also provides fairly powerful routines for
   rendering navmaps, and last but not least, provides the navmaps
   view for when the user clicks the NAV button.
   
   B<Note>: Apache::lonnavmaps I<only> works for the "currently
   logged in user"; if you want things like "due dates for another
   student" lonnavmaps can not directly retrieve information like
   that. You need the EXT function. This module can still help,
   because many things, such as the course structure, are constant
   between users, and Apache::lonnavmaps can help by providing
   symbs for the EXT call.
   
   The rest of this file will cover the provided rendering routines, 
   which can often be used without fiddling with the navmap object at
   all, then documents the Apache::lonnavmaps::navmap object, which
   is the key to accessing the Big Hash information, covers the use
   of the Iterator (which provides the logic for traversing the 
   somewhat-complicated Big Hash data structure), documents the
   Apache::lonnavmaps::Resource objects that are returned by 
   
 =head1 Subroutine: render  =head1 Subroutine: render
   
 The navmap renderer package provides a sophisticated rendering of the  The navmap renderer package provides a sophisticated rendering of the
 standard navigation maps interface into HTML. The provided nav map  standard navigation maps interface into HTML. The provided nav map
 handler is actually just a glorified call to this.  handler is actually just a glorified call to this.
   
 Because of the large number of parameters this function presents,  Because of the large number of parameters this function accepts,
 instead of passing it arguments as is normal, pass it in an anonymous  instead of passing it arguments as is normal, pass it in an anonymous
 hash with the given options. This is because there is no obvious order  hash with the desired options.
 you may wish to override these in and a hash is easier to read and  
 understand then "undef, undef, undef, 1, undef, undef, renderButton,  
 undef, 0" when you mostly want default behaviors.  
   
 The package provides a function called 'render', called as  The package provides a function called 'render', called as
 Apache::lonnavmaps::render({}).  Apache::lonnavmaps::render({}).
Line 597  that takes a resource reference, a part Line 616  that takes a resource reference, a part
 argument hash passed to the renderer, and returns a string that will  argument hash passed to the renderer, and returns a string that will
 be inserted into the HTML representation as it.  be inserted into the HTML representation as it.
   
   All other parameters are ways of either changing how the columns
   are printing, or which rows are shown.
   
 The pre-packaged column names are refered to by constants in the  The pre-packaged column names are refered to by constants in the
 Apache::lonnavmaps namespace. The following currently exist:  Apache::lonnavmaps namespace. The following currently exist:
   
 =over 4  =over 4
   
 =item * B<resource>:  =item * B<Apache::lonnavmaps::resource>:
   
 The general info about the resource: Link, icon for the type, etc. The  The general info about the resource: Link, icon for the type, etc. The
 first column in the standard nav map display. This column also accepts  first column in the standard nav map display. This column provides the
   indentation effect seen in the B<NAV> screen. This column also accepts
 the following parameters in the renderer hash:  the following parameters in the renderer hash:
   
 =over 4  =over 4
   
 =item * B<resource_nolink>:  =item * B<resource_nolink>: default false
   
 If true, the resource will not be linked. Default: false, resource  If true, the resource will not be linked. By default, all non-folder
 will have links.  resources are linked.
   
 =item * B<resource_part_count>:  =item * B<resource_part_count>: default true
   
 If true (default), the resource will show a part count if the full  If true, the resource will show a part count B<if> the full
 part list is not displayed. If false, the resource will never show a  part list is not displayed. (See "condense_parts" later.) If false,
 part count.  the resource will never show a part count.
   
 =item * B<resource_no_folder_link>:  =item * B<resource_no_folder_link>:
   
Line 629  can't close or open folders when this is Line 652  can't close or open folders when this is
   
 =back  =back
   
 =item B<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<quick_status>:  =item * B<Apache::lonnavmaps::quick_status>:
   
 An icon for the status of a problem, with four possible states:  An icon for the status of a problem, with five possible states:
 Correct, incorrect, open, or none (not open yet, not a problem). The  Correct, incorrect, open, awaiting grading (for a problem where the
   computer's grade is suppressed, or the computer can't grade, like
   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<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 664  to override vertical and horizontal alig Line 702  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 675  automatically. Line 711  automatically.
   
 =over 4  =over 4
   
 =item * B<iterator>:  =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
Line 684  one is not passed, the renderer will att Line 720  one is not passed, the renderer will att
 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>:  =item * B<iterator_map>: default: not used
   
 If you are letting the renderer do the iterator handling, you can  If you are letting the renderer do the iterator handling, you can
 instruct the renderer to render only a particular map by passing it  instruct the renderer to render only a particular map by passing it
 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>:  =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>:  =item * B<r>: default: must be passed in
   
 The standard Apache response object. This must be passed to the  The standard Apache response object. This must be passed to the
 renderer or the course hash will be locked.  renderer or the course hash will be locked.
   
 =item * B<cols>:  =item * B<cols>: default: empty (useless)
   
 An array reference  An array reference
   
 =item * B<showParts>:  =item * B<showParts>:default true
   
 A flag. If yes (default), a line for the resource itself, and a line  A flag. If true, a line for the resource itself, and a line
 for each part will be displayed. If not, only one line for each  for each part will be displayed. If not, only one line for each
 resource will be displayed.  resource will be displayed.
   
 =item * B<condenseParts>:  =item * B<condenseParts>: default true
   
 A flag. If yes (default), if all parts of the problem have the same  A flag. If true, if all parts of the problem have the same
 status and that status is Nothing Set, Correct, or Network Failure,  status and that status is Nothing Set, Correct, or Network Failure,
 then only one line will be displayed for that resource anyhow. If no,  then only one line will be displayed for that resource anyhow. If no,
 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>:  =item * B<jumpCount>: default: determined from %ENV
   
 A string identifying the URL to place the anchor 'curloc' at. Default  A string identifying the URL to place the anchor 'curloc' at.
 to no anchor at all. 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>:  =item * B<here>: default: empty string
   
 A Symb identifying where to place the 'here' marker. Default empty,  A Symb identifying where to place the 'here' marker. The empty
 which means no marker.  string means no marker.
   
 =item * B<indentString>:  =item * B<indentString>: default: 25 pixel whitespace image
   
 A string identifying the indentation string to use. By default, this  A string identifying the indentation string to use. 
 is a 25 pixel whitespace image with no alt text.  
   
 =item * B<queryString>:  =item * B<queryString>: default: empty
   
 A string which will be prepended to the query string used when the  A string which will be prepended to the query string used when the
 folders are opened or closed.  folders are opened or closed. You can use this to pass
   application-specific values.
   
 =item * B<url>:  =item * B<url>: default: none
   
 The url the folders will link to, which should be the current  The url the folders will link to, which should be the current
 page. Required if the resource info column is shown.  page. Required if the resource info column is shown, and you 
   are allowing the user to open and close folders.
   
 =item * B<currentJumpIndex>:  =item * B<currentJumpIndex>: default: no jumping
   
 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>:  =item * B<printKey>: default: false
   
 If true, print the key that appears on the top of the standard  If true, print the key that appears on the top of the standard
 navmaps. Default is false.  navmaps.
   
 =item * B<printCloseAll>:  =item * B<printCloseAll>: default: true
   
 If true, print the "Close all folders" or "open all folders"  If true, print the "Close all folders" or "open all folders"
 links. Default is true.  links.
   
 =item * B<filterFunc>:  =item * B<filterFunc>: default: sub {return 1;} (accept everything)
   
 A function that takes the resource object as its only parameter and  A function that takes the resource object as its only parameter and
 returns a true or false value. If true, the resource is displayed. If  returns a true or false value. If true, the resource is displayed. If
 false, it is simply skipped in the display. By default, all resources  false, it is simply skipped in the display.
 are shown.  
   
 =item * B<suppressEmptySequences>:  =item * B<suppressEmptySequences>: default: false
   
 If you're using a filter function, and displaying sequences to orient  If you're using a filter function, and displaying sequences to orient
 the user, then frequently some sequences will be empty. Setting this to  the user, then frequently some sequences will be empty. Setting this to
 true will cause those sequences not to display, so as not to confuse the  true will cause those sequences not to display, so as not to confuse the
 user into thinking that if the sequence is there there should be things  user into thinking that if the sequence is there there should be things
 under it.  under it; for example, see the "Show Uncompleted Homework" view on the
   B<NAV> screen.
   
 =item * B<suppressNavmaps>:  =item * B<suppressNavmaps>: default: false
   
 If true, will not display Navigate Content resources. Default to  If true, will not display Navigate Content resources. 
 false.  
   
 =back  =back
   
Line 794  be passed through unchange to the column Line 830  be passed through unchange to the column
 generate the following information which your renderer may find  generate the following information which your renderer may find
 useful:  useful:
   
 If you want to know how many rows were printed, the 'counter' element  
 of the hash passed into the render function will contain the  
 count. You may want to check whether any resources were printed at  
 all.  
   
 =over 4  =over 4
   
   =item * B<counter>: 
   
   Contains the number of rows printed. Useful after calling the render 
   function, as you can detect whether anything was printed at all.
   
   =item * B<isNewBranch>:
   
   Useful for renderers: If this resource is currently the first resource
   of a new branch, this will be true. The Resource column (leftmost in the
   navmaps screen) uses this to display the "new branch" icon 
   
 =back  =back
   
 =cut  =cut
Line 809  sub resource { return 0; } Line 851  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 849  sub render_resource { Line 890  sub render_resource {
             $icon = $params->{'indentString'};              $icon = $params->{'indentString'};
         }          }
     } else {      } else {
  my $curfext= (split (/\./,$resource->src))[-1];   $icon = "<img src='".&Apache::loncommon::icon($resource->src).
  my $embstyle = &Apache::loncommon::fileembstyle($curfext);      "' alt='' border='0' />";
  # 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 924  sub render_resource { Line 961  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=&Apache::lonnet::EXT('resource.'.$part.'.display',
        $resource->symb());
    unless ($displaypart) { $displaypart=$part; }
           $partLabel = " (Part: $displaypart)";
    $link.='#'.&Apache::lonnet::escape($part);
         $title = "";          $title = "";
     }      }
   
Line 970  sub render_communication_status { Line 1011  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="/adm/lonMisc/bomb.gif" '
Line 999  sub render_quick_status { Line 1043  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";              $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";
Line 1019  sub render_long_status { Line 1064  sub render_long_status {
         $params->{'multipart'} && $part eq "0";          $params->{'multipart'} && $part eq "0";
                                   
     my $color;      my $color;
     if ($resource->is_problem() && ($resource->countParts() <= 1 || $part ne '') ) {      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 1038  sub render_long_status { Line 1083  sub render_long_status {
     if ($resource->is_map() && advancedUser() && $resource->randompick()) {      if ($resource->is_map() && advancedUser() && $resource->randompick()) {
         $result .= '(randomly select ' . $resource->randompick() .')';          $result .= '(randomly select ' . $resource->randompick() .')';
     }      }
   
       # Debugging code
       #$result .= " " . $resource->awarded($part) . '/' . $resource->weight($part) .
       # ' - Part: ' . $part;
   
       $result .= "</td>\n";
           
     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()) { return '<td></td>'; }
       if ($params->{showParts}) { 
    return '<td></td>';
       }
   
       my $td = "<td align='right'>\n";
       my $endtd = "</td>\n";
   
       # If there is a single part, just show the simple status
       if ($resource->singlepart()) {
    my $status = $resource->simpleStatus(${$resource->parts}[0]);
    return $td . "<font color='" . $statusColors{$status} . "'>"
       . $statusStrings{$status} . "</font>" . $endtd;
       }
   
       # Now we can be sure the $part doesn't really matter.
       my $statusCount = $resource->simpleStatusCount();
       my @counts;
       foreach my $status(@statuses) {
    # decouple display order from the simpleStatusCount order
    my $slot = Apache::lonnavmaps::resource::statusToSlot($status);
    if ($statusCount->[$slot]) {
       push @counts, "<font color='" . $statusColors{$status} .
    "'>" . $statusCount->[$slot] . ' '
    . $statusStrings{$status} . "</font>";
    }
       }
   
       return $td . $resource->countParts() . ' parts: ' . join (', ', @counts) . $endtd;
   }
   
 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 1109  sub render { Line 1218  sub render {
     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.
Line 1132  sub render { Line 1238  sub render {
   
         # 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 (($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 1157  sub render { Line 1258  sub render {
                 }                  }
                 $found = 1;                  $found = 1;
             }              }
               
             $curRes = $mapIterator->next();  
         }                      }            
     }              }        
   
Line 1175  sub render { Line 1274  sub render {
                   
         # Step 1: Check to see if we have a navmap          # Step 1: Check to see if we have a navmap
         if (!defined($navmap)) {          if (!defined($navmap)) {
             $navmap = Apache::lonnavmaps::navmap->new($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 1200  sub render { Line 1295  sub render {
     # 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 1219  sub render { Line 1310  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 1239  sub render { Line 1328  sub render {
         $result.='<tr><td align="right" valign="bottom">Key:&nbsp;&nbsp;</td>';          $result.='<tr><td align="right" valign="bottom">Key:&nbsp;&nbsp;</td>';
         if ($navmap->{LAST_CHECK}) {          if ($navmap->{LAST_CHECK}) {
             $result .=               $result .= 
                 '<img src="/adm/lonMisc/chat.gif"> New discussion since '.                  '<img src="/adm/lonMisc/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="/adm/lonMisc/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="/adm/lonMisc/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="/adm/lonMisc/feedback.gif"> '.&mt('New message (click to open)').
                 '</td>';                   '</td>'; 
         }          }
   
Line 1258  sub render { Line 1347  sub render {
         if ($condition) {          if ($condition) {
             $result.="<a href=\"navmaps?condition=0&filter=&$queryString" .              $result.="<a href=\"navmaps?condition=0&filter=&$queryString" .
                 "&here=" . Apache::lonnet::escape($here) .                  "&here=" . Apache::lonnet::escape($here) .
                 "\">Close All Folders</a>";                  "\">".&mt('Close All Folders')."</a>";
         } else {          } else {
             $result.="<a href=\"navmaps?condition=1&filter=&$queryString" .              $result.="<a href=\"navmaps?condition=1&filter=&$queryString" .
                 "&here=" . Apache::lonnet::escape($here) .                   "&here=" . Apache::lonnet::escape($here) . 
                 "\">Open All Folders</a>";                  "\">".&mt('Open All Folders')."</a>";
         }          }
         $result .= "<br /><br />\n";          $result .= "<br /><br />\n";
     }          }    
Line 1298  sub render { Line 1387  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 1330  sub render { Line 1419  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 1340  sub render { Line 1426  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++; }      while ($curRes = $it->next()) {
         if ($curRes == $it->END_MAP()) { $depth--; }  
   
         # Maintain indentation level.          # Maintain indentation level.
         if ($curRes == $it->BEGIN_MAP() ||          if ($curRes == $it->BEGIN_MAP() ||
             $curRes == $it->BEGIN_BRANCH() ) {              $curRes == $it->BEGIN_BRANCH() ) {
Line 1459  sub render { Line 1543  sub render {
             my $filter = $it->{FILTER};              my $filter = $it->{FILTER};
             my $stack = $it->getStack();              my $stack = $it->getStack();
             my $src = getLinkForResource($stack);              my $src = getLinkForResource($stack);
                           my $anchor='';
               if ($src=~s/(\#.*$)//) {
    $anchor=$1;
       }
             my $srcHasQuestion = $src =~ /\?/;              my $srcHasQuestion = $src =~ /\?/;
             $args->{"resourceLink"} = $src.              $args->{"resourceLink"} = $src.
                 ($srcHasQuestion?'&':'?') .                  ($srcHasQuestion?'&':'?') .
                 'symb=' . &Apache::lonnet::escape($curRes->symb());                  'symb=' . &Apache::lonnet::escape($curRes->symb()).
    $anchor;
                           
             # Now, display each column.              # Now, display each column.
             foreach my $col (@$cols) {              foreach my $col (@$cols) {
Line 1496  sub render { Line 1584  sub render {
             $r->rflush();              $r->rflush();
         }          }
     } continue {      } continue {
         $curRes = $it->next();   if ($r) {
       # If we have the connection, make sure the user is still connected
       my $c = $r->connection;
       if ($c->aborted()) {
    # Who cares what we do, nobody will see it anyhow.
    return '';
       }
    }
     }      }
           
     # Print out the part that jumps to #curloc if it exists      # Print out the part that jumps to #curloc if it exists
Line 1507  sub render { Line 1602  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 1529  package Apache::lonnavmaps::navmap; Line 1629  package Apache::lonnavmaps::navmap;
   
 =pod  =pod
   
 lonnavmaps provides functions and objects for dealing with the  =head1 Object: Apache::lonnavmaps::navmap
 compiled course hashes generated when a user enters the course, the  
 Apache handler for the "Navigation Map" button, and a flexible  
 prepared renderer for navigation maps that are easy to use anywhere.  
   
 =head1 Object: navmap  =head2 Overview
   
 Encapsulating the compiled nav map  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.
   
 navmap is an object that encapsulates a compiled course map and  Generally, you'll use the navmap object in one of three basic ways.
 provides a reasonable interface to it.  In order of increasing complexity and power:
   
 Most notably it provides a way to navigate the map sensibly and a  =over 4
 flexible iterator that makes it easy to write various renderers based  
 on nav maps.  
   
 You must obtain resource objects through the navmap object.  =item * C<$navmap-E<gt>getByX>, where X is B<Id>, B<Symb>, B<Url> or B<MapPc>. This provides
       various ways to obtain resource objects, based on various identifiers.
       Use this when you want to request information about one object or 
       a handful of resources you already know the identities of, from some
       other source. For more about Ids, Symbs, and MapPcs, see the
       Resource documentation. Note that Url should be a B<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.
   
 =head2 Methods  =back
   
   =head2 Creation And Destruction
   
   To create a navmap object, use the following function:
   
 =over 4  =over 4
   
 =item * B<new>(navHashFile, parmHashFile, genCourseAndUserOptions,  =item * B<Apache::lonnavmaps::navmap-E<gt>new>():
   genMailDiscussStatus):  
   
 Binds a new navmap object to the compiled nav map hash and parm hash  Creates a new navmap object. Returns the navmap object if this is
 given as filenames. genCourseAndUserOptions is a flag saying whether  successful, or B<undef> if not.
 the course options and user options hash should be generated. This is  
 for when you are using the parameters of the resources that require  =back
 them; see documentation in resource object  
 documentation. genMailDiscussStatus causes the nav map to retreive  When you are done with the $navmap object, you I<must> call 
 information about the email and discussion status of  $navmap->untieHashes(), or you'll prevent the current user from using that 
 resources. Returns the navmap object if this is successful, or  course until the web server is restarted. (!)
 B<undef> if not. You must check for undef; errors will occur when you  
 try to use the other methods otherwise.  =head2 Methods
   
   =over 4
   
 =item * B<getIterator>(first, finish, filter, condition):  =item * B<getIterator>(first, finish, filter, condition):
   
Line 1580  sub new { Line 1703  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;  
   
     # 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 1597  sub new { Line 1715  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 1611  sub new { Line 1730  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 $uhome=$ENV{'user.home'};
         my $uname=$ENV{'user.name'};      my $cid=$ENV{'request.course.id'};
         my $udom=$ENV{'user.domain'};      my $chome=$ENV{'course.'.$cid.'.home'};
         my $uhome=$ENV{'user.home'};      my ($cdom,$cnum)=split(/\_/,$cid);
         my $cid=$ENV{'request.course.id'};      
         my $chome=$ENV{'course.'.$cid.'.home'};      my $userprefix=$uname.'_'.$udom.'_';
         my ($cdom,$cnum)=split(/\_/,$cid);      
               my %courserdatas; my %useropt; my %courseopt; my %userrdatas;
         my $userprefix=$uname.'_'.$udom.'_';      unless ($uhome eq 'no_host') { 
           
         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) {   unless ((time-$courserdatas{$cid.'.last_cache'})<240) {
                 my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.      my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.
                                                  ':resourcedata',$chome);       ':resourcedata',$chome);
                 # Check for network failure      # Check for network failure
                 if ( $reply =~ /no.such.host/i || $reply =~ /con_lost/i) {      if ( $reply =~ /no.such.host/i || $reply =~ /con_lost/i) {
                     $self->{NETWORK_FAILURE} = 1;   $self->{NETWORK_FAILURE} = 1;
                 } elsif ($reply!~/^error\:/) {      } elsif ($reply!~/^error\:/) {
                     $courserdatas{$cid}=$reply;   $courserdatas{$cid}=$reply;
                     $courserdatas{$cid.'.last_cache'}=time;   $courserdatas{$cid.'.last_cache'}=time;
                 }      }
             }   }
             foreach (split(/\&/,$courserdatas{$cid})) {   foreach (split(/\&/,$courserdatas{$cid})) {
                 my ($name,$value)=split(/\=/,$_);      my ($name,$value)=split(/\=/,$_);
                 $courseopt{$userprefix.&Apache::lonnet::unescape($name)}=      $courseopt{$userprefix.&Apache::lonnet::unescape($name)}=
                     &Apache::lonnet::unescape($value);   &Apache::lonnet::unescape($value);
             }   }
 # --------------------------------------------------- Get userdata (if present)  # --------------------------------------------------- Get userdata (if present)
             unless ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) {   unless ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) {
                 my $reply=&Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome);      my $reply=&Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome);
                 if ($reply!~/^error\:/) {      if ($reply!~/^error\:/) {
                     $userrdatas{$uname.'___'.$udom}=$reply;   $userrdatas{$uname.'___'.$udom}=$reply;
                     $userrdatas{$uname.'___'.$udom.'.last_cache'}=time;   $userrdatas{$uname.'___'.$udom.'.last_cache'}=time;
                 }      }
                 # check to see if network failed      # check to see if network failed
                 elsif ( $reply=~/no.such.host/i || $reply=~/con.*lost/i )      elsif ( $reply=~/no.such.host/i || $reply=~/con.*lost/i )
                 {      {
                     $self->{NETWORK_FAILURE} = 1;   $self->{NETWORK_FAILURE} = 1;
                 }      }
             }   }
             foreach (split(/\&/,$userrdatas{$uname.'___'.$udom})) {   foreach (split(/\&/,$userrdatas{$uname.'___'.$udom})) {
                 my ($name,$value)=split(/\=/,$_);      my ($name,$value)=split(/\=/,$_);
                 $useropt{$userprefix.&Apache::lonnet::unescape($name)}=      $useropt{$userprefix.&Apache::lonnet::unescape($name)}=
                     &Apache::lonnet::unescape($value);   &Apache::lonnet::unescape($value);
             }   }
             $self->{COURSE_OPT} = \%courseopt;   $self->{COURSE_OPT} = \%courseopt;
             $self->{USER_OPT} = \%useropt;   $self->{USER_OPT} = \%useropt;
         }      }
     }     
   
     if ($self->{GENERATE_EMAIL_DISCUSS_STATUS}) {      $self->{COURSE_USER_OPT_GENERATED} = 1;
         my $cid=$ENV{'request.course.id'};      
         my ($cdom,$cnum)=split(/\_/,$cid);      return;
           }
         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 %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;  
           
     }      
   
     $self->{PARM_CACHE} = {};  sub generate_email_discuss_status {
     $self->{INITED} = 1;      my $self = shift;
       if ($self->{EMAIL_DISCUSS_GENERATED}) { return; }
   
       my $cid=$ENV{'request.course.id'};
       my ($cdom,$cnum)=split(/\_/,$cid);
       
       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 %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;
       
       $self->{EMAIL_DISCUSS_GENERATED} = 1;
   }
   
   sub get_user_data {
       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 1734  sub navhash { Line 1867  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;
Line 1763  sub untieHashes { Line 1905  sub untieHashes {
 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});
Line 1777  sub getFeedback { Line 1922  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 1786  sub getFeedback { Line 1933  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 1815  the given map. This is one of the proper Line 1964  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 1835  sub getById { Line 1983  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);      return $self->getById($map->map_pc() . '.' . $id);
 }  }
Line 1898  sub parmval { Line 2046  sub parmval {
   
 sub parmval_real {  sub parmval_real {
     my $self = shift;      my $self = shift;
     my ($what,$symb) = @_;      my ($what,$symb,$recurse) = @_;
   
       # Make sure the {USER_OPT} and {COURSE_OPT} hashes are populated
       $self->generate_course_user_opt();
   
     my $cid=$ENV{'request.course.id'};      my $cid=$ENV{'request.course.id'};
     my $csec=$ENV{'request.course.sec'};      my $csec=$ENV{'request.course.sec'};
Line 1908  sub parmval_real { Line 2059  sub parmval_real {
     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 1958  sub parmval_real { Line 2109  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}
       $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat);
     if (defined($default)) { return $default}      if (defined($default)) { return $default}
   
 # --------------------------------------------------- fifth , cascade up parts  # --------------------------------------------------- fifth , cascade up parts
Line 1970  sub parmval_real { Line 2125  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 2053  sub retrieveResources { Line 2211  sub retrieveResources {
         $map = $self->getResourceByUrl($map);          $map = $self->getResourceByUrl($map);
     }      }
   
       # If nothing was passed, assume top-level map
       if (!$map) {
    $map = $self->getById('0.0');
       }
   
     # Check the map's validity.      # Check the map's validity.
     if (!$map || !$map->is_map()) {      if (!$map->is_map()) {
         # Oh, to throw an exception.... how I'd love that!          # Oh, to throw an exception.... how I'd love that!
         return ();          return ();
     }      }
   
     # 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(),
                                 !$recursive);                                  undef, $recursive);
   
     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 2090  sub retrieveResources { Line 2244  sub retrieveResources {
             }              }
         }          }
   
         $curRes = $it->next();  
     }      }
   
     return @resources;      return @resources;
Line 2166  new branch. The possible tokens are: Line 2319  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 2198  but only one resource will be returned. Line 2358  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 2214  sub min { Line 2395  sub min {
     if ($a < $b) { return $a; } else { return $b; }      if ($a < $b) { return $a; } else { return $b; }
 }  }
   
 # In the CVS repository, documentation of this algorithm is included   
 # in /doc/lonnavdocs, as a PDF and .tex source. Markers like **1**  
 # will reference the same location in the text as the part of the  
 # algorithm is running through.  
   
 sub new {  sub new {
     # magic invocation to create a class instance      # magic invocation to create a class instance
     my $proto = shift;      my $proto = shift;
Line 2274  sub new { Line 2450  sub new {
     # that isn't just a redirector.      # that isn't just a redirector.
     my $resource; my $resourceCount = 0;      my $resource; my $resourceCount = 0;
   
       # Documentation on this algorithm can be found in the CVS repository at 
       # /docs/lonnavdocs; these "**#**" markers correspond to documentation
       # in that file.
     # **1**      # **1**
   
     foreach my $pass (@iterations) {      foreach my $pass (@iterations) {
Line 2289  sub new { Line 2468  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 2329  sub new { Line 2508  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 
Line 2351  sub new { Line 2530  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 2368  sub new { Line 2548  sub new {
 sub next {  sub next {
     my $self = shift;      my $self = 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.
     if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0}) {      if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0}) {
Line 2427  sub next { Line 2611  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 2560  package Apache::lonnavmaps::DFSiterator; Line 2745  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 2739  use Apache::lonnet; Line 2924  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 2760  writing, there is no way to override thi Line 2946  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  
 you can store whatever you want in. This allows you to easily do  
 two-pass algorithms without worrying about managing your own  
 resource->data hash.  
   
 =head2 Methods  
   
 =over 4  
   
 =item * B<new>($navmapRef, $idString):  
   
 The first arg is a reference to the parent navmap object. The second  
 is the idString of the resource itself. Very rarely, if ever, called  
 directly. Use the nav map->getByID() method.  
   
 =back  A B<Resource> is the most granular type of object in LON-CAPA that can
   be included in a course. It can either be a particular resource, like
   an HTML page, external resource, problem, etc., or it can be a
   container sequence, such as a "page" or a "map".
   
   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
   Manual.
   
   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
   information about that resource.
   
   Generally, you do not ever want to create a resource object yourself,
   so creation has been left undocumented. Always retrieve resources
   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 2816  sub navHash { Line 3030  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 2834  resource has a title, and is otherwise t Line 3055  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 2866  Returns the symb for the resource. Line 3082  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 2880  want to use B<getNext> instead.) Line 3091  want to use B<getNext> instead.)
 sub comesfrom { my $self=shift; return $self->navHash("comesfrom_", 1); }  sub comesfrom { my $self=shift; return $self->navHash("comesfrom_", 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 2896  sub symb { Line 3108  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 title { my $self=shift; return $self->navHash("title_", 1); }  sub title { 
       my $self=shift; 
       if ($self->{ID} eq '0.0') {
    # If this is the top-level map, return the title of the course
    # since this map can not be titled otherwise.
    return $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
       }
       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 compTitle {  sub compTitle {
     my $self = shift;      my $self = shift;
Line 2958  sub is_page { Line 3178  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 is_sequence {  sub is_sequence {
     my $self=shift;      my $self=shift;
Line 3076  Get the Client IP/Name Access Control in Line 3296  Get the Client IP/Name Access Control in
   
 Get the answer-reveal date for the problem.  Get the answer-reveal date for the problem.
   
   =item * B<awarded>: 
   
   Gets the awarded value for the problem part. Requires genUserData set to
   true when the navmap object was created.
   
 =item * B<duedate>:  =item * B<duedate>:
   
 Get the due date for the problem.  Get the due date for the problem.
Line 3129  sub answerdate { Line 3354  sub answerdate {
     }      }
     return $self->parmval("answerdate", $part);      return $self->parmval("answerdate", $part);
 }  }
 sub awarded { my $self = shift; return $self->queryRestoreHash('awarded', shift); }  sub awarded { 
       my $self = shift; my $part = shift;
       $self->{NAV_MAP}->get_user_data();
       if (!defined($part)) { $part = '0'; }
       return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.awarded'};
   }
 sub duedate {  sub duedate {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("duedate", $part);      return $self->parmval("duedate", $part);
Line 3148  sub opendate { Line 3378  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 3170  sub type { Line 3400  sub type {
 }  }
 sub weight {   sub weight { 
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
     return $self->parmval("weight", $part);      if (!defined($part)) { $part = '0'; }
       return &Apache::lonnet::EXT('resource.'.$part.'.weight',
    $self->symb(), $ENV{'user.domain'},
    $ENV{'user.name'}, 
    $ENV{'request.course.sec'});
   
 }  }
   
 # Multiple things need this  # Multiple things need this
Line 3311  sub multipart { Line 3546  sub multipart {
     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_TYPE}->{$part};      if (defined($self->{RESPONSE_TYPES}->{$part})) {
    return @{$self->{RESPONSE_TYPES}->{$part}};
       } else {
    return undef;
       }
 }  }
   
 sub responseIds {  sub responseIds {
Line 3324  sub responseIds { Line 3568  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 3341  sub extractParts { Line 3589  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;  
   
         my %responseIdHash;          my %responseIdHash;
         my %responseTypeHash;          my %responseTypeHash;
Line 3378  sub extractParts { Line 3638  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 3390  sub extractParts { Line 3650  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 3398  sub extractParts { Line 3657  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;  
                     }                      }
                 }                  }
             }              }
         }          }
   
         $self->{RESPONSE_IDS} = \%responseIdHash;          $self->{RESPONSE_IDS} = \%responseIdHash;
         $self->{RESPONSE_TYPES} = \%responseTypeHash;          $self->{RESPONSE_TYPES} = \%responseTypeHash;
     }      }
Line 3596  sub getCompletionStatus { Line 3853  sub getCompletionStatus {
   
     my $status = $self->queryRestoreHash('solved', shift);      my $status = $self->queryRestoreHash('solved', shift);
   
     # Left as seperate if statements in case we ever do more with this      # Left as separate if statements in case we ever do more with this
     if ($status eq 'correct_by_student') {return $self->CORRECT;}      if ($status eq 'correct_by_student') {return $self->CORRECT;}
     if ($status eq 'correct_by_override') {return $self->CORRECT_BY_OVERRIDE; }      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; }
Line 3714  sub status { Line 3971  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->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 ||
Line 3760  sub status { Line 4023  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 3769  sub status { Line 4032  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,
         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 3800  sub completable { Line 4153  sub completable {
         # "If any of the parts are open, or have tries left (implies open),          # "If any of the parts are open, or have tries left (implies open),
         # and it is not "attempted" (manually graded problem), it is          # and it is not "attempted" (manually graded problem), it is
         # not "complete"          # not "complete"
         if (!(($status == OPEN() || $status == TRIES_LEFT())    if ($self->getCompletionStatus($part) == ATTEMPTED() ||
               && $self->getCompletionStatus($part) != ATTEMPTED()      $status == ANSWER_SUBMITTED() ) {
               && $status != ANSWER_SUBMITTED())) {      # did this part already, as well as we can
             return 0;      next;
         }   }
    if ($status == OPEN() || $status == TRIES_LEFT()) {
       return 1;
    }
     }      }
                   
     # If all the parts were complete, so was this problem.      # If all the parts were complete, so was this problem.
     return 1;      return 0;
 }  }
   
 =pod  =pod

Removed from v.1.205  
changed lines
  Added in v.1.255


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