Diff for /loncom/interface/lonnavmaps.pm between versions 1.51 and 1.66

version 1.51, 2002/09/24 01:18:50 version 1.66, 2002/10/04 20:34:04
Line 124  sub parmval { Line 124  sub parmval {
     my $courselevelm=$usercourseprefix.'.'.$mapparm;      my $courselevelm=$usercourseprefix.'.'.$mapparm;
   
 # ---------------------------------------------------------- first, check user  # ---------------------------------------------------------- first, check user
     if ($uname) {      if (defined($uname)) {
         if ($useropt{$courselevelr}) { return $useropt{$courselevelr}; }          if (defined($useropt{$courselevelr})) { return $useropt{$courselevelr}; }
         if ($useropt{$courselevelm}) { return $useropt{$courselevelm}; }          if (defined($useropt{$courselevelm})) { return $useropt{$courselevelm}; }
         if ($useropt{$courselevel}) { return $useropt{$courselevel}; }          if (defined($useropt{$courselevel})) { return $useropt{$courselevel}; }
     }      }
   
 # ------------------------------------------------------- second, check course  # ------------------------------------------------------- second, check course
     if ($csec) {      if (defined($csec)) {
         if ($courseopt{$seclevelr}) { return $courseopt{$seclevelr}; }          if (defined($courseopt{$seclevelr})) { return $courseopt{$seclevelr}; }
         if ($courseopt{$seclevelm}) { return $courseopt{$seclevelm}; }          if (defined($courseopt{$seclevelm})) { return $courseopt{$seclevelm}; }
         if ($courseopt{$seclevel}) { return $courseopt{$seclevel}; }          if (defined($courseopt{$seclevel})) { return $courseopt{$seclevel}; }
     }      }
   
     if ($courseopt{$courselevelr}) { return $courseopt{$courselevelr}; }      if (defined($courseopt{$courselevelr})) { return $courseopt{$courselevelr}; }
     if ($courseopt{$courselevelm}) { return $courseopt{$courselevelm}; }      if (defined($courseopt{$courselevelm})) { return $courseopt{$courselevelm}; }
     if ($courseopt{$courselevel}) { return $courseopt{$courselevel}; }      if (defined($courseopt{$courselevel})) { return $courseopt{$courselevel}; }
   
 # ----------------------------------------------------- third, check map parms  # ----------------------------------------------------- third, check map parms
   
     my $thisparm=$parmhash{$symbparm};      my $thisparm=$parmhash{$symbparm};
     if ($thisparm) { return $thisparm; }      if (defined($thisparm)) { return $thisparm; }
   
 # ----------------------------------------------------- fourth , check default  # ----------------------------------------------------- fourth , check default
   
     my $default=&Apache::lonnet::metadata($fn,$rwhat.'.default');      my $default=&Apache::lonnet::metadata($fn,$rwhat.'.default');
     if ($default) { return $default}      if (defined($default)) { return $default}
   
 # --------------------------------------------------- fifth , cascade up parts  # --------------------------------------------------- fifth , cascade up parts
   
Line 159  sub parmval { Line 159  sub parmval {
         my ($part,$id)=split(/\_/,$space);          my ($part,$id)=split(/\_/,$space);
         if ($id) {          if ($id) {
             my $partgeneral=&parmval($part.".$qualifier",$symb);              my $partgeneral=&parmval($part.".$qualifier",$symb);
             if ($partgeneral) { return $partgeneral; }              if (defined($partgeneral)) { return $partgeneral; }
         } else {          } else {
             my $resourcegeneral=&parmval("0.$qualifier",$symb);              my $resourcegeneral=&parmval("0.$qualifier",$symb);
             if ($resourcegeneral) { return $resourcegeneral; }              if (defined($resourcegeneral)) { return $resourcegeneral; }
         }          }
     }      }
     return '';      return '';
Line 819  sub new_handle { Line 819  sub new_handle {
     &Apache::loncommon::no_cache($r);      &Apache::loncommon::no_cache($r);
     $r->send_http_header;      $r->send_http_header;
   
     # Header  
     $r->print(&Apache::loncommon::bodytag('Navigate Course Map','',  
                                           ''));  
     # Initialize the nav map      # Initialize the nav map
     my $navmap = Apache::lonnavmaps::navmap->new(      my $navmap = Apache::lonnavmaps::navmap->new(
                         $ENV{"request.course.fn"}.".db",                          $ENV{"request.course.fn"}.".db",
                         $ENV{"request.course.fn"}."_parms.db", 1);                          $ENV{"request.course.fn"}."_parms.db", 1);
   
     # Grab a resource object so we have access to the constants; this  
     # is technically not proper, but should be harmless  
     my $res = $navmap->firstResource();  
   
     # Defines a status->color mapping, null string means don't color  
     my %colormap =   
     ( $res->NETWORK_FAILURE    => '',  
       $res->CORRECT            => '#BBFFBB',  
       $res->EXCUSED            => '#BBBBFF',  
       $res->PAST_DUE           => '#FFAA00',  
       $res->ANSWER_OPEN        => '#FF00AA',  
       $res->OPEN_LATER         => '',  
       $res->TRIES_LEFT         => '#FFFF00',  
       $res->INCORRECT          => '#FFAA00',  
       $res->OPEN               => '#FFFF88' );  
   
     if (!defined($navmap)) {      if (!defined($navmap)) {
         my $requrl = $r->uri;          my $requrl = $r->uri;
Line 849  sub new_handle { Line 831  sub new_handle {
         return HTTP_NOT_ACCEPTABLE;          return HTTP_NOT_ACCEPTABLE;
     }      }
   
       # Header
       $r->print(&Apache::loncommon::bodytag('Navigate Course Map','',
                                             ''));
       $r->print('<script>window.focus();</script>');
       my $desc=$ENV{'course.'.$ENV{'request.course.id'}.'.description'};
       if (defined($desc)) { $r->print("<h2>$desc</h2>\n"); }
       my $date=localtime;
       $r->print("<h3>$date</h3>\n");
       $r->rflush();
       if ($navmap->{LAST_CHECK}) {
           $r->print('<img src="/adm/lonMisc/chat.gif"> New discussion since '.
                     timeToHumanString($navmap->{LAST_CHECK}).
                     '<br><img src="/adm/lonMisc/feedback.gif"> New message (click to open)<p>'); 
       } else {
           $r->print('<img src="/adm/lonMisc/chat.gif"> Discussions'.
                     '<br><img src="/adm/lonMisc/feedback.gif"> New message (click to open)<p>'); 
       }
       #if (($currenturl=~/^\/res/) &&
       #    ($currenturl!~/^\/res\/adm/)) {
       #    $r->print('<a href="#curloc">Current Location</a><p>');
       #}
   
       # Check that it's defined
       if (!($navmap->courseMapDefined())) {
           $r->print('<font size="+2" color="red">Coursemap undefined.</font>' .
                     '</body></html>');
           return OK;
       }
   
       # Grab a resource object so we have access to the constants; this
       # is technically not proper, but should be harmless
       my $res = $navmap->firstResource();
   
       # Defines a status->color mapping, null string means don't color
       my %colormap = 
       ( $res->NETWORK_FAILURE        => '',
         $res->CORRECT                => '',
         $res->EXCUSED                => '#BBBBFF',
         $res->PAST_DUE_ANSWER_LATER  => '',
         $res->PAST_DUE_NO_ANSWER     => '',
         $res->ANSWER_OPEN            => '#CCFFCC',
         $res->OPEN_LATER             => '',
         $res->TRIES_LEFT             => '',
         $res->INCORRECT              => '',
         $res->OPEN                   => '',
         $res->NOTHING_SET            => ''        );
       # And a special case in the nav map; what to do when the assignment
       # is not yet done and due in less then 24 hours
       my $hurryUpColor = "#FFCCCC";
   
       my %statusIconMap = 
           ( $res->NETWORK_FAILURE    => '',
             $res->NOTHING_SET        => '',
             $res->CORRECT            => 'navmap.correct.gif',
             $res->EXCUSED            => 'navmap.correct.gif',
             $res->PAST_DUE_NO_ANSWER => 'navmap.wrong.gif',
             $res->PAST_DUE_ANSWER_LATER => 'navmap.wrong.gif',
             $res->ANSWER_OPEN        => 'navmap.wrong.gif',
             $res->OPEN_LATER         => '',
             $res->TRIES_LEFT         => 'navmap.open.gif',
             $res->INCORRECT          => 'navmap.wrong.gif',
             $res->OPEN               => 'navmap.open.gif' );
   
       my %condenseStatuses =
           ( $res->NETWORK_FAILURE    => 1,
             $res->NOTHING_SET        => 1,
             $res->CORRECT            => 1 );
   
     my %filterHash;      my %filterHash;
     # Figure out what we're not displaying      # Figure out what we're not displaying
     foreach (split(/\,/, $ENV{"form.filter"})) {      foreach (split(/\,/, $ENV{"form.filter"})) {
Line 857  sub new_handle { Line 907  sub new_handle {
         }          }
     }      }
   
       my $currenturl = $ENV{'form.postdata'};
       $currenturl=~s/^http\:\/\///;
       $currenturl=~s/^[^\/]+//;
       my $queryAdd = "postdata=" . &Apache::lonnet::escape($currenturl);
   
       $r->print('<a href="navmaps?condition=1&filter=">Show All Resources</a><br /><br />');
   
     # Begin the HTML table      # Begin the HTML table
     # two columns: the title/icon on the left, the info on the right      # four cols: resource + indent, chat+feedback, icon, text string
     $r->print('<table cellspacing="3" cellpadding="0" bgcolor="#FFFFFF">' ."\n");      $r->print('<table cellspacing="3" cellpadding="0" bgcolor="#FFFFFF">' ."\n");
   
     my $mapIterator = $navmap->getIterator(undef, undef, \%filterHash);      my $condition = 0;
       if ($ENV{'form.condition'}) {
    $condition = 1;
       }
   
       my $mapIterator = $navmap->getIterator(undef, undef, \%filterHash, $condition);
     my $curRes = $mapIterator->next();      my $curRes = $mapIterator->next();
       undef $res; # so we don't accidentally use it later
     my $indentLevel = -1;      my $indentLevel = -1;
     my $indentString = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";      my $indentString = "<img src=\"/adm/lonIcons/whitespace1.gif\" width=\"25\" height=\"1\" alt=\"\" border=\"0\" />";
   
     my $isNewBranch = 0;      my $isNewBranch = 0;
       my $now = time();
       my $in24Hours = $now + 24 * 60 * 60;
   
     while ($curRes != $mapIterator->END_NAV_MAP) {      while ($curRes != $mapIterator->END_NAV_MAP) {
         if ($curRes == $mapIterator->BEGIN_MAP() ||          if ($curRes == $mapIterator->BEGIN_MAP() ||
     $curRes == $mapIterator->BEGIN_BRANCH()) {              $curRes == $mapIterator->BEGIN_BRANCH()) {
             $indentLevel++;              $indentLevel++;
         }          }
         if ($curRes == $mapIterator->END_MAP() ||          if ($curRes == $mapIterator->END_MAP() ||
     $curRes == $mapIterator->END_BRANCH()) {              $curRes == $mapIterator->END_BRANCH()) {
             $indentLevel--;              $indentLevel--;
         }          }
           if ($curRes == $mapIterator->BEGIN_BRANCH()) {
  if ($curRes == $mapIterator->BEGIN_BRANCH()) {              $isNewBranch = 1;
     $r->print("Begin branch<br/>");  
  }  
         if ($curRes == $mapIterator->END_BRANCH()) {  
             $r->print("End branch<br/>");  
         }          }
   
         if (ref($curRes) && $curRes->src()) {          if (ref($curRes) && $curRes->src()) {
   
             my @parts;              # Step one: Decide which parts to show
               my @parts = @{$curRes->parts()};
               my $multipart = scalar(@parts) > 1;
               my $condensed = 0;
                   
             if ($curRes->is_problem()) {              if ($curRes->is_problem()) {
                 @parts = @{$curRes->parts()};                  # Is it multipart?
                 if (scalar(@parts) > 1) { shift(@parts); }  
                   if ($multipart) {
                       # If it's multipart, see if part 0 is "open"
                       # if it is, display all parts, if it isn't,
                       # just display first
                       if (!$curRes->opendate("0")) {
                           @parts = ("0"); # just display the zero-th part
    $condensed = 1;
                       } else {
                           # Otherwise, only display part 0 if we want to 
                           # attach feedback or email information to it
                           if ($curRes->hasDiscussion() || $curRes->getFeedback()) {
                               shift @parts;
                           } else {
                               # If there's discussion or feedback, that counts
                               # as a difference, so display the parts.
                               
                               # Now, we decide whether to condense the
                               # parts due to similarity
                               my $status = $curRes->status($parts[1]);
                               my $due = $curRes->duedate($parts[1]);
                               my $open = $curRes->opendate($parts[1]);
                               my $statusAllSame = 1;
                               my $dueAllSame = 1;
                               my $openAllSame = 1;
                               for (my $i = 2; $i < scalar(@parts); $i++) {
                                   if ($curRes->status($parts[$i]) != $status){
                                       $statusAllSame = 0;
                                   }
                                   if ($curRes->duedate($parts[$i]) != $due ) {
                                       $dueAllSame = 0;
                                   }
                                   if ($curRes->opendate($parts[$i]) != $open) {
                                       $openAllSame = 0;
                                   }
                               }
                               
                               # $allSame is true if all the statuses were
                               # the same. Now, if they are all the same and
                               # match one of the statuses to condense, or they
                               # are all open with the same due date, or they are
                               # all OPEN_LATER with the same open date, display the
                               # status of the first non-zero part (to get the 'correct'
                               # status right, since 0 is never 'correct' or 'open').
                               if (($statusAllSame && defined($condenseStatuses{$status})) ||
                                   ($dueAllSame && $status == $curRes->OPEN && $statusAllSame)||
                                   ($openAllSame && $status == $curRes->OPEN_LATER && $statusAllSame) ){
                                   @parts = ($parts[1]);
                                   $condensed = 1;
                               }
                           }
                       }
                   }
   
             } else {              } else {
                 @parts[0] = "0"; # this is to get past foreach loop below                  @parts[0] = "0"; # this is to get past foreach loop below
                  # you can consider a non-problem resource as a resource                   # you can consider a non-problem resource as a resource
                  # with only one part without loss                   # with only one part without loss
             }              }
   
               # Display one part, in event of network error.
               # If this is a single part, we can at least show the correct
               # status, but if it's multipart, we're lost.
               if ($curRes->{RESOURCE_ERROR}) {
                   @parts = ("0");
               }
   
               # Step two: Show the parts
             foreach my $part (@parts) {              foreach my $part (@parts) {
   
                   my $deltalevel = 0; # for inserting the branch icon
                                   
                 # For each thing we're displaying...                  # For each thing we're displaying...
   
Line 911  sub new_handle { Line 1039  sub new_handle {
                     '"';                      '"';
                 my $title = $curRes->title();                  my $title = $curRes->title();
                 my $partLabel = "";                  my $partLabel = "";
                   my $newBranchText = "";
   
                   # If this is a new branch, label it so
                   # (temporary, this should be an icon w/ alt text)
                   if ($isNewBranch) {
                       $newBranchText = "<img src=\"/adm/lonIcons/branch.gif\" border=\"0\">";
                       $isNewBranch = 0;
                       $deltalevel = 1;
                   }
   
                 # links to open and close the folders                  # links to open and close the folders
                 my $linkopen = "<a href=\"$link\">";                  my $linkopen = "<a href=\"$link\">";
                 my $linkclose = "</a>";                  my $linkclose = "</a>";
   
                 my $icon = "html.gif";                  my $icon = "<img src=\"/adm/lonIcons/html.gif\" alt=\"\" border=\"0\" />";
                 if ($curRes->is_problem()) {                   if ($curRes->is_problem()) { 
                     $icon = "problem.gif";                       if ($part eq "0" || $condensed) {
                           $icon = '<img src="/adm/lonIcons/problem.gif" alt="" border=\"0\" />'; 
                       } else {
                           $icon = $indentString;
                       }
                 }                  }
   
                 # Display the correct icon, link to open or shut map                  # Display the correct icon, link to open or shut map
Line 926  sub new_handle { Line 1067  sub new_handle {
                     my $mapId = $curRes->map_pc();                      my $mapId = $curRes->map_pc();
                     my $nowOpen = !defined($filterHash{$mapId});                      my $nowOpen = !defined($filterHash{$mapId});
                     $icon = $nowOpen ?                      $icon = $nowOpen ?
                         "folder_opened.gif" : "folder_closed.gif";                          "folder_closed.gif" : "folder_opened.gif";
                     $linkopen = "<a href=\"/adm/navmaps?jtest=1&filter=";                      $icon = "<img src=\"/adm/lonIcons/$icon\" alt=\"\" border=\"0\" />";
                     $linkopen .= $nowOpen ?                       $linkopen = "<a href=\"/adm/navmaps?filter=";
                       $linkopen .= ($nowOpen xor $condition) ? 
                         addToFilter(\%filterHash, $mapId) :                          addToFilter(\%filterHash, $mapId) :
                         removeFromFilter(\%filterHash, $mapId);                          removeFromFilter(\%filterHash, $mapId);
                     $linkopen .= "\">";                      $linkopen .= "&condition=$condition&$queryAdd\">";
                     $linkclose = "</a>";                      $linkclose = "</a>";
                                           
                 }                  }
Line 945  sub new_handle { Line 1087  sub new_handle {
                     }                      }
                 }                  }
   
                 $r->print("  <tr><td align=\"left\" valign=\"top\">\n");                  # FIRST COL: The resource indentation, branch icon, and name
                   $r->print("  <tr><td align=\"left\" valign=\"bottom\">\n");
   
                 # print indentation                  # print indentation
                 for (my $i = 0; $i < $indentLevel; $i++) {                  for (my $i = 0; $i < $indentLevel - $deltalevel; $i++) {
                     $r->print($indentString);                      $r->print($indentString);
                 }                  }
   
                 $r->print("  ${linkopen}<img border=\"0\" src=\"" .                  $r->print("  ${newBranchText}${linkopen}$icon${linkclose}\n");
                           "/adm/lonIcons/$icon\" alt=\"\">${linkclose}\n");  
    my $nonLinkedText = "";
   
                   if ($curRes->is_problem() && $part != "0" && !$condensed) { 
                       $partLabel = " (Part $part)"; 
                       $title = "";
                   }
                   if ($multipart && $condensed) {
                       $nonLinkedText = ' (' . $curRes->countParts() . ' parts)';
                   }
   
                   $r->print("  <a href=\"$link\">$title$partLabel</a> $nonLinkedText");
   
                   if ($curRes->{RESOURCE_ERROR}) {
                       $r->print(&Apache::loncommon::help_open_topic ("Navmap_Host_Down",
                                                     '<font size="-1">Host down</font>'));
                       }
   
                   my $discussionHTML = ""; my $feedbackHTML = "";
   
                   # SECOND COL: Is there text or feedback?
                   if ($curRes->hasDiscussion()) {
                       $discussionHTML = $linkopen .
                           '<img border="0" src="/adm/lonMisc/chat.gif" />' .
                           $linkclose;
                   }
   
                   if ($curRes->getFeedback()) {
                       my $feedback = $curRes->getFeedback();
                       foreach (split(/\,/, $feedback)) {
                           if ($_) {
                               $feedbackHTML .= '&nbsp;<a href="/adm/email?display='
                                   . &Apache::lonnet::escape($_) . '">'
                                   . '<img src="/adm/lonMisc/feedback.gif" '
                                   . 'border="0" /></a>';
                           }
                       }
                   }
   
                   $r->print("<td align=\"left\" valign=\"bottom\">$discussionHTML$feedbackHTML</td>");
   
                 if ($curRes->is_problem() && $part != "0") { $partLabel = " (Part $part)"; }                  # Is this the first displayed part of a multi-part problem
                   # that has not been condensed, so we should suppress these two
                   # columns?
                   my $firstDisplayed = !$condensed && $multipart && $part eq "0";
   
                   # THIRD ROW: Problem status icon
                   if ($curRes->is_problem() &&
                       !$firstDisplayed) {
                       my $icon = $statusIconMap{$curRes->status($part)};
                       if ($icon) {
                           $r->print("<td valign=\"bottom\" width=\"50\" align=\"right\">$linkopen<img src=\"/adm/lonIcons/$icon\" border=\"0\" />$linkclose</td>\n");
                       } else {
                           $r->print("<td></td>\n");
                       }
                   } else { # not problem, no icon
                       $r->print("<td></td>\n");
                   }
   
                 $r->print("  <a href=\"$link\">$title$partLabel</a></td>".                  # FOURTH ROW: Text description
                           "<td $colorizer align=\"right\">\n");                  $r->print("<td $colorizer align=\"right\" valign=\"bottom\">\n");
                                   
                 if ($curRes->kind() eq "res" and                   if ($curRes->kind() eq "res" &&
                     $curRes->is_problem() ) {                      $curRes->is_problem() &&
                     $r->print (" Due: " . localtime($curRes->duedate()));                      !$firstDisplayed) {
                       $r->print (getDescription($curRes, $part));
                 }                  }
   
                   $r->print("</td></tr>\n");
             }              }
         }          }
         $curRes = $mapIterator->next();          $curRes = $mapIterator->next();
Line 971  sub new_handle { Line 1172  sub new_handle {
   
     $r->print("</table></body></html>");      $r->print("</table></body></html>");
   
       $navmap->untieHashes();
   
     return OK;      return OK;
 }  }
   
Line 1020  sub getLinkForResource { Line 1223  sub getLinkForResource {
     return $res->src();      return $res->src();
 }  }
   
   # Convenience function: This seperates the logic of how to create
   # the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned",
   # etc.) into a seperate function. It takes a resource object as the
   # first parameter, and the part number of the resource as the second.
   # It's basically a big switch statement on the status of the resource.
   
   sub getDescription {
       my $res = shift;
       my $part = shift;
       my $status = $res->getDateStatus();
   
       if ($status == $res->NETWORK_FAILURE) { return ""; }
       if ($status == $res->NOTHING_SET) {
           return "Not currently assigned.";
       }
       if ($status == $res->OPEN_LATER) {
           return "Opens: " . timeToHumanString($res->opendate($part));
       }
       if ($status == $res->OPEN) {
           if ($res->duedate()) {
               return "Due: $status " . timeToHumanString($res->duedate($part));
           } else {
               return "Open, no due date";
           }
       }
       if ($status == $res->PAST_DUE_ANSWER_LATER) {
           return "Answer opens: " . timeToHumanString($res->answerdate($part));
       }
       if ($status == $res->PAST_DUE_NO_ANSWER) {
           return "Was Due: " . timeToHumanString($res->duedate($part));
       }
       if ($status == $res->ANSWER_OPEN) {
           return "Answer available";
       }
       if ($status == $res->EXCUSED) {
           return "Excused by instructor";
       }
       if ($status == $res->TRIES_LEFT) {
           my $tries = $res->tries();
           my $maxtries = $res->maxtries();
           my $triesString = "($tries of $maxtries tries used)";
           if ($res->duedate()) {
               return "Due: $status " . timeToHumanString($res->duedate($part)) .
                   " $triesString";
           } else {
               return "No due date $triesString";
           }
       }
   }
   
   # I want to change this into something more human-friendly. For
   # now, this is a simple call to localtime. The final function
   # probably belongs in loncommon.
   sub timeToHumanString {
       my ($time) = @_;
       # zero, '0' and blank are bad times
       if ($time) {
           return localtime($time);
       } else {
           return 'Never';
       }
   }
   
 1;  1;
   
 package Apache::lonnavmaps::navmap;  package Apache::lonnavmaps::navmap;
Line 1044  You must obtain resource objects through Line 1310  You must obtain resource objects through
   
 =over 4  =over 4
   
 =item * B<new>(filename, parmHashFile, genCourseAndUserOptions): Binds a new navmap object to the compiled course representation and parmHashFile. genCourseAndUserOptions is a flag saying whether the course options and user options hash should be generated. This is for when you are using the parameters of the resources that require them; see documentation in resource object documentation. Returns the navmap object if this is successful, or B<undef> if not. You must check for undef; errors will occur when you try to use the other methods otherwise.  =item * B<new>(filename, parmHashFile, genCourseAndUserOptions, genMailDiscussStatus): Binds a new navmap object to the compiled course representation and parmHashFile. genCourseAndUserOptions is a flag saying whether the course options and user options hash should be generated. This is for when you are using the parameters of the resources that require them; see documentation in resource object documentation. genMailDiscussStatus causes the nav map to retreive information about the email and discussion status of resources. Returns the navmap object if this is successful, or B<undef> if not. You must check for undef; errors will occur when you try to use the other methods otherwise.
   
 =item * B<getIterator>(first, finish, filter, condition): See iterator documentation below.  =item * B<getIterator>(first, finish, filter, condition): See iterator documentation below.
   
Line 1062  sub new { Line 1328  sub new {
     $self->{NAV_HASH_FILE} = shift;      $self->{NAV_HASH_FILE} = shift;
     $self->{PARM_HASH_FILE} = shift;      $self->{PARM_HASH_FILE} = shift;
     $self->{GENERATE_COURSE_USER_OPT} = shift;      $self->{GENERATE_COURSE_USER_OPT} = shift;
       $self->{GENERATE_EMAIL_DISCUSS_STATUS} = shift;
   
     # Resource cache stores navmapresource's as we reference them. We generate      # Resource cache stores navmapresource's 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.
Line 1083  sub new { Line 1350  sub new {
     if (!(tie(%parmhash, 'GDBM_File', $self->{PARM_HASH_FILE},      if (!(tie(%parmhash, 'GDBM_File', $self->{PARM_HASH_FILE},
               &GDBM_READER(), 0640)))                &GDBM_READER(), 0640)))
     {      {
           untie $self->{PARM_HASH};
         return undef;          return undef;
     }      }
     $self->{PARM_HASH} = \%parmhash;      $self->{PARM_HASH} = \%parmhash;
Line 1139  sub new { Line 1407  sub new {
                 $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}) {
           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;
           
       }    
       
     bless($self);      bless($self);
           
     return $self;      return $self;
 }  }
   
   # Checks to see if coursemap is defined, matching test in old lonnavmaps
   sub courseMapDefined {
       my $self = shift;
       my $uri = &Apache::lonnet::clutter($ENV{'request.course.uri'});
   
       my $firstres = $self->{NAV_HASH}->{"map_start_$uri"};
       my $lastres = $self->{NAV_HASH}->{"map_finish_$uri"};
       return $firstres && $lastres;
   }
   
 sub getIterator {  sub getIterator {
     my $self = shift;      my $self = shift;
     my $iterator = Apache::lonnavmaps::iterator->new($self, shift, shift,      my $iterator = Apache::lonnavmaps::iterator->new($self, shift, shift,
                                                      shift, shift);                                                       shift, undef, shift);
     return $iterator;      return $iterator;
 }  }
   
 # unties the hash when done  # unties the hash when done
 sub untieHashes {  sub untieHashes {
     my $self = shift;      my $self = shift;
     untie $self->{NAV_HASH} if ($self->{HASH_TIED});      untie %{$self->{NAV_HASH}} if ($self->{HASH_TIED});
     untie $self->{PARM_HASH} if ($self->{HASH_TIED});      untie %{$self->{PARM_HASH}} if ($self->{HASH_TIED});
     $self->{HASH_TIED} = 0;      $self->{HASH_TIED} = 0;
 }  }
   
Line 1170  sub DESTROY { Line 1495  sub DESTROY {
     $self->untieHashes();      $self->untieHashes();
 }  }
   
   # Private function: Does the given resource (as a symb string) have
   # current discussion? Returns 0 if chat/mail data not extracted.
   sub hasDiscussion {
       my $self = shift;
       my $symb = shift;
       if (!defined($self->{DISCUSSION_TIME})) { return 0; }
   
       return $self->{DISCUSSION_TIME}->{$symb} >
              $self->{LAST_CHECK};
   }
   
   # Private function: Does the given resource (as a symb string) have
   # current feedback? Returns the string in the feedback hash, which
   # will be false if it does not exist.
   sub getFeedback { 
       my $self = shift;
       my $symb = shift;
   
       if (!defined($self->{FEEDBACK})) { return ""; }
       
       return $self->{FEEDBACK}->{$symb};
   }
   
 =pod  =pod
   
 =item * B<getById>(id): Based on the ID of the resource (1.1, 3.2, etc.), get a resource object for that resource. This method, or other methods that use it (as in the resource object) is the only proper way to obtain a resource object.  =item * B<getById>(id): Based on the ID of the resource (1.1, 3.2, etc.), get a resource object for that resource. This method, or other methods that use it (as in the resource object) is the only proper way to obtain a resource object.
Line 1264  sub parmval { Line 1612  sub parmval {
   
 # ---------------------------------------------------------- first, check user  # ---------------------------------------------------------- first, check user
     if ($uname and defined($useropt)) {      if ($uname and defined($useropt)) {
         if ($$useropt{$courselevelr}) { return $$useropt{$courselevelr}; }          if (defined($$useropt{$courselevelr})) { return $$useropt{$courselevelr}; }
         if ($$useropt{$courselevelm}) { return $$useropt{$courselevelm}; }          if (defined($$useropt{$courselevelm})) { return $$useropt{$courselevelm}; }
         if ($$useropt{$courselevel}) { return $$useropt{$courselevel}; }          if (defined($$useropt{$courselevel})) { return $$useropt{$courselevel}; }
     }      }
   
 # ------------------------------------------------------- second, check course  # ------------------------------------------------------- second, check course
     if ($csec and defined($courseopt)) {      if ($csec and defined($courseopt)) {
         if ($$courseopt{$seclevelr}) { return $$courseopt{$seclevelr}; }          if (defined($$courseopt{$seclevelr})) { return $$courseopt{$seclevelr}; }
         if ($$courseopt{$seclevelm}) { return $$courseopt{$seclevelm}; }          if (defined($$courseopt{$seclevelm})) { return $$courseopt{$seclevelm}; }
         if ($$courseopt{$seclevel}) { return $$courseopt{$seclevel}; }          if (defined($$courseopt{$seclevel})) { return $$courseopt{$seclevel}; }
     }      }
   
     if (defined($courseopt)) {      if (defined($courseopt)) {
         if ($$courseopt{$courselevelr}) { return $$courseopt{$courselevelr}; }          if (defined($$courseopt{$courselevelr})) { return $$courseopt{$courselevelr}; }
         if ($$courseopt{$courselevelm}) { return $$courseopt{$courselevelm}; }          if (defined($$courseopt{$courselevelm})) { return $$courseopt{$courselevelm}; }
         if ($$courseopt{$courselevel}) { return $$courseopt{$courselevel}; }          if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; }
     }      }
   
 # ----------------------------------------------------- third, check map parms  # ----------------------------------------------------- third, check map parms
   
     my $thisparm=$$parmhash{$symbparm};      my $thisparm=$$parmhash{$symbparm};
     if ($thisparm) { return $thisparm; }      if (defined($thisparm)) { return $thisparm; }
   
 # ----------------------------------------------------- fourth , check default  # ----------------------------------------------------- fourth , check default
   
     my $default=&Apache::lonnet::metadata($fn,$rwhat.'.default');      my $default=&Apache::lonnet::metadata($fn,$rwhat.'.default');
     if ($default) { return $default}      if (defined($default)) { return $default}
   
 # --------------------------------------------------- fifth , cascade up parts  # --------------------------------------------------- fifth , cascade up parts
   
Line 1300  sub parmval { Line 1648  sub parmval {
         my ($part,$id)=split(/\_/,$space);          my ($part,$id)=split(/\_/,$space);
         if ($id) {          if ($id) {
             my $partgeneral=$self->parmval($part.".$qualifier",$symb);              my $partgeneral=$self->parmval($part.".$qualifier",$symb);
             if ($partgeneral) { return $partgeneral; }              if (defined($partgeneral)) { return $partgeneral; }
         } else {          } else {
             my $resourcegeneral=$self->parmval("0.$qualifier",$symb);              my $resourcegeneral=$self->parmval("0.$qualifier",$symb);
             if ($resourcegeneral) { return $resourcegeneral; }              if (defined($resourcegeneral)) { return $resourcegeneral; }
         }          }
     }      }
     return '';      return '';
Line 1338  getIterator behaves as follows: Line 1686  getIterator behaves as follows:
   
 =over 4  =over 4
   
 =item B<getIterator>(nav_map, firstResource, finishResource, filterHash): All parameters are optional. firstResource is a resource reference corresponding to where the iterator should start. It defaults to navmap->firstResource() for the corresponding nav map. finishResource corresponds to where you want the iterator to end, defaulting to navmap->finishResource(). It is your responsibility to ensure that the iterator will actually get there. filterHash is a hash used as a set containing strings representing the resource IDs, defaulting to empty. Condition is a 1 or 0 that sets what to do with the filter hash: If a 1, then only resource that exist IN the filterHash will be recursed on. If it is a 0, only resources NOT in the filterHash will be recursed on. Defaults to 0.  =item B<getIterator>(nav_map, firstResource, finishResource, filterHash, condition): All parameters are optional. firstResource is a resource reference corresponding to where the iterator should start. It defaults to navmap->firstResource() for the corresponding nav map. finishResource corresponds to where you want the iterator to end, defaulting to navmap->finishResource(). It is your responsibility to ensure that the iterator will actually get there. filterHash is a hash used as a set containing strings representing the resource IDs, defaulting to empty. Condition is a 1 or 0 that sets what to do with the filter hash: If a 0, then only resource that exist IN the filterHash will be recursed on. If it is a 1, only resources NOT in the filterHash will be recursed on. Defaults to 0, which is to say, do not recurse unless explicitly asked to.
   
 Thus, by default, all resources will be shown. Change the condition to a 1 without changing the hash, and only the top level of the map will be shown. Changing the condition to 1 and including some values in the hash will allow you to selectively examine parts of the navmap, while leaving it on 0 and adding things to the hash will allow you to selectively ignore parts of the nav map. See the handler code for examples.  Thus, by default, all resources will be shown. Change the condition to a 1 without changing the hash, and only the top level of the map will be shown. Changing the condition to 1 and including some values in the hash will allow you to selectively examine parts of the navmap, while leaving it on 0 and adding things to the hash will allow you to selectively ignore parts of the nav map. See the handler code for examples.
   
Line 1390  sub new { Line 1738  sub new {
     # A hash, used as a set, of resource already seen      # A hash, used as a set, of resource already seen
     $self->{ALREADY_SEEN} = shift;      $self->{ALREADY_SEEN} = shift;
     if (!defined($self->{ALREADY_SEEN})) { $self->{ALREADY_SEEN} = {} };      if (!defined($self->{ALREADY_SEEN})) { $self->{ALREADY_SEEN} = {} };
       $self->{CONDITION} = shift;
   
     # Flag: Have we started yet? If not, the first action is to return BEGIN_MAP.      # Flag: Have we started yet? If not, the first action is to return BEGIN_MAP.
     $self->{STARTED} = 0;      $self->{STARTED} = 0;
Line 1460  sub next { Line 1809  sub next {
     if (scalar(@{$self->{BRANCH_STACK}}) == 0) {      if (scalar(@{$self->{BRANCH_STACK}}) == 0) {
         if ($self->{BRANCH_DEPTH} > 0) {          if ($self->{BRANCH_DEPTH} > 0) {
             $self->{FORCE_NEXT} = $self->END_MAP();              $self->{FORCE_NEXT} = $self->END_MAP();
     $self->{BRANCH_DEPTH}--;              $self->{BRANCH_DEPTH}--;
             return $self->END_BRANCH();              return $self->END_BRANCH();
         } else {          } else {
             return $self->END_MAP();              return $self->END_MAP();
Line 1479  sub next { Line 1828  sub next {
     # to start a new one. (We know because we already checked to see      # to start a new one. (We know because we already checked to see
     # if the stack was empty.)      # if the stack was empty.)
     if ( scalar (@{$self->{BRANCH_STACK}}) < $self->{BRANCH_STACK_SIZE}) {      if ( scalar (@{$self->{BRANCH_STACK}}) < $self->{BRANCH_STACK_SIZE}) {
  $self->{BRANCH_STACK_SIZE} = scalar(@{$self->{BRANCH_STACK}});          $self->{BRANCH_STACK_SIZE} = scalar(@{$self->{BRANCH_STACK}});
  $self->{BRANCH_DEPTH}++;          $self->{BRANCH_DEPTH}++;
  return $self->BEGIN_BRANCH();          return $self->BEGIN_BRANCH();
     }      }
   
       # Remember the size for comparision next time.
       $self->{BRANCH_STACK_SIZE} = scalar(@{$self->{BRANCH_STACK}});
   
       # If the next resource we mean to return is going to need
       # a lower branch level, terminate branches until we get 
       # there.
   
     # Get the next resource in the branch      # Get the next resource in the branch
     $self->{HERE} = pop @{$self->{BRANCH_STACK}};      $self->{HERE} = pop @{$self->{BRANCH_STACK}};
   
       # Are we at the right depth? If not, close a branch and return
       # the current resource onto the branch stack
       if (defined($self->{HERE}->{DATA}->{ITERATOR_DEPTH})
           && $self->{HERE}->{DATA}->{ITERATOR_DEPTH} <
           $self->{BRANCH_DEPTH} ) {
           $self->{BRANCH_DEPTH}--;
           # return it so we can pick it up eventually
           push @{$self->{BRANCH_STACK}}, $self->{HERE};
           return $self->END_BRANCH();
       }
   
     # We always return it after this point and never before      # We always return it after this point and never before
     # (proof: look at just the return statements), so we      # (proof: look at just the return statements), so we
     # remember that we've seen this.      # remember that we've seen this.
Line 1493  sub next { Line 1861  sub next {
           
     # Are we at the utter end? If so, return the END_NAV_MAP marker.      # Are we at the utter end? If so, return the END_NAV_MAP marker.
     if ($self->{HERE} == $self->{NAV_MAP}->finishResource() ) {      if ($self->{HERE} == $self->{NAV_MAP}->finishResource() ) {
  $self->{FORCE_NEXT} = $self->END_NAV_MAP;          $self->{FORCE_NEXT} = $self->END_NAV_MAP;
         return $self->{HERE};          return $self->{HERE};
     }      }
           
     # Remember the size for comparision next time.  
     $self->{BRANCH_STACK_SIZE} = scalar(@{$self->{BRANCH_STACK}});  
   
     # Get the next possible resources      # Get the next possible resources
     my $nextUnfiltered = $self->{HERE}->getNext();      my $nextUnfiltered = $self->{HERE}->getNext();
     my $next = [];      my $next = [];
   
     # filter the next possibilities to remove things we've       # filter the next possibilities to remove things we've 
     # already seen      # already seen. Also, remember what branch depth they should
       # be displayed at, since there's no other reliable way to tell.
     foreach (@$nextUnfiltered) {      foreach (@$nextUnfiltered) {
  if (!defined($self->{ALREADY_SEEN}->{$_->{ID}})) {          if (!defined($self->{ALREADY_SEEN}->{$_->{ID}})) {
     push @$next, $_;              push @$next, $_;
  }              $_->{DATA}->{ITERATOR_DEPTH} = 
                   $self->{BRANCH_DEPTH} + 1;
           }
     }      }
   
     # Handle branch cases:      # Handle branch cases:
Line 1518  sub next { Line 1886  sub next {
     # 2+ things next: have some branches      # 2+ things next: have some branches
     my $nextCount = scalar(@$next);      my $nextCount = scalar(@$next);
     if ($nextCount == 0) {      if ($nextCount == 0) {
  # Return this and on the next run, close the branch up if we're           # Return this and on the next run, close the branch up if we're 
  # in a branch          # in a branch
  if ($self->{BRANCH_DEPTH} > 0 ) {          if ($self->{BRANCH_DEPTH} > 0 ) {
     $self->{FORCE_NEXT} = $self->END_BRANCH();              $self->{FORCE_NEXT} = $self->END_BRANCH();
     $self->{BRANCH_DEPTH}--;              $self->{BRANCH_DEPTH}--;
  }          }
         return $self->{HERE};          return $self->{HERE};
     }      }
           
     while (@$next) {      while (@$next) {
         # copy the next possibilities over to the branch stack          # copy the next possibilities over to the branch stack
         # in the right order          # in the right order
  push @{$self->{BRANCH_STACK}}, shift @$next;          push @{$self->{BRANCH_STACK}}, shift @$next;
     }      }
   
     if ($nextCount >= 2) {      if ($nextCount >= 2) {
  $self->{FORCE_NEXT} = $self->BEGIN_BRANCH();          $self->{FORCE_NEXT} = $self->BEGIN_BRANCH();
  $self->{BRANCH_DEPTH}++;          $self->{BRANCH_DEPTH}++;
         return $self->{HERE};           return $self->{HERE}; 
     }      }
   
     # If this is a map and we want to recurse down it... (not filtered out)      # If this is a map and we want to recurse down it... (not filtered out)
     if ($self->{HERE}->is_map() &&       if ($self->{HERE}->is_map() && 
          !defined($self->{FILTER}->{$self->{HERE}->map_pc()})) {            (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) { 
         $self->{RECURSIVE_ITERATOR_FLAG} = 1;          $self->{RECURSIVE_ITERATOR_FLAG} = 1;
         my $firstResource = $self->{HERE}->map_start();          my $firstResource = $self->{HERE}->map_start();
         my $finishResource = $self->{HERE}->map_finish();          my $finishResource = $self->{HERE}->map_finish();
Line 1551  sub next { Line 1919  sub next {
         # isn't MAIN::new, __PACKAGE__::new or Apache::lonnavmaps::iterator->new          # isn't MAIN::new, __PACKAGE__::new or Apache::lonnavmaps::iterator->new
         $self->{RECURSIVE_ITERATOR} =          $self->{RECURSIVE_ITERATOR} =
           Apache::lonnavmaps::iterator->new ($self->{NAV_MAP}, $firstResource,             Apache::lonnavmaps::iterator->new ($self->{NAV_MAP}, $firstResource, 
                      $finishResource, $self->{FILTER}, $self->{ALREADY_SEEN});                       $finishResource, $self->{FILTER}, $self->{ALREADY_SEEN},
  # prime the new iterator with the first resource                                               $self->{CONDITION});
  #push @{$self->{RECURSIVE_ITERATOR}->{BRANCH_STACK}}, $firstResource;  
     }      }
   
     return $self->{HERE};      return $self->{HERE};
Line 1626  sub new { Line 1993  sub new {
   
     # Store this new resource in the parent nav map's cache.      # Store this new resource in the parent nav map's cache.
     $self->{NAV_MAP}->{RESOURCE_CACHE}->{$self->{ID}} = $self;      $self->{NAV_MAP}->{RESOURCE_CACHE}->{$self->{ID}} = $self;
       $self->{RESOURCE_ERROR} = 0;
   
     # A hash that can be used by two-pass algorithms to store data      # A hash that can be used by two-pass algorithms to store data
     # about this resource in. Not used by the resource object      # about this resource in. Not used by the resource object
Line 1883  Misc. functions for the resource. Line 2251  Misc. functions for the resource.
   
 =over 4  =over 4
   
   =item * B<hasDiscussion>: Returns a false value if there has been discussion since the user last logged in, true if there has. Always returns false if the discussion data was not extracted when the nav map was constructed.
   
   =item * B<getFeedback>: Gets the feedback for the resource and returns the raw feedback string for the resource, or the null string if there is no feedback or the email data was not extracted when the nav map was constructed. Usually used like this:
   
    for (split(/\,/, $res->getFeedback())) {
       my $link = &Apache::lonnet::escape($_);
       ...
   
   and use the link as appropriate.
   
   =cut
   
   sub hasDiscussion {
       my $self = shift;
       return $self->{NAV_MAP}->hasDiscussion($self->symb());
   }
   
   sub getFeedback {
       my $self = shift;
       return $self->{NAV_MAP}->getFeedback($self->symb());
   }
   
   =pod
   
 =item * B<parts>(): Returns a list reference containing sorted strings corresponding to each part of the problem. To count the number of parts, use the list in a scalar context, and subtract one if greater then two. (One part problems have a part 0. Multi-parts have a part 0, plus a part for each part. You may or may not wish to filter out part 0.)  =item * B<parts>(): Returns a list reference containing sorted strings corresponding to each part of the problem. To count the number of parts, use the list in a scalar context, and subtract one if greater then two. (One part problems have a part 0. Multi-parts have a part 0, plus a part for each part. You may or may not wish to filter out part 0.)
   
 =item * B<countParts>(): Returns the number of parts of the problem a student can answer. Thus, for single part problems, returns 1. For multipart, it returns the number of parts in the problem, not including psuedo-part 0.  =item * B<countParts>(): Returns the number of parts of the problem a student can answer. Thus, for single part problems, returns 1. For multipart, it returns the number of parts in the problem, not including psuedo-part 0.
Line 1902  sub countParts { Line 2294  sub countParts {
     my $self = shift;      my $self = shift;
           
     my $parts = $self->parts();      my $parts = $self->parts();
       
     if (scalar(@{$parts}) == 1) { return 1;}      if ($self->{RESOURCE_ERROR}) {
           return 0;
       }
   
       if (scalar(@{$parts}) < 2) { return 1;}
   
     return scalar(@{$parts}) - 1;      return scalar(@{$parts}) - 1;
 }  }
Line 1917  sub extractParts { Line 2313  sub extractParts {
     $self->{PARTS} = [];      $self->{PARTS} = [];
   
     # Retrieve part count      # Retrieve part count
     foreach (split(/\,/,&Apache::lonnet::metadata($self->src(),       my $metadata = &Apache::lonnet::metadata($self->src(), 'allpossiblekeys');
                                                   'allpossiblekeys'))) {      if (!$metadata) {
           $self->{RESOURCE_ERROR} = 1;
           $self->{PARTS} = [];
           return;
       }
   
       foreach (split(/\,/,$metadata)) {
         if ($_ =~ /^parameter\_(.*)\_opendate$/) {          if ($_ =~ /^parameter\_(.*)\_opendate$/) {
             push @{$self->{PARTS}}, $1;              push @{$self->{PARTS}}, $1;
         }          }
     }      }
   
           
     # Is this possible to do in one line? - Jeremy      # Is this possible to do in one line? - Jeremy
     my @sortedParts = sort @{$self->{PARTS}};      my @sortedParts = sort @{$self->{PARTS}};
Line 1950  Idiomatic usage of these two methods wou Line 2353  Idiomatic usage of these two methods wou
   
 =over 4  =over 4
   
 =item * B<getDateStatus>($part): ($part defaults to 0). A convenience function that returns a symbolic constant telling you about the date status of the part, with the following possible results:  =item * B<getDateStatus>($part): ($part defaults to 0). A convenience function that returns a symbolic constant telling you about the date status of the part. You should still check to see if there is a due date at all, if you care about that. The possible return values are:
   
 =back  =back
   
Line 1962  B<Date Codes> Line 2365  B<Date Codes>
   
 =item * B<OPEN>: Open and not yet due.  =item * B<OPEN>: Open and not yet due.
   
 =item * B<PAST_DUE>: The due date has passed, but the answer date has not yet arrived.  
   =item * B<PAST_DUE_ANSWER_LATER>: The due date has passed, but the answer date has not yet arrived.
   
   =item * B<PAST_DUE_NO_ANSWER>: The due date has passed and there is no answer opening date set.
   
 =item * B<ANSWER_OPEN>: The answer date is here.  =item * B<ANSWER_OPEN>: The answer date is here.
   
Line 1973  B<Date Codes> Line 2379  B<Date Codes>
 =cut  =cut
   
 # Apparently the compiler optimizes these into constants automatically  # Apparently the compiler optimizes these into constants automatically
 sub OPEN_LATER      { return 0; }  sub OPEN_LATER             { return 0; }
 sub OPEN            { return 1; }  sub OPEN                   { return 1; }
 sub PAST_DUE        { return 2; }  sub PAST_DUE_NO_ANSWER     { return 2; }
 sub ANSWER_OPEN     { return 3; }  sub PAST_DUE_ANSWER_LATER  { return 3; }
 sub NETWORK_FAILURE { return 100; }  sub ANSWER_OPEN            { return 4; }
   sub NOTHING_SET            { return 5; } 
   sub NETWORK_FAILURE        { return 100; }
   
   # getDateStatus gets the date status for a given problem part. 
   # Because answer date, due date, and open date are fully independent
   # (i.e., it is perfectly possible to *only* have an answer date), 
   # we have to completely cover the 3x3 maxtrix of (answer, due, open) x
   # (past, future, none given). This function handles this with a decision
   # tree. Read the comments to follow the decision tree.
   
 sub getDateStatus {  sub getDateStatus {
     my $self = shift;      my $self = shift;
     my $part = shift;      my $part = shift;
     $part = "0" if (!defined($part));      $part = "0" if (!defined($part));
   
       # Always return network failure if there was one.
     return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE});      return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE});
   
     my $now = time();      my $now = time();
   
     my $o = $now - $self->opendate($part);      my $open = $self->opendate($part);
     my $d = $now - $self->duedate($part);      my $due = $self->duedate($part);
     my $a = $now - $self->answerdate($part);      my $answer = $self->answerdate($part);
   
     if ($o < 0) {return $self->OPEN_LATER};      if (!$open && !$due && !$answer) {
     if ($d < 0) {return $self->OPEN};          # no data on the problem at all
     if ($a < 0) {return $self->PAST_DUE};          # should this be the same as "open later"? think multipart.
     return $self->ANSWER_OPEN;          return $self->NOTHING_SET;
       }
       if (!$open || $now < $open) {return $self->OPEN_LATER}
       if (!$due || $now < $due) {return $self->OPEN}
       if ($answer && $now < $answer) {return $self->PAST_DUE_ANSWER_LATER}
       if ($answer) { return $self->ANSWER_OPEN; }
       return PAST_DUE_NO_ANSWER;
 }  }
   
 =pod  =pod
Line 2029  B<Completion Codes> Line 2452  B<Completion Codes>
   
 =cut  =cut
   
 sub NOT_ATTEMPTED         { return 0; }  sub NOT_ATTEMPTED         { return 10; }
 sub INCORRECT             { return 1; }  sub INCORRECT             { return 11; }
 sub INCORRECT_BY_OVERRIDE { return 2; }  sub INCORRECT_BY_OVERRIDE { return 12; }
 sub CORRECT               { return 3; }  sub CORRECT               { return 13; }
 sub CORRECT_BY_OVERRIDE   { return 4; }  sub CORRECT_BY_OVERRIDE   { return 14; }
 sub EXCUSED               { return 5; }  sub EXCUSED               { return 15; }
   
 sub getCompletionStatus {  sub getCompletionStatus {
     my $self = shift;      my $self = shift;
Line 2066  Along with directly returning the date o Line 2489  Along with directly returning the date o
   
 =item * NETWORK_FAILURE: The network has failed and the information is not available.  =item * NETWORK_FAILURE: The network has failed and the information is not available.
   
   =item * NOTHING_SET: No dates have been set for this problem (part) at all. (Because only certain parts of a multi-part problem may be assigned, this can not be collapsed into "open later", as we don't know a given part will EVER be opened. For single part, this is the same as "OPEN_LATER".)
   
 =item * CORRECT: For any reason at all, the part is considered correct.  =item * CORRECT: For any reason at all, the part is considered correct.
   
 =item * EXCUSED: For any reason at all, the problem is excused.  =item * EXCUSED: For any reason at all, the problem is excused.
   
 =item * PAST_DUE: The problem is past due, and not considered correct.  =item * PAST_DUE_NO_ANSWER: The problem is past due, not considered correct, and no answer date is set.
   
   =item * PAST_DUE_ANSWER_LATER: The problem is past due, not considered correct, and an answer date in the future is set.
   
 =item * ANSWER_OPEN: The problem is past due, not correct, and the answer is now available.  =item * ANSWER_OPEN: The problem is past due, not correct, and the answer is now available.
   
Line 2097  sub status { Line 2524  sub status {
   
     # What we have is a two-dimensional matrix with 4 entries on one      # What we have is a two-dimensional matrix with 4 entries on one
     # 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.      # plus network failure and "no date data at all".
   
     # Don't colorize on network failure.      if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; }
     if ($completionStatus == NETWORK_FAILURE()) { return $self->NETWORK_FAILURE(); }  
   
     # There are a few whole rows we can dispose of:      # There are a few whole rows we can dispose of:
     # If the problem is CORRECT, color it green no matter what      if ($completionStatus == CORRECT ||
     if ($completionStatus == CORRECT() ||          $completionStatus == CORRECT_BY_OVERRIDE ) {
         $completionStatus == CORRECT_BY_OVERRIDE() ) {          return CORRECT(); 
         return $self->CORRECT(); # Return a nice green.      }
   
       # If it's EXCUSED, then return that no matter what
       if ($completionStatus == EXCUSED) {
           return EXCUSED; 
     }      }
   
     # If it's EXCUSED, then return something no matter what      if ($dateStatus == NOTHING_SET) {
     if ($completionStatus == EXCUSED()) {          return NOTHING_SET;
         return $self->EXCUSED(); # return a nice blue  
     }      }
   
     # Now we're down to a 3 (incorrect, incorrect_override, not_attempted)      # Now we're down to a 3 (incorrect, incorrect_override, not_attempted)
     # by 4 matrix (date status).      # by 4 matrix (date status).
   
     # If it's Past Due and we didn't bail earlier because it's correct,      if ($dateStatus == PAST_DUE_ANSWER_LATER ||
     # color it orange. (Red is sort inappropriate; too drastic a color          $dateStatus == PAST_DUE_NO_ANSWER ) {
     # for something the student can't fix.          return $dateStatus; 
     if ($dateStatus == PAST_DUE()) {  
         return $self->PAST_DUE(); # return orange  
     }      }
   
     if ($dateStatus == ANSWER_OPEN()) {      if ($dateStatus == ANSWER_OPEN) {
         return $self->ANSWER_OPEN();          return ANSWER_OPEN;
     }      }
   
     # Now: (incorrect, incorrect_override, not_attempted) x       # Now: (incorrect, incorrect_override, not_attempted) x 
     # (open_later), (open)      # (open_later), (open)
           
     # If it's open later, then don't colorize      if ($dateStatus == OPEN_LATER) {
     if ($dateStatus == OPEN_LATER()) {          return OPEN_LATER;
         return $self->OPEN_LATER();  
     }      }
   
     # If it's WRONG...      # If it's WRONG...
     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() < $self->maxtries()) {          if ($self->tries() < $self->maxtries()) {
             return $self->TRIES_LEFT(); # return red: The student can fix this              return TRIES_LEFT;
         }          }
         return $self->INCORRECT(); # otherwise, return orange; student can't fix this          return INCORRECT; # otherwise, return orange; student can't fix this
     }      }
   
     # Otherwise, it's untried and open      # Otherwise, it's untried and open
     return $self->OPEN(); # Light yellow      return OPEN; 
 }  }
   
 =pod  =pod
Line 2173  sub getNext { Line 2599  sub getNext {
         my $next = $choice->goesto();          my $next = $choice->goesto();
         $next = $self->{NAV_MAP}->getById($next);          $next = $self->{NAV_MAP}->getById($next);
   
         # Don't remember it if we've already seen it          # Don't remember it if we've already seen it or if
           # the student doesn't have browse priviledges
           my $browsePriv = &Apache::lonnet::allowed('bre', $self->src);
         if (!defined($alreadySeenHash) ||          if (!defined($alreadySeenHash) ||
             !defined($alreadySeenHash->{$next->{ID}})) {              !defined($alreadySeenHash->{$next->{ID}}) ||
               ($browsePriv ne '2' && $browsePriv ne 'F')) {
                 push @branches, $next;                  push @branches, $next;
             }              }
     }      }

Removed from v.1.51  
changed lines
  Added in v.1.66


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