Diff for /loncom/interface/lonnavmaps.pm between versions 1.55 and 1.69

version 1.55, 2002/09/26 16:56:21 version 1.69, 2002/10/08 18:54:39
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",
Line 834  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      # 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 848  sub new_handle { Line 867  sub new_handle {
     # Defines a status->color mapping, null string means don't color      # Defines a status->color mapping, null string means don't color
     my %colormap =       my %colormap = 
     ( $res->NETWORK_FAILURE        => '',      ( $res->NETWORK_FAILURE        => '',
       $res->CORRECT                => '#BBFFBB',        $res->CORRECT                => '',
       $res->EXCUSED                => '#BBBBFF',        $res->EXCUSED                => '#BBBBFF',
       $res->PAST_DUE_ANSWER_LATER  => '#FFAA00',        $res->PAST_DUE_ANSWER_LATER  => '',
       $res->PAST_DUE_NO_ANSWER     => '#FFAA00',        $res->PAST_DUE_NO_ANSWER     => '',
       $res->ANSWER_OPEN            => '#FF00AA',        $res->ANSWER_OPEN            => '#CCFFCC',
       $res->OPEN_LATER             => '',        $res->OPEN_LATER             => '',
       $res->TRIES_LEFT             => '#FFFF00',        $res->TRIES_LEFT             => '',
       $res->INCORRECT              => '#FFAA00',        $res->INCORRECT              => '',
       $res->OPEN                   => '#FFFF88',        $res->OPEN                   => '',
       $res->NOTHING_SET            => ''        );        $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',
             $res->ATTEMPTED          => '' );
   
       my %iconAltTags = 
           ( 'navmap.correct.gif' => 'Correct',
             'navmap.wrong.gif'   => 'Incorrect',
             'navmap.open.gif'    => 'Open' );
   
       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
Line 867  sub new_handle { Line 913  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      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() ||
Line 892  sub new_handle { Line 952  sub new_handle {
             $isNewBranch = 1;              $isNewBranch = 1;
         }          }
   
           # Is this resource being blotted out?
           if (ref($curRes) && !advancedUser() && $curRes->randomout()) {
               $curRes = $mapIterator->next();
               next; # and totally ignore this resource
           }
   
         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
                   my $nonLinkedText = ""; # unlinked stuff after title
                                   
                 # For each thing we're displaying...                  # For each thing we're displaying...
   
Line 923  sub new_handle { Line 1057  sub new_handle {
                 # If this is a new branch, label it so                  # If this is a new branch, label it so
                 # (temporary, this should be an icon w/ alt text)                  # (temporary, this should be an icon w/ alt text)
                 if ($isNewBranch) {                  if ($isNewBranch) {
                     $newBranchText = "NB -> ";                      $newBranchText = "<img src=\"/adm/lonIcons/branch.gif\" border=\"0\">";
                     $isNewBranch = 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 941  sub new_handle { Line 1080  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";
                       $icon = "<img src=\"/adm/lonIcons/$icon\" alt=\"\" border=\"0\" />";
                     $linkopen = "<a href=\"/adm/navmaps?filter=";                      $linkopen = "<a href=\"/adm/navmaps?filter=";
                     $linkopen .= $nowOpen ?                       $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>";
                       
                 }                  }
                                   
                 my $colorizer = "";                  my $colorizer = "";
Line 960  sub new_handle { Line 1100  sub new_handle {
                     }                      }
                 }                  }
   
                 $r->print("  <tr><td align=\"left\" valign=\"top\">\n");                  if ($curRes->randomout()) {
                       $nonLinkedText .= ' <i>(hidden)</i> ';
                   }
   
                   # 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("  ${newBranchText}${linkopen}<img border=\"0\" src=\"" .                  $r->print("  ${newBranchText}${linkopen}$icon${linkclose}\n");
                           "/adm/lonIcons/$icon\" alt=\"\">${linkclose}\n");  
                   if ($curRes->is_problem() && $part ne "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 COL: Problem status icon
                   if ($curRes->is_problem() &&
                       !$firstDisplayed) {
                       my $icon = $statusIconMap{$curRes->status($part)};
                       my $alt = $iconAltTags{$icon};
                       if ($icon) {
                           $r->print("<td valign=\"bottom\" width=\"50\" align=\"right\">$linkopen<img src=\"/adm/lonIcons/$icon\" border=\"0\" alt=\"$alt\" />$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 COL: 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() &&
                       !$firstDisplayed) {
                     $r->print (getDescription($curRes, $part));                      $r->print (getDescription($curRes, $part));
                 }                  }
                   if ($curRes->is_map() && advancedUser() && $curRes->randompick()) {
                       $r->print('(randomly select ' . $curRes->randompick() .')');
                   }
   
                   $r->print("</td></tr>\n");
             }              }
         }          }
         $curRes = $mapIterator->next();          $curRes = $mapIterator->next();
Line 986  sub new_handle { Line 1191  sub new_handle {
   
     $r->print("</table></body></html>");      $r->print("</table></body></html>");
   
       $navmap->untieHashes();
   
     return OK;      return OK;
 }  }
   
Line 1044  sub getLinkForResource { Line 1251  sub getLinkForResource {
 sub getDescription {  sub getDescription {
     my $res = shift;      my $res = shift;
     my $part = shift;      my $part = shift;
     my $status = $res->getDateStatus();      my $status = $res->status($part);
   
     if ($status == $res->NETWORK_FAILURE) { return ""; }      if ($status == $res->NETWORK_FAILURE) { return ""; }
     if ($status == $res->NOTHING_SET) {      if ($status == $res->NOTHING_SET) {
Line 1054  sub getDescription { Line 1261  sub getDescription {
         return "Opens: " . timeToHumanString($res->opendate($part));          return "Opens: " . timeToHumanString($res->opendate($part));
     }      }
     if ($status == $res->OPEN) {      if ($status == $res->OPEN) {
         return "Due: $status " . timeToHumanString($res->duedate($part));          if ($res->duedate()) {
               return "Due: $status " . timeToHumanString($res->duedate($part));
           } else {
               return "Open, no due date";
           }
     }      }
     if ($status == $res->PAST_DUE_ANSWER_LATER) {      if ($status == $res->PAST_DUE_ANSWER_LATER) {
         return "Answer: " . timeToHumanString($res->answerdate($part));          return "Answer opens: " . 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 "Was Due: " . timeToHumanString($res->duedate($part));
Line 1065  sub getDescription { Line 1276  sub getDescription {
     if ($status == $res->ANSWER_OPEN) {      if ($status == $res->ANSWER_OPEN) {
         return "Answer available";          return "Answer available";
     }      }
       if ($status == $res->EXCUSED) {
           return "Excused by instructor";
       }
       if ($status == $res->ATTEMPTED) {
           return "Not yet graded.";
       }
       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";
           }
       }
   }
   
   sub advancedUser {
       return $ENV{'user.adv'};
 }  }
   
 # I want to change this into something more human-friendly. For  # I want to change this into something more human-friendly. For
 # now, this is a simple call to localtime. The final function  # now, this is a simple call to localtime. The final function
 # probably belongs in loncommon.  # probably belongs in loncommon.
 sub timeToHumanString {  sub timeToHumanString {
     return localtime(shift);      my ($time) = @_;
       # zero, '0' and blank are bad times
       if ($time) {
           return localtime($time);
       } else {
           return 'Never';
       }
 }  }
   
 1;  1;
Line 1138  sub new { Line 1376  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 1204  sub new { Line 1443  sub new {
         my ($cdom,$cnum)=split(/\_/,$cid);          my ($cdom,$cnum)=split(/\_/,$cid);
                   
         my %emailstatus = &Apache::lonnet::dump('email_status');          my %emailstatus = &Apache::lonnet::dump('email_status');
         my logoutTime = $emailstatus{'logout'};          my $logoutTime = $emailstatus{'logout'};
         my courseLeaveTime = $emailstatus{'logout_'.$ENV{'request.course.id'}};          my $courseLeaveTime = $emailstatus{'logout_'.$ENV{'request.course.id'}};
         $self->{LAST_CHECK} = ($courseLeaveTime > $logoutTime ?          $self->{LAST_CHECK} = ($courseLeaveTime > $logoutTime ?
                                $courseLeaveTime : $logoutTime);                                 $courseLeaveTime : $logoutTime);
         my %discussiontime = &Apache::lonnet::dump{'discussiontimes',           my %discussiontime = &Apache::lonnet::dump('discussiontimes', 
                                                    $cdom, $cnum);                                                     $cdom, $cnum);
         my %feedback=();          my %feedback=();
         my %error=();          my %error=();
Line 1256  sub courseMapDefined { Line 1495  sub courseMapDefined {
     my $self = shift;      my $self = shift;
     my $uri = &Apache::lonnet::clutter($ENV{'request.course.uri'});      my $uri = &Apache::lonnet::clutter($ENV{'request.course.uri'});
   
     my $firstres = $self->{NAV_HASH}->{'map_start_$uri'};      my $firstres = $self->{NAV_HASH}->{"map_start_$uri"};
     my $lastres = $self->{NAV_HASH}->{'map_finish_$uri'};      my $lastres = $self->{NAV_HASH}->{"map_finish_$uri"};
     return $firstres && $lastres;      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 1282  sub DESTROY { Line 1521  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 1376  sub parmval { Line 1638  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 1412  sub parmval { Line 1674  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 1450  getIterator behaves as follows: Line 1712  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 1502  sub new { Line 1764  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 1533  sub new { Line 1796  sub new {
     return $self;      return $self;
 }  }
   
   # FIXME: Document this.
   sub cancelTopRecursion {
       my $self = shift;
       
       if (!$self->{RECURSIVE_ITERATOR_FLAG}) {return;}
   
       # is this the iterator we want to kill?
       if ($self->{RECURSIVE_ITERATOR_FLAG} && 
           !$self->{RECURSIVE_ITERATOR}->{RECURSIVE_ITERATOR_FLAG}) {
           $self->{RECURSIVE_ITERATOR_FLAG} = 0;
           undef $self->{RECURSIVE_ITERATOR};
           return;
       }
       
       $self->{RECURSIVE_ITERATOR}->cancelTopRecursion();
   }
   
 # Note... this function is *touchy*. I strongly recommend tracing  # Note... this function is *touchy*. I strongly recommend tracing
 # through it with the debugger a few times on a non-trivial map before  # through it with the debugger a few times on a non-trivial map before
 # modifying it. Order is *everything*.  # modifying it. Order is *everything*.
   # FIXME: Doc that skipMap will prevent the recursion, if any.
 sub next {  sub next {
     my $self = shift;      my $self = shift;
       my $skipMap = shift;
           
     # Iterator logic goes here      # Iterator logic goes here
   
     # Is this return value pre-determined?  
     if (defined($self->{FORCE_NEXT})) {  
         my $tmp = $self->{FORCE_NEXT};  
         $self->{FORCE_NEXT} = undef;  
         return $tmp;  
     }  
   
     # Are we using a recursive iterator? If so, pull from that and      # Are we using a recursive iterator? If so, pull from that and
     # watch the depth; we want to resume our level at the correct time.      # watch the depth; we want to resume our level at the correct time.
     if ($self->{RECURSIVE_ITERATOR_FLAG})      if ($self->{RECURSIVE_ITERATOR_FLAG})
Line 1567  sub next { Line 1842  sub next {
         return $next;          return $next;
     }      }
   
       # Is this return value pre-determined?
       if (defined($self->{FORCE_NEXT})) {
           my $tmp = $self->{FORCE_NEXT};
           $self->{FORCE_NEXT} = undef;
           return $tmp;
       }
   
     # Is there a current resource to grab? If not, then return      # Is there a current resource to grab? If not, then return
     # END_BRANCH and END_MAP in succession.      # END_BRANCH and END_MAP in succession.
     if (scalar(@{$self->{BRANCH_STACK}}) == 0) {      if (scalar(@{$self->{BRANCH_STACK}}) == 0) {
Line 1655  sub next { Line 1937  sub next {
             $self->{FORCE_NEXT} = $self->END_BRANCH();              $self->{FORCE_NEXT} = $self->END_BRANCH();
             $self->{BRANCH_DEPTH}--;              $self->{BRANCH_DEPTH}--;
         }          }
         return $self->{HERE};  
     }      }
           
     while (@$next) {      while (@$next) {
Line 1671  sub next { Line 1952  sub next {
     }      }
   
     # 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() && !$skipMap && 
          !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();
   
         # Odd perl syntax here; $self->new allows one to create a new iterator  
         # can't figure out how to ref this package directly correctly  
         # 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 1757  sub new { Line 2034  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 1800  sub goesto { my $self=shift; return $sel Line 2078  sub goesto { my $self=shift; return $sel
 # "to" can return a comma seperated list for branches  # "to" can return a comma seperated list for branches
 sub to { my $self=shift; return $self->navHash("to_", 1); }  sub to { my $self=shift; return $self->navHash("to_", 1); }
 sub kind { my $self=shift; return $self->navHash("kind_", 1); }  sub kind { my $self=shift; return $self->navHash("kind_", 1); }
   sub ext { my $self=shift; return $self->navHash("ext_", 1) eq 'true:'; }
   sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }
   sub randompick { 
       my $self = shift;
       return $self->{NAV_MAP}->{PARM_HASH}->{$self->symb .
                                                  '.0.parameter_randompick'};
   }
 sub src {   sub src { 
     my $self=shift;      my $self=shift;
     return $self->navHash("src_", 1);      return $self->navHash("src_", 1);
Line 2014  Misc. functions for the resource. Line 2299  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 2025  Misc. functions for the resource. Line 2334  Misc. functions for the resource.
 sub parts {  sub parts {
     my $self = shift;      my $self = shift;
   
       if ($self->ext) { return ['0']; }
   
     $self->extractParts();      $self->extractParts();
     return $self->{PARTS};      return $self->{PARTS};
 }  }
Line 2033  sub countParts { Line 2344  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 2044  sub extractParts { Line 2359  sub extractParts {
     my $self = shift;      my $self = shift;
           
     return if ($self->{PARTS});      return if ($self->{PARTS});
       return if ($self->ext);
   
     $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 2081  Idiomatic usage of these two methods wou Line 2404  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 2093  B<Date Codes> Line 2416  B<Date Codes>
   
 =item * B<OPEN>: Open and not yet due.  =item * B<OPEN>: Open and not yet due.
   
   
 =item * B<PAST_DUE_ANSWER_LATER>: 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<PAST_DUE_NO_ANSWER>: The due date has passed and there is no answer opening date set.
Line 2140  sub getDateStatus { Line 2464  sub getDateStatus {
         # should this be the same as "open later"? think multipart.          # should this be the same as "open later"? think multipart.
         return $self->NOTHING_SET;          return $self->NOTHING_SET;
     }      }
     if (!$open || $now < $open) {return $self->OPEN_LATER};      if (!$open || $now < $open) {return $self->OPEN_LATER}
     if (!$due || $now < $due) {return $self->OPEN};      if (!$due || $now < $due) {return $self->OPEN}
     if ($answer && $now < $answer) {return $self->PAST_DUE_ANSWER_LATER};      if ($answer && $now < $answer) {return $self->PAST_DUE_ANSWER_LATER}
     if ($answer) { return $self->ANSWER_OPEN; };      if ($answer) { return $self->ANSWER_OPEN; }
     return PAST_DUE_NO_ANSWER;      return PAST_DUE_NO_ANSWER;
 }  }
   
Line 2175  B<Completion Codes> Line 2499  B<Completion Codes>
   
 =item * B<NETWORK_FAILURE>: Information not available due to network failure.  =item * B<NETWORK_FAILURE>: Information not available due to network failure.
   
   =item * B<ATTEMPTED>: Attempted, and not yet graded.
   
 =back  =back
   
 =cut  =cut
Line 2185  sub INCORRECT_BY_OVERRIDE { return 12; } Line 2511  sub INCORRECT_BY_OVERRIDE { return 12; }
 sub CORRECT               { return 13; }  sub CORRECT               { return 13; }
 sub CORRECT_BY_OVERRIDE   { return 14; }  sub CORRECT_BY_OVERRIDE   { return 14; }
 sub EXCUSED               { return 15; }  sub EXCUSED               { return 15; }
   sub ATTEMPTED             { return 16; }
   
 sub getCompletionStatus {  sub getCompletionStatus {
     my $self = shift;      my $self = shift;
Line 2203  sub getCompletionStatus { Line 2530  sub getCompletionStatus {
     if ($status eq 'incorrect_attempted') {return $self->INCORRECT; }      if ($status eq 'incorrect_attempted') {return $self->INCORRECT; }
     if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; }      if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; }
     if ($status eq 'excused') {return $self->EXCUSED; }      if ($status eq 'excused') {return $self->EXCUSED; }
       if ($status eq 'ungraded_attempted') {return $self->ATTEMPTED; }
     return $self->NOT_ATTEMPTED;      return $self->NOT_ATTEMPTED;
 }  }
   
Line 2216  Along with directly returning the date o Line 2544  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.)  =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.
   
Line 2236  Along with directly returning the date o Line 2564  Along with directly returning the date o
   
 =item * OPEN: The item is open and not yet tried.  =item * OPEN: The item is open and not yet tried.
   
   =item * ATTEMPTED: The problem has been attempted.
   
 =back  =back
   
 =cut  =cut
Line 2258  sub status { Line 2588  sub status {
     # There are a few whole rows we can dispose of:      # There are a few whole rows we can dispose of:
     if ($completionStatus == CORRECT ||      if ($completionStatus == CORRECT ||
         $completionStatus == CORRECT_BY_OVERRIDE ) {          $completionStatus == CORRECT_BY_OVERRIDE ) {
         return CORRECT();           return CORRECT; 
       }
   
       if ($completionStatus == ATTEMPTED) {
           return ATTEMPTED;
     }      }
   
     # If it's EXCUSED, then return that no matter what      # If it's EXCUSED, then return that no matter what
Line 2270  sub status { Line 2604  sub status {
         return NOTHING_SET;          return NOTHING_SET;
     }      }
   
     # Now we're down to a 3 (incorrect, incorrect_override, not_attempted)      # Now we're down to a 4 (incorrect, incorrect_override, not_attempted)
     # by 4 matrix (date status).      # by 4 matrix (date statuses).
   
     if ($dateStatus == PAST_DUE_ANSWER_LATER ||      if ($dateStatus == PAST_DUE_ANSWER_LATER ||
         $dateStatus == PAST_DUE_NO_ANSWER) {          $dateStatus == PAST_DUE_NO_ANSWER ) {
         return $dateStatus;           return $dateStatus; 
     }      }
   
Line 2326  sub getNext { Line 2660  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.55  
changed lines
  Added in v.1.69


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