Diff for /loncom/interface/lonnavmaps.pm between versions 1.444.2.4.2.1 and 1.509.2.5.4.2

version 1.444.2.4.2.1, 2012/07/09 18:30:46 version 1.509.2.5.4.2, 2017/09/15 23:17:24
Line 164  If true, the resource's folder will not Line 164  If true, the resource's folder will not
 it. Default is false. True implies printCloseAll is false, since you  it. Default is false. True implies printCloseAll is false, since you
 can't close or open folders when this is on anyhow.  can't close or open folders when this is on anyhow.
   
   =item * B<map_no_edit_link>:
   
   If true, the title of the folder or page will not be followed by an
   icon/link to direct editing of a folder or composite page, originally
   added via the Course Editor.
   
 =back  =back
   
 =item * B<Apache::lonnavmaps::communication_status>:  =item * B<Apache::lonnavmaps::communication_status>:
Line 464  returns 4 Line 470  returns 4
   
 =item add_linkitem()  =item add_linkitem()
   
 =item show_linkitems()  =item show_linkitems_toolbar()
   
 =back  =back
   
Line 478  use Apache::loncommon(); Line 484  use Apache::loncommon();
 use Apache::lonenc();  use Apache::lonenc();
 use Apache::lonlocal;  use Apache::lonlocal;
 use Apache::lonnet;  use Apache::lonnet;
   use Apache::lonmap;
   
 use POSIX qw (floor strftime);  use POSIX qw (ceil floor strftime);
 use Time::HiRes qw( gettimeofday tv_interval );  use Time::HiRes qw( gettimeofday tv_interval );
 use LONCAPA;  use LONCAPA;
 use DateTime();  use DateTime();
   use HTML::Entities;
   
   # For debugging
   
   #use Data::Dumper;
   
   
 # symbolic constants  # symbolic constants
 sub SYMB { return 1; }  sub SYMB { return 1; }
Line 509  my %statusIconMap = Line 522  my %statusIconMap =
 my %iconAltTags =   #texthash does not work here  my %iconAltTags =   #texthash does not work here
     ( 'navmap.correct.gif'  => 'Correct',      ( 'navmap.correct.gif'  => 'Correct',
       'navmap.wrong.gif'    => 'Incorrect',        'navmap.wrong.gif'    => 'Incorrect',
       'navmap.open.gif'     => 'Open',        'navmap.open.gif'     => 'Is Open',
       'navmap.partial.gif'  => 'Partially Correct',        'navmap.partial.gif'  => 'Partially Correct',
       'navmap.ellipsis.gif' => 'Attempted',        'navmap.ellipsis.gif' => 'Attempted',
      );       );
Line 527  my %colormap = Line 540  my %colormap =
       $resObj->INCORRECT              => '',        $resObj->INCORRECT              => '',
       $resObj->OPEN                   => '',        $resObj->OPEN                   => '',
       $resObj->NOTHING_SET            => '',        $resObj->NOTHING_SET            => '',
       $resObj->CREDIT_ATTEMPTED       => '',  
       $resObj->ATTEMPTED              => '',        $resObj->ATTEMPTED              => '',
         $resObj->CREDIT_ATTEMPTED       => '',
       $resObj->ANSWER_SUBMITTED       => '',        $resObj->ANSWER_SUBMITTED       => '',
       $resObj->PARTIALLY_CORRECT      => '#006600'        $resObj->PARTIALLY_CORRECT      => '#006600'
       );        );
Line 536  my %colormap = Line 549  my %colormap =
 # is not yet done and due in less than 24 hours  # is not yet done and due in less than 24 hours
 my $hurryUpColor = "#FF0000";  my $hurryUpColor = "#FF0000";
   
 my $future_slots_checked = 0;  
 my $future_slots = 0;  
   
 sub close {  
     if ($env{'environment.remotenavmap'} ne 'on') { return ''; }  
     return(<<ENDCLOSE);  
 <script type="text/javascript">  
 window.status='Accessing Nav Control';  
 menu=window.open("/adm/rat/empty.html","loncapanav",  
                  "height=600,width=400,scrollbars=1");  
 window.status='Closing Nav Control';  
 menu.close();  
 window.status='Done.';  
 </script>  
 ENDCLOSE  
 }  
   
 sub update {  
     if ($env{'environment.remotenavmap'} ne 'on') { return ''; }  
     if (!$env{'request.course.id'}) { return ''; }  
     if ($ENV{'REQUEST_URI'}=~m|^/adm/navmaps|) { return ''; }  
     return(<<ENDUPDATE);  
 <form name="navform"></form>  
 <script type="text/javascript">  
 this.document.navform.action='/adm/navmaps#curloc';  
 this.document.navform.target='loncapanav';  
 this.document.navform.submit();  
 </script>  
 ENDUPDATE  
 }  
   
   
 sub addToFilter {  sub addToFilter {
     my $hashIn = shift;      my $hashIn = shift;
     my $addition = shift;      my $addition = shift;
Line 638  sub getDescription { Line 619  sub getDescription {
         return &mt("Having technical difficulties; please check status later");           return &mt("Having technical difficulties; please check status later"); 
     }      }
     if ($status == $res->NOTHING_SET) {      if ($status == $res->NOTHING_SET) {
         return &mt("Not currently assigned.");          return &Apache::lonhtmlcommon::direct_parm_link(&mt('Not currently assigned'),$res->symb(),'opendate',$part);
     }      }
     if ($status == $res->OPEN_LATER) {      if ($status == $res->OPEN_LATER) {
         return &mt("Open ") .timeToHumanString($open,'start');          return &mt("Open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($open,'start'),$res->symb(),'opendate',$part));
     }      }
       my $slotinfo;
     if ($res->simpleStatus($part) == $res->OPEN) {      if ($res->simpleStatus($part) == $res->OPEN) {
         unless (&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) {          unless (&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) {
             my ($slot_status,$slot_time,$slot_name)=$res->check_for_slot($part);              my ($slot_status,$slot_time,$slot_name)=$res->check_for_slot($part);
               my $slotmsg;
             if ($slot_status == $res->UNKNOWN) {              if ($slot_status == $res->UNKNOWN) {
                 return &mt('Reservation status unknown');                  $slotmsg = &mt('Reservation status unknown');
             } elsif ($slot_status == $res->RESERVED) {              } elsif ($slot_status == $res->RESERVED) {
                 return &mt('Reserved - ends [_1]',                  $slotmsg = &mt('Reserved - ends [_1]',
                            timeToHumanString($slot_time,'end'));                             timeToHumanString($slot_time,'end'));
             } elsif ($slot_status == $res->RESERVED_LOCATION) {              } elsif ($slot_status == $res->RESERVED_LOCATION) {
                 return &mt('Reserved - specific location(s) - ends [_1]',                  $slotmsg = &mt('Reserved - specific location(s) - ends [_1]',
                            timeToHumanString($slot_time,'end'));                             timeToHumanString($slot_time,'end'));
             } elsif ($slot_status == $res->RESERVED_LATER) {              } elsif ($slot_status == $res->RESERVED_LATER) {
                 return &mt('Reserved - next open [_1]',                  $slotmsg = &mt('Reserved - next open [_1]',
                            timeToHumanString($slot_time,'start'));                             timeToHumanString($slot_time,'start'));
             } elsif ($slot_status == $res->RESERVABLE) {              } elsif ($slot_status == $res->RESERVABLE) {
                 return &mt('Reservable ending [_1]',                  $slotmsg = &mt('Reservable, reservations close [_1]',
                            timeToHumanString($slot_time,'end'));                             timeToHumanString($slot_time,'end'));
             } elsif ($slot_status == $res->RESERVABLE_LATER) {              } elsif ($slot_status == $res->RESERVABLE_LATER) {
                 return &mt('Reservable starting [_1]',                  $slotmsg = &mt('Reservable, reservations open [_1]',
                            timeToHumanString($slot_time,'start'));                             timeToHumanString($slot_time,'start'));
             } elsif ($slot_status == $res->NOT_IN_A_SLOT) {              } elsif ($slot_status == $res->NOT_IN_A_SLOT) {
                 return &mt('Reserve a time/place to work');                  $slotmsg = &mt('Reserve a time/place to work');
             } elsif ($slot_status == $res->NOTRESERVABLE) {              } elsif ($slot_status == $res->NOTRESERVABLE) {
                 return &mt('Reservation not available');                  $slotmsg = &mt('Reservation not available');
             } elsif ($slot_status == $res->WAITING_FOR_GRADE) {              } elsif ($slot_status == $res->WAITING_FOR_GRADE) {
                 return &mt('Submission in grading queue');                  $slotmsg = &mt('Submission in grading queue');
               }
               if ($slotmsg) {
                   if ($res->is_task() || !$due) {
                        return $slotmsg;
                   }
                   $slotinfo = ('&nbsp;' x 2).'('.$slotmsg.')';
             }              }
         }          }
     }      }
     if ($status == $res->OPEN) {      if ($status == $res->OPEN) {
         if ($due) {          if ($due) {
     if ($res->is_practice()) {      if ($res->is_practice()) {
  return &mt("Closes ")."  " .timeToHumanString($due,'start');   return &mt("Closes [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start'),$res->symb(),'duedate',$part)).$slotinfo;
     } else {      } else {
  return &mt("Due")."  " .timeToHumanString($due,'end');   return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'duedate',$part)).$slotinfo;
     }      }
         } else {          } else {
             return &mt("Open, no due date");              return &Apache::lonhtmlcommon::direct_parm_link(&mt("Open, no due date"),$res->symb(),'duedate',$part).$slotinfo;
         }          }
     }      }
     if ($status == $res->PAST_DUE_ANSWER_LATER) {      if ($status == $res->PAST_DUE_ANSWER_LATER) {
         return &mt("Answer open")." " .timeToHumanString($answer,'start');          return &mt("Answer open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($answer,'start'),$res->symb(),'answerdate',$part));
     }      }
     if ($status == $res->PAST_DUE_NO_ANSWER) {      if ($status == $res->PAST_DUE_NO_ANSWER) {
  if ($res->is_practice()) {   if ($res->is_practice()) {
     return &mt("Closed")." " . timeToHumanString($due,'start');      return &mt("Closed [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start'),$res->symb(),'answerdate,duedate',$part));
  } else {   } else {
     return &mt("Was due")." " . timeToHumanString($due,'end');      return &mt("Was due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'answerdate,duedate',$part));
  }   }
     }      }
     if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT)      if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT)
  && $res->handgrade($part) ne 'yes') {   && $res->handgrade($part) ne 'yes') {
         return &mt("Answer available");          return &Apache::lonhtmlcommon::direct_parm_link(&mt("Answer available"),$res->symb(),'answerdate,duedate',$part);
     }      }
     if ($status == $res->EXCUSED) {      if ($status == $res->EXCUSED) {
         return &mt("Excused by instructor");          return &mt("Excused by instructor");
Line 717  sub getDescription { Line 706  sub getDescription {
         my $maxtries = $res->maxtries($part);          my $maxtries = $res->maxtries($part);
         my $triesString = "";          my $triesString = "";
         if ($tries && $maxtries) {          if ($tries && $maxtries) {
             $triesString = '<font size="-1"><i>('.&mt('[_1] of [quant,_2,try,tries] used',$tries,$maxtries).')</i></font>';              $triesString = '<span class="LC_fontsize_medium"><i>('.&mt('[_1] of [quant,_2,try,tries] used',$tries,$maxtries).')</i></span>';
             if ($maxtries > 1 && $maxtries - $tries == 1) {              if ($maxtries > 1 && $maxtries - $tries == 1) {
                 $triesString = "<b>$triesString</b>";                  $triesString = "<b>$triesString</b>";
             }              }
         }          }
         if ($due) {          if ($due) {
             return &mt("Due")." " . timeToHumanString($due,'end') .              return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'duedate',$part)) .
                 " $triesString";                  " $triesString";
         } else {          } else {
             return &mt("No due date")." $triesString";              return &Apache::lonhtmlcommon::direct_parm_link(&mt("No due date"),$res->symb(),'duedate',$part)." $triesString";
         }          }
     }      }
     if ($status == $res->ANSWER_SUBMITTED) {      if ($status == $res->ANSWER_SUBMITTED) {
Line 808  sub timeToHumanString { Line 797  sub timeToHumanString {
   
         # Less than an hour          # Less than an hour
         if ( $delta < $hour ) {          if ( $delta < $hour ) {
             # If so, use minutes              # If so, use minutes; or minutes, seconds (if format requires)
             my $minutes = floor($delta / 60);              my $minutes = floor($delta / 60);
               if (($format ne '') && ($format =~ /\%(T|S)/)) {
                   my $display;
                   if ($minutes == 1) {
                       $display = "${prefix}1 minute";
                   } else {
                       $display = "$prefix$minutes minutes";
                   }
                   my $seconds = $delta % $minute;
                   if ($seconds == 0) {
                       $display .= $tense;
                   } elsif ($seconds == 1) {
                       $display .= ", 1 second$tense";
                   } else {
                       $display .= ", $seconds seconds$tense";
                   }
                   return $display;
               }
             if ($minutes == 1) { return "${prefix}1 minute$tense"; }              if ($minutes == 1) { return "${prefix}1 minute$tense"; }
             return "$prefix$minutes minutes$tense";              return "$prefix$minutes minutes$tense";
         }          }
                   
         # Is it less than 24 hours away? If so,          # Is it less than 24 hours away? If so,
         # display hours + minutes          # display hours + minutes, (and + seconds, if format specified it)  
         if ( $delta < $hour * 24) {          if ( $delta < $hour * 24) {
             my $hours = floor($delta / $hour);              my $hours = floor($delta / $hour);
             my $minutes = floor(($delta % $hour) / $minute);              my $minutes = floor(($delta % $hour) / $minute);
Line 830  sub timeToHumanString { Line 836  sub timeToHumanString {
             if ($minutes == 0) {              if ($minutes == 0) {
                 $minuteString = "";                  $minuteString = "";
             }              }
               if (($format ne '') && ($format =~ /\%(T|S)/)) {
                   my $display = "$prefix$hourString$minuteString";
                   my $seconds = $delta-(($hours * $hour)+($minutes * $minute));
                   if ($seconds == 0) {
                       $display .= $tense;
                   } elsif ($seconds == 1) {
                       $display .= ", 1 second$tense";
                   } else {
                       $display .= ", $seconds seconds$tense";
                   }
                   return $display;
               }
             return "$prefix$hourString$minuteString$tense";              return "$prefix$hourString$minuteString$tense";
         }          }
   
           # Date/time is more than 24 hours away
   
  my $dt = DateTime->from_epoch(epoch => $time)   my $dt = DateTime->from_epoch(epoch => $time)
                  ->set_time_zone(&Apache::lonlocal::gettimezone());                   ->set_time_zone(&Apache::lonlocal::gettimezone());
   
  # If there's a caller supplied format, use it.   # If there's a caller supplied format, use it, unless it only displays
           # H:M:S or H:M.
   
  if ($format ne '') {   if (($format ne '') && ($format ne '%T') && ($format ne '%R')) {
     my $timeStr = $dt->strftime($format);      my $timeStr = $dt->strftime($format);
     return $timeStr.' ('.$dt->time_zone_short_name().')';      return $timeStr.' ('.$dt->time_zone_short_name().')';
  }   }
Line 890  sub part_status_summary { return 4; } Line 911  sub part_status_summary { return 4; }
 sub render_resource {  sub render_resource {
     my ($resource, $part, $params) = @_;      my ($resource, $part, $params) = @_;
   
       my $editmapLink;
     my $nonLinkedText = ''; # stuff after resource title not in link      my $nonLinkedText = ''; # stuff after resource title not in link
   
     my $link = $params->{"resourceLink"};      my $link = $params->{"resourceLink"};
       if ($resource->ext()) {
           $link =~ s/\#.+(\?)/$1/g;
       }
   
     #  The URL part is not escaped at this point, but the symb is...       #  The URL part is not escaped at this point, but the symb is... 
   
Line 967  sub render_resource { Line 992  sub render_resource {
             # Don't allow users to manipulate folder              # Don't allow users to manipulate folder
             $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';              $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';
             $icon = "<img class=\"LC_space\" src='$whitespace' alt='' />"."<img class=\"LC_contentImage\" src='$location/$icon' alt=\"".($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" />";              $icon = "<img class=\"LC_space\" src='$whitespace' alt='' />"."<img class=\"LC_contentImage\" src='$location/$icon' alt=\"".($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" />";
               if ($params->{'caller'} eq 'sequence') {
             $linkopen = "";                  $linkopen = "<a href=\"$link\">";
             $linkclose = "";              } else {
                   $linkopen = "";
                   $linkclose = "";
               }
           }
           if (((&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) ||
                (&Apache::lonnet::allowed('cev',$env{'request.course.id'}))) &&
               ($resource->symb=~/\_\_\_[^\_]+\_\_\_uploaded/)) {
               if (!$params->{'map_no_edit_link'}) {
                   my $icon = &Apache::loncommon::lonhttpdurl('/res/adm/pages').'/editmap.png';
                   $editmapLink='&nbsp;'.
                            '<a href="/adm/coursedocs?command=directnav&amp;symb='.&escape($resource->symb()).'">'.
                            '<img src="'.$icon.'" alt="'.&mt('Edit Content').'" title="'.&mt('Edit Content').'" />'.
                            '</a>';
               }
           }
           if ($params->{'mapHidden'} || $resource->randomout()) {
               $nonLinkedText .= ' <span class="LC_warning">('.&mt('hidden').')</span> ';
           }
       } else {
           if ($resource->randomout()) {
               $nonLinkedText .= ' <span class="LC_warning">('.&mt('hidden').')</span> ';
         }          }
     }  
   
     if ($resource->randomout()) {  
         $nonLinkedText .= ' <i>('.&mt('hidden').')</i> ';  
     }      }
     if (!$resource->condval()) {      if (!$resource->condval()) {
         $nonLinkedText .= ' <i>('.&mt('conditionally hidden').')</i> ';          $nonLinkedText .= ' <span class="LC_info">('.&mt('conditionally hidden').')</span> ';
     }      }
     if (($resource->is_practice()) && ($resource->is_raw_problem())) {      if (($resource->is_practice()) && ($resource->is_raw_problem())) {
         $nonLinkedText .=' <font color="green"><b>'.&mt('not graded').'</b></font>';          $nonLinkedText .=' <span class="LC_info"><b>'.&mt('not graded').'</b></span>';
     }      }
   
     # We're done preparing and finally ready to start the rendering      # We're done preparing and finally ready to start the rendering
Line 1004  sub render_resource { Line 1046  sub render_resource {
     # Is this the current resource?      # Is this the current resource?
     if (!$params->{'displayedHereMarker'} &&       if (!$params->{'displayedHereMarker'} && 
         $resource->symb() eq $params->{'here'} ) {          $resource->symb() eq $params->{'here'} ) {
         $curMarkerBegin = '<em>';          unless ($resource->is_map()) {
         $curMarkerEnd = '</em>';              $curMarkerBegin = '<span class="LC_current_nav_location">';
               $curMarkerEnd = '</span>';
           }
  $params->{'displayedHereMarker'} = 1;   $params->{'displayedHereMarker'} = 1;
     }      }
   
Line 1021  sub render_resource { Line 1065  sub render_resource {
         $nonLinkedText .= ' ('.&mt('[_1] parts', $resource->countParts()).')';          $nonLinkedText .= ' ('.&mt('[_1] parts', $resource->countParts()).')';
     }      }
   
     my $target;  
     if ($env{'environment.remotenavmap'} eq 'on') {  
  $target=' target="loncapaclient" ';  
     }  
     if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) {      if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) {
         $result .= "$curMarkerBegin<a $target href=\"$link\">$title$partLabel</a>$curMarkerEnd$nonLinkedText</td>";          $result .= "$curMarkerBegin<a href=\"$link\">$title$partLabel</a>$curMarkerEnd$editmapLink$nonLinkedText</td>";
     } else {      } else {
         $result .= "$curMarkerBegin$linkopen$title$partLabel</a>$curMarkerEnd$nonLinkedText</td>";          $result .= "$curMarkerBegin$linkopen$title$partLabel</a>$curMarkerEnd$editmapLink$nonLinkedText</td>";
     }      }
   
     return $result;      return $result;
Line 1039  sub render_communication_status { Line 1079  sub render_communication_status {
     my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = "";      my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = "";
   
     my $link = $params->{"resourceLink"};      my $link = $params->{"resourceLink"};
     my $target;      my $linkopen = "<a href=\"$link\">";
     if ($env{'environment.remotenavmap'} eq 'on') {  
  $target=' target="loncapaclient" ';  
     }  
     my $linkopen = "<a $target href=\"$link\">";  
     my $linkclose = "</a>";      my $linkclose = "</a>";
     my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");      my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");
   
     if ($resource->hasDiscussion()) {      if ($resource->hasDiscussion()) {
         $discussionHTML = $linkopen .          $discussionHTML = $linkopen .
             '<img alt="'.&mt('New Discussion').'" src="'.$location.'/chat.gif" title="'.&mt('New Discussion').'"/>' .              '<img alt="'.&mt('New Discussion').'" src="'.$location.'/chat.gif" title="'.&mt('New Discussion').'"/>' .
Line 1056  sub render_communication_status { Line 1093  sub render_communication_status {
         my $feedback = $resource->getFeedback();          my $feedback = $resource->getFeedback();
         foreach my $msgid (split(/\,/, $feedback)) {          foreach my $msgid (split(/\,/, $feedback)) {
             if ($msgid) {              if ($msgid) {
                 $feedbackHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='                  $feedbackHTML .= '&nbsp;<a href="/adm/email?display='
                     . &escape($msgid) . '">'                      . &escape($msgid) . '">'
                     . '<img alt="'.&mt('New E-mail').'" src="'.$location.'/feedback.gif" title="'.&mt('New E-mail').'"/></a>';                      . '<img alt="'.&mt('New E-mail').'" src="'.$location.'/feedback.gif" title="'.&mt('New E-mail').'"/></a>';
             }              }
Line 1070  sub render_communication_status { Line 1107  sub render_communication_status {
             last if ($errorcount>=10); # Only output 10 bombs maximum              last if ($errorcount>=10); # Only output 10 bombs maximum
             if ($msgid) {              if ($msgid) {
                 $errorcount++;                  $errorcount++;
                 $errorHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='                  $errorHTML .= '&nbsp;<a href="/adm/email?display='
                     . &escape($msgid) . '">'                      . &escape($msgid) . '">'
                     . '<img alt="'.&mt('New Error').'" src="'.$location.'/bomb.gif" title="'.&mt('New Error').'"/></a>';                      . '<img alt="'.&mt('New Error').'" src="'.$location.'/bomb.gif" title="'.&mt('New Error').'"/></a>';
             }              }
Line 1090  sub render_quick_status { Line 1127  sub render_quick_status {
         $params->{'multipart'} && $part eq "0";          $params->{'multipart'} && $part eq "0";
   
     my $link = $params->{"resourceLink"};      my $link = $params->{"resourceLink"};
     my $target;      my $linkopen = "<a href=\"$link\">";
     if ($env{'environment.remotenavmap'} eq 'on') {  
  $target=' target="loncapaclient" ';  
     }  
     my $linkopen = "<a $target href=\"$link\">";  
     my $linkclose = "</a>";      my $linkclose = "</a>";
   
  $result .= '<td class="LC_middle">';   $result .= '<td class="LC_middle">';
Line 1122  sub render_long_status { Line 1155  sub render_long_status {
         $params->{'multipart'} && $part eq "0";          $params->{'multipart'} && $part eq "0";
                                   
     my $color;      my $color;
       my $info = '';
     if ($resource->is_problem() || $resource->is_practice()) {      if ($resource->is_problem() || $resource->is_practice()) {
         $color = $colormap{$resource->status};          $color = $colormap{$resource->status};
           
         if (dueInLessThan24Hours($resource, $part) ||          if (dueInLessThan24Hours($resource, $part)) {
             lastTry($resource, $part)) {  
             $color = $hurryUpColor;              $color = $hurryUpColor;
         }              $info = ' title="'.&mt('Due in less than 24 hours!').'"';
           } elsif (lastTry($resource, $part)) {
               unless (($resource->problemstatus($part) eq 'no') ||
                       ($resource->problemstatus($part) eq 'no_feedback_ever')) {
                   $color = $hurryUpColor;
                   $info = ' title="'.&mt('One try remaining!').'"';
               }
            }
     }      }
           
     if ($resource->kind() eq "res" &&      if ($resource->kind() eq "res" &&
         ($resource->is_problem() || $resource->is_practice()) &&          $resource->is_raw_problem() &&
         !$firstDisplayed) {          !$firstDisplayed) {
         if ($color) {$result .= "<font color=\"$color\"><b>"; }          if ($color) {$result .= '<span style="color:'.$color.'"'.$info.'><b>'; }
         $result .= getDescription($resource, $part);          $result .= getDescription($resource, $part);
         if ($color) {$result .= "</b></font>"; }          if ($color) {$result .= "</b></span>"; }
     }      }
     if ($resource->is_map() && &advancedUser() && $resource->randompick()) {      if ($resource->is_map() && &advancedUser() && $resource->randompick()) {
         $result .= &mt('(randomly select [_1])', $resource->randompick());          $result .= &mt('(randomly select [_1])', $resource->randompick());
Line 1218  sub render_parts_summary_status { Line 1258  sub render_parts_summary_status {
     }      }
     $return.= $td . $totalParts . ' parts: ';      $return.= $td . $totalParts . ' parts: ';
     foreach my $status (@statuses) {      foreach my $status (@statuses) {
  if ($overallstatus{$status}) {          if ($overallstatus{$status}) {
     $return.="<font color='" . $statusColors{$status} .              $return.='<span style="color:' . $statusColors{$status}
  "'>" . $overallstatus{$status} . ' '                     . '">' . $overallstatus{$status} . ' '
  . $statusStrings{$status} . "</font>";                     . $statusStrings{$status} . '</span>';
  }          }
     }      }
     $return.= $endtd;      $return.= $endtd;
     return $return;      return $return;
Line 1320  sub render { Line 1360  sub render {
             my $currenturl = $env{'form.postdata'};              my $currenturl = $env{'form.postdata'};
             #$currenturl=~s/^http\:\/\///;              #$currenturl=~s/^http\:\/\///;
             #$currenturl=~s/^[^\/]+//;              #$currenturl=~s/^[^\/]+//;
                           unless ($args->{'caller'} eq 'sequence') {
             $here = $jump = &Apache::lonnet::symbread($currenturl);                  $here = $jump = &Apache::lonnet::symbread($currenturl);
               }
  }   }
  if ($here eq '') {   if (($here eq '') && ($args->{'caller'} ne 'sequence')) {
     my $last;      my $last;
     if (tie(my %hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db',      if (tie(my %hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db',
                     &GDBM_READER(),0640)) {                      &GDBM_READER(),0640)) {
Line 1459  sub render { Line 1500  sub render {
             $link .= '&amp;register='.$env{'form.register'};              $link .= '&amp;register='.$env{'form.register'};
         }          }
  if ($args->{'caller'} eq 'navmapsdisplay') {   if ($args->{'caller'} eq 'navmapsdisplay') {
     &add_linkitem($args->{'linkitems'},'changefolder',              unless ($args->{'notools'}) {
                           "location.href='$link'",$text);                  &add_linkitem($args->{'linkitems'},'changefolder',
                                 "location.href='$link'",$text);
               }
  } else {   } else {
             $result.= '<a href="'.$link.'">'.&mt($text).'</a>';      $result.= '<a href="'.$link.'">'.&mt($text).'</a>';
  }   }
         $result .= "\n";          $result .= "\n";
     }      }
   
     # Check for any unread discussions in all resources.      # Check for any unread discussions in all resources.
     if ($args->{'caller'} eq 'navmapsdisplay') {      if (($args->{'caller'} eq 'navmapsdisplay') && (!$args->{'notools'})) {
  &add_linkitem($args->{'linkitems'},'clearbubbles',   &add_linkitem($args->{'linkitems'},'clearbubbles',
       'document.clearbubbles.submit()',        'document.clearbubbles.submit()',
       'Mark all posts read');        'Mark all posts read');
  my $time=time;   my $time=time;
           my $querystr = &HTML::Entities::encode($ENV{'QUERY_STRING'},'<>&"');
  $result .= (<<END);   $result .= (<<END);
     <form name="clearbubbles" method="post" action="/adm/feedback">      <form name="clearbubbles" method="post" action="/adm/feedback">
  <input type="hidden" name="navurl" value="$ENV{'QUERY_STRING'}" />   <input type="hidden" name="navurl" value="$querystr" />
  <input type="hidden" name="navtime" value="$time" />   <input type="hidden" name="navtime" value="$time" />
 END  END
         if ($env{'form.register'}) {          if ($env{'form.register'}) {
Line 1501  END Line 1545  END
  }   }
  $result.='</form>';   $result.='</form>';
     }      }
       if (($args->{'caller'} eq 'navmapsdisplay') &&
           ((&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) ||
            (&Apache::lonnet::allowed('cev',$env{'request.course.id'})))) {
           my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
           my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
           if ($env{'course.'.$env{'request.course.id'}.'.url'} eq 
               "uploaded/$cdom/$cnum/default.sequence") {
               &add_linkitem($args->{'linkitems'},'edittoplevel',
                             "javascript:gocmd('/adm/coursedocs','editdocs');",
                             'Content Editor');
           }
       }
   
     if ($args->{'caller'} eq 'navmapsdisplay') {      if ($args->{'caller'} eq 'navmapsdisplay') {
         $result .= '<table><tr><td>'.          $result .= &show_linkitems_toolbar($args,$condition);
                    &Apache::loncommon::help_open_menu('Navigation Screen','Navigation_Screen',undef,'RAT').'</td><td>&nbsp;</td>'.  
                    '<td class="LC_middle">'.&mt('Tools:').'</td>'.  
                    &show_linkitems_toolbar($args->{'linkitems'});  
         if ($args->{'sort_html'}) {  
     if ($env{'environment.remotenavmap'} ne 'on') {  
         $result .= '<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>'.  
                            '<td align="right">'.$args->{'sort_html'}.'</td></tr>';  
             } else {  
         $result .= '</tr><tr><td align="left" colspan="4"><br />'.  
                            $args->{'sort_html'}.'</td></tr>';  
             }  
         } else {  
             $result .= '</tr>';  
         }  
         $result .= '</table>';  
     } elsif ($args->{'sort_html'}) {       } elsif ($args->{'sort_html'}) { 
         $result.=$args->{'sort_html'};           $result.=$args->{'sort_html'}; 
     }      }
Line 1552  END Line 1593  END
     $args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='$location' alt='' />");      $args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='$location' alt='' />");
     $args->{'displayedHereMarker'} = 0;      $args->{'displayedHereMarker'} = 0;
   
     # If we're suppressing empty sequences, look for them here. Use DFS for speed,      # If we're suppressing empty sequences, look for them here.
     # since structure actually doesn't matter, except what map has what resources.      # We also do this even if $args->{'suppressEmptySequences'}
     if ($args->{'suppressEmptySequences'}) {      # is not true, so we can hide empty sequences for which the
         my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap,      # hiddenresource parameter is set to yes (at map level), or
                                                          $it->{FIRST_RESOURCE},      # mark as hidden for users who have $userCanSeeHidden.
                                                          $it->{FINISH_RESOURCE},      # Use DFS for speed, since structure actually doesn't matter,
                                                          {}, undef, 1);      # except what map has what resources.
         my $depth = 0;  
         $dfsit->next();      my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap,
         my $curRes = $dfsit->next();                                                       $it->{FIRST_RESOURCE},
         while ($depth > -1) {                                                       $it->{FINISH_RESOURCE},
             if ($curRes == $dfsit->BEGIN_MAP()) { $depth++; }                                                       {}, undef, 1);
             if ($curRes == $dfsit->END_MAP()) { $depth--; }  
       my $depth = 0;
             if (ref($curRes)) {       $dfsit->next();
                 # Parallel pre-processing: Do sequences have non-filtered-out children?      my $curRes = $dfsit->next();
                 if ($curRes->is_map()) {      while ($depth > -1) {
                     $curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0;          if ($curRes == $dfsit->BEGIN_MAP()) { $depth++; }
                     # Sequences themselves do not count as visible children,          if ($curRes == $dfsit->END_MAP()) { $depth--; }
                     # unless those sequences also have visible children.  
                     # This means if a sequence appears, there's a "promise"          if (ref($curRes)) {
                     # that there's something under it if you open it, somewhere.              # Parallel pre-processing: Do sequences have non-filtered-out children?
                 } else {              if ($curRes->is_map()) {
                     # Not a sequence: if it's filtered, ignore it, otherwise                  $curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0;
                     # rise up the stack and mark the sequences as having children                  # Sequences themselves do not count as visible children,
                     if (&$filterFunc($curRes)) {                  # unless those sequences also have visible children.
                         for my $sequence (@{$dfsit->getStack()}) {                  # This means if a sequence appears, there's a "promise"
                             $sequence->{DATA}->{HAS_VISIBLE_CHILDREN} = 1;                  # that there's something under it if you open it, somewhere.
                         }              } elsif ($curRes->src()) {
                   # Not a sequence: if it's filtered, ignore it, otherwise
                   # rise up the stack and mark the sequences as having children
                   if (&$filterFunc($curRes)) {
                       for my $sequence (@{$dfsit->getStack()}) {
                           $sequence->{DATA}->{HAS_VISIBLE_CHILDREN} = 1;
                     }                      }
                 }                  }
             }              }
         } continue {  
             $curRes = $dfsit->next();  
         }          }
       } continue {
           $curRes = $dfsit->next();
     }      }
   
     my $displayedJumpMarker = 0;      my $displayedJumpMarker = 0;
Line 1679  END Line 1725  END
         }           } 
   
         # If this is an empty sequence and we're filtering them, continue on          # If this is an empty sequence and we're filtering them, continue on
         if ($curRes->is_map() && $args->{'suppressEmptySequences'} &&          $args->{'mapHidden'} = 0;
             !$curRes->{DATA}->{HAS_VISIBLE_CHILDREN}) {          if (($curRes->is_map()) && (!$curRes->{DATA}->{HAS_VISIBLE_CHILDREN})) {
             next;              if ($args->{'suppressEmptySequences'}) {
                   next;
               } else {
                   my $mapname = &Apache::lonnet::declutter($curRes->src());
                   $mapname = &Apache::lonnet::deversion($mapname);
                   if (lc($navmap->get_mapparam(undef,$mapname,"0.hiddenresource")) eq 'yes') {
                       if ($userCanSeeHidden) {
                           $args->{'mapHidden'} = 1;
                       } else {
                           next;
                       }
                   }
               }
         }          }
   
         # If we're suppressing navmaps and this is a navmap, continue on          # If we're suppressing navmaps and this is a navmap, continue on
Line 1765  END Line 1823  END
  $stack=$it->getStack();   $stack=$it->getStack();
     }      }
     ($src,$symb,$anchor)=getLinkForResource($stack);      ($src,$symb,$anchor)=getLinkForResource($stack);
               my $srcHasQuestion = $src =~ /\?/; 
     if (defined($anchor)) { $anchor='#'.$anchor; }      if (defined($anchor)) { $anchor='#'.$anchor; }
     my $srcHasQuestion = $src =~ /\?/;              if (($args->{'caller'} eq 'sequence') && ($curRes->is_map())) {
     $args->{"resourceLink"} = $src.                  $args->{"resourceLink"} = $src.($srcHasQuestion?'&amp;':'?') .'navmap=1';
  ($srcHasQuestion?'&amp;':'?') .              } else {
  'symb=' . &escape($symb).$anchor;                  $args->{"resourceLink"} = $src.
       ($srcHasQuestion?'&amp;':'?') .
       'symb=' . &escape($symb).$anchor;
               }
  }   }
         # Now, we've decided what parts to show. Loop through them and          # Now, we've decided what parts to show. Loop through them and
         # show them.          # show them.
Line 1821  END Line 1883  END
     }      }
  }   }
     }      }
   
       $result.=&Apache::loncommon::end_data_table();
           
     # Print out the part that jumps to #curloc if it exists      # Print out the part that jumps to #curloc if it exists
     # delay needed because the browser is processing the jump before      # delay needed because the browser is processing the jump before
Line 1837  if (location.href.indexOf('#curloc')==-1 Line 1901  if (location.href.indexOf('#curloc')==-1
 ");  ");
     }      }
   
     $result.=&Apache::loncommon::end_data_table();  
   
     if ($r) {      if ($r) {
         $r->print($result);          $r->print($result);
         $result = "";          $result = "";
Line 1855  sub add_linkitem { Line 1917  sub add_linkitem {
 }  }
   
 sub show_linkitems_toolbar {  sub show_linkitems_toolbar {
     my ($linkitems,$condition)=@_;      my ($args,$condition) = @_;
     my @linkorder = ("launchnav","closenav","firsthomework",      my $result;
      "everything","uncompleted","changefolder","clearbubbles");      if (ref($args) eq 'HASH') {
     my $result .='<td align="left">'."\n".          if (ref($args->{'linkitems'}) eq 'HASH') {
                  '<span class="LC_nobreak">'."\n".              my $numlinks = scalar(keys(%{$args->{'linkitems'}}));
                  '<ul id="LC_toolbar">';              if ($numlinks > 1) {
     foreach my $link (@linkorder) {                  $result = '<td>'.
         my $link_id = 'LC_content_toolbar_'.$link;                            &Apache::loncommon::help_open_menu('Navigation Screen','Navigation_Screen',
         if (defined($linkitems->{$link})) {                                                               undef,'RAT').
             if ($linkitems->{$link}{'text'} ne '') {                            '</td>'.
                 $linkitems->{$link}{'cmd'}=~s/"/'/g;                            '<td>&nbsp;</td>'.
                 if ($linkitems->{$link}{'cmd'}) {                            '<td class="LC_middle">'.&mt('Tools:').'</td>';
                     if ($link eq 'changefolder') {              }
                         if ($condition) {              $result .= '<td align="left">'."\n".
                             $link_id='LC_content_toolbar_changefolder_toggled';                         '<ul id="LC_toolbar">';
                         } else {              my @linkorder = ('firsthomework','everything','uncompleted',
                             $link_id='LC_content_toolbar_changefolder';                               'changefolder','clearbubbles','edittoplevel');
               foreach my $link (@linkorder) {
                   if (ref($args->{'linkitems'}{$link}) eq 'HASH') {
                       if ($args->{'linkitems'}{$link}{'text'} ne '') {
                           $args->{'linkitems'}{$link}{'cmd'}=~s/"/'/g;
                           if ($args->{'linkitems'}{$link}{'cmd'}) {
                               my $link_id = 'LC_content_toolbar_'.$link;
                               if ($link eq 'changefolder') {
                                   if ($condition) {
                                       $link_id='LC_content_toolbar_changefolder_toggled';
                                   } else {
                                       $link_id='LC_content_toolbar_changefolder';
                                   }
                               }
                               $result .= '<li><a href="#" '.
                                          'onclick="'.$args->{'linkitems'}{$link}{'cmd'}.'" '.
                                          'id="'.$link_id.'" '.
                                          'class="LC_toolbarItem" '.
                                          'title="'.$args->{'linkitems'}{$link}{'text'}.'">'.
                                          '</a></li>'."\n";
                         }                          }
                     }                      }
                     $result .= '<li><a href="#" '.  
                                'onclick="'.$linkitems->{$link}{'cmd'}.'" '.  
                                'id="'.$link_id.'" '.  
                                'class="LC_toolbarItem" '.  
                                'title="'.$linkitems->{$link}{'text'}.'">'.  
                                '</a></li>'."\n";  
                 }                  }
             }              }
               $result .= '</ul>'.
                          '</td>';
               if (($numlinks==1) && (exists($args->{'linkitems'}{'edittoplevel'}))) {
                   $result .= '<td><a href="'.$args->{'linkitems'}{'edittoplevel'}{'cmd'}.'">'.
                              &mt('Content Editor').'</a></td>';
               }
           }
           if ($args->{'sort_html'}) {
               $result .= '<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>'.
                          '<td align="right">'.$args->{'sort_html'}.'</td>';
         }          }
     }      }
     $result .= '</ul>'.      if ($result) {
                '</span></td>'."\n";          $result = "<table><tr>$result</tr></table>";
       }
     return $result;      return $result;
 }  }
   
   
 1;  1;
   
   
Line 1976  sub new { Line 2061  sub new {
     my $proto = shift;      my $proto = shift;
     my $class = ref($proto) || $proto;      my $class = ref($proto) || $proto;
     my $self = {};      my $self = {};
       bless($self); # So we can call change_user if necessary
   
       $self->{USERNAME} = shift || $env{'user.name'};
       $self->{DOMAIN}   = shift || $env{'user.domain'};
       $self->{CODE}     = shift;
       $self->{NOHIDE} = shift;
   
   
   
     # Resource cache stores navmap resources as we reference them. We generate      # Resource cache stores navmap resources as we reference them. We generate
     # them on-demand so we don't pay for creating resources unless we use them.      # them on-demand so we don't pay for creating resources unless we use them.
Line 1985  sub new { Line 2078  sub new {
     # failed      # failed
     $self->{NETWORK_FAILURE} = 0;      $self->{NETWORK_FAILURE} = 0;
   
     # tie the nav hash      # We can only tie the nav hash as done below if the username/domain
       # match the env one. Otherwise change_user does everything we need...since we can't
       # assume there are course hashes for the specific requested user:domain
       # Note: change_user is also called if we need the nav hash when printing CODEd 
       # assignments or printing an exam, in which the enclosing folder for the items in
       # the exam has hidden set.
       #
   
     my %navmaphash;      if (($self->{USERNAME} eq $env{'user.name'}) && ($self->{DOMAIN} eq $env{'user.domain'}) &&
     my %parmhash;           !$self->{CODE} && !$self->{NOHIDE}) {
     my $courseFn = $env{"request.course.fn"};  
     if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db",   # tie the nav hash
               &GDBM_READER(), 0640))) {  
         return undef;   my %navmaphash;
    my %parmhash;
    my $courseFn = $env{"request.course.fn"};
    if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db",
     &GDBM_READER(), 0640))) {
       return undef;
    }
   
    if (!(tie(%parmhash, 'GDBM_File', "${courseFn}_parms.db",
     &GDBM_READER(), 0640)))
    {
       untie %{$self->{PARM_HASH}};
       return undef;
    }
   
    $self->{NAV_HASH} = \%navmaphash;
    $self->{PARM_HASH} = \%parmhash;
    $self->{PARM_CACHE} = {};
       } else {
    $self->change_user($self->{USERNAME}, $self->{DOMAIN},  $self->{CODE}, $self->{NOHIDE});
     }      }
   
       return $self;
   }
   
   #
   #  In some instances it is useful to be able to dynamically change the
   # username/domain associated with a navmap (e.g. to navigate for someone
   # else besides the current user...if sufficiently privileged.
   # Parameters:
   #    user  - New user.
   #    domain- Domain the user belongs to.
   #    code  - Anonymous CODE in use.
   # Implicit inputs:
   #   
   sub change_user {
       my $self = shift;
       $self->{USERNAME} = shift;
       $self->{DOMAIN}   = shift;
       $self->{CODE}     = shift;
       $self->{NOHIDE}   = shift;
   
       # If the hashes are already tied make sure to break that bond:
   
       untie %{$self->{NAV_HASH}}; 
       untie %{$self->{PARM_HASH}};
   
       # The assumption is that we have to
       # use lonmap here to re-read the hash and from it reconstruct
       # new big and parameter hashes.  An implicit assumption at this time
       # is that the course file is probably not created locally yet
       # an that we will therefore just read without tying.
   
       my ($cdom, $cnum) = split(/\_/, $env{'request.course.id'});
   
       my %big_hash;
       &Apache::lonmap::loadmap($cnum, $cdom, $self->{USERNAME}, $self->{DOMAIN}, $self->{CODE}, $self->{NOHIDE}, \%big_hash);
       $self->{NAV_HASH} = \%big_hash;
   
   
   
       # Now clear the parm cache and reconstruct the parm hash fromt he big_hash
       # param.xxxx keys.
   
       $self->{PARM_CACHE} = {};
           
     if (!(tie(%parmhash, 'GDBM_File', "${courseFn}_parms.db",      my %parm_hash = {};
               &GDBM_READER(), 0640)))      foreach my $key (keys(%big_hash)) {
     {   if ($key =~ /^param\./) {
         untie %{$self->{PARM_HASH}};      my $param_key = $key;
         return undef;      $param_key =~ s/^param\.//;
       $parm_hash{$param_key} = $big_hash{$key};
    }
     }      }
   
     $self->{NAV_HASH} = \%navmaphash;      $self->{PARM_HASH} = \%parm_hash;
     $self->{PARM_HASH} = \%parmhash;  
     $self->{PARM_CACHE} = {};  
   
     bless($self);  
           
     return $self;  
 }  }
   
 sub generate_course_user_opt {  sub generate_course_user_opt {
     my $self = shift;      my $self = shift;
     if ($self->{COURSE_USER_OPT_GENERATED}) { return; }      if ($self->{COURSE_USER_OPT_GENERATED}) { return; }
   
     my $uname=$env{'user.name'};      my $uname=$self->{USERNAME};
     my $udom=$env{'user.domain'};      my $udom=$self->{DOMAIN};
   
     my $cid=$env{'request.course.id'};      my $cid=$env{'request.course.id'};
     my $cdom=$env{'course.'.$cid.'.domain'};      my $cdom=$env{'course.'.$cid.'.domain'};
     my $cnum=$env{'course.'.$cid.'.num'};      my $cnum=$env{'course.'.$cid.'.num'};
Line 2050  sub generate_course_user_opt { Line 2210  sub generate_course_user_opt {
     return;      return;
 }  }
   
   
   
 sub generate_email_discuss_status {  sub generate_email_discuss_status {
     my $self = shift;      my $self = shift;
     my $symb = shift;      my $symb = shift;
Line 2059  sub generate_email_discuss_status { Line 2221  sub generate_email_discuss_status {
     my $cdom=$env{'course.'.$cid.'.domain'};      my $cdom=$env{'course.'.$cid.'.domain'};
     my $cnum=$env{'course.'.$cid.'.num'};      my $cnum=$env{'course.'.$cid.'.num'};
           
     my %emailstatus = &Apache::lonnet::dump('email_status');      my %emailstatus = &Apache::lonnet::dump('email_status',$self->{DOMAIN},$self->{USERNAME});
     my $logoutTime = $emailstatus{'logout'};      my $logoutTime = $emailstatus{'logout'};
     my $courseLeaveTime = $emailstatus{'logout_'.$env{'request.course.id'}};      my $courseLeaveTime = $emailstatus{'logout_'.$env{'request.course.id'}};
     $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?      $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?
Line 2067  sub generate_email_discuss_status { Line 2229  sub generate_email_discuss_status {
     my %discussiontime = &Apache::lonnet::dump('discussiontimes',       my %discussiontime = &Apache::lonnet::dump('discussiontimes', 
        $cdom, $cnum);         $cdom, $cnum);
     my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',      my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',
                                         $env{'user.domain'},$env{'user.name'},'lastread');                                          $self->{DOMAIN},$self->{USERNAME},'lastread');
     my %lastreadtime = ();      my %lastreadtime = ();
     foreach my $key (keys %lastread) {      foreach my $key (keys(%lastread)) {
         my $shortkey = $key;          my $shortkey = $key;
         $shortkey =~ s/_lastread$//;          $shortkey =~ s/_lastread$//;
         $lastreadtime{$shortkey} = $lastread{$key};          $lastreadtime{$shortkey} = $lastread{$key};
Line 2077  sub generate_email_discuss_status { Line 2239  sub generate_email_discuss_status {
   
     my %feedback=();      my %feedback=();
     my %error=();      my %error=();
     my @keys = &Apache::lonnet::getkeys('nohist_email',$env{'user.domain'},      my @keys = &Apache::lonnet::getkeys('nohist_email',$self->{DOMAIN},
  $env{'user.name'});   $self->{USERNAME});
           
     foreach my $msgid (@keys) {      foreach my $msgid (@keys) {
  if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {   if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {
             my ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$fromcid,              my ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$fromcid,
                 $symb,$error) = &Apache::lonmsg::unpackmsgid($msgid);                  $symb,$error) = &Apache::lonmsg::unpackmsgid(&LONCAPA::escape($msgid));
             &Apache::lonenc::check_decrypt(\$symb);               &Apache::lonenc::check_decrypt(\$symb); 
             if (($fromcid ne '') && ($fromcid ne $cid)) {              if (($fromcid ne '') && ($fromcid ne $cid)) {
                 next;                  next;
Line 2126  sub get_user_data { Line 2288  sub get_user_data {
   
     # Retrieve performance data on problems      # Retrieve performance data on problems
     my %student_data = Apache::lonnet::currentdump($env{'request.course.id'},      my %student_data = Apache::lonnet::currentdump($env{'request.course.id'},
    $env{'user.domain'},     $self->{DOMAIN},
    $env{'user.name'});     $self->{USERNAME});
     $self->{STUDENT_DATA} = \%student_data;      $self->{STUDENT_DATA} = \%student_data;
   
     $self->{RETRIEVED_USER_DATA} = 1;      $self->{RETRIEVED_USER_DATA} = 1;
Line 2441  sub parmval { Line 2603  sub parmval {
     my $self = shift;      my $self = shift;
     my ($what,$symb,$recurse)=@_;      my ($what,$symb,$recurse)=@_;
     my $hashkey = $what."|||".$symb;      my $hashkey = $what."|||".$symb;
       my $cache = $self->{PARM_CACHE};
     if (defined($self->{PARM_CACHE}->{$hashkey})) {      if (defined($self->{PARM_CACHE}->{$hashkey})) {
         if (ref($self->{PARM_CACHE}->{$hashkey}) eq 'ARRAY') {           if (ref($self->{PARM_CACHE}->{$hashkey}) eq 'ARRAY') { 
             if (defined($self->{PARM_CACHE}->{$hashkey}->[0])) {              if (defined($self->{PARM_CACHE}->{$hashkey}->[0])) {
Line 2463  sub parmval { Line 2625  sub parmval {
     return $result->[0];      return $result->[0];
 }  }
   
   
 sub parmval_real {  sub parmval_real {
     my $self = shift;      my $self = shift;
     my ($what,$symb,$recurse) = @_;      my ($what,$symb,$recurse) = @_;
   
   
     # Make sure the {USER_OPT} and {COURSE_OPT} hashes are populated      # Make sure the {USER_OPT} and {COURSE_OPT} hashes are populated
     $self->generate_course_user_opt();      $self->generate_course_user_opt();
   
Line 2478  sub parmval_real { Line 2642  sub parmval_real {
         @cgrps = sort(@cgrps);          @cgrps = sort(@cgrps);
         $cgroup = $cgrps[0];          $cgroup = $cgrps[0];
     }       } 
     my $uname=$env{'user.name'};      my $uname=$self->{USERNAME};
     my $udom=$env{'user.domain'};      my $udom=$self->{DOMAIN};
   
     unless ($symb) { return ['']; }      unless ($symb) { return ['']; }
     my $result='';      my $result='';
Line 2495  sub parmval_real { Line 2659  sub parmval_real {
     my $mapparm=$mapname.'___(all).'.$what;      my $mapparm=$mapname.'___(all).'.$what;
     my $usercourseprefix=$cid;      my $usercourseprefix=$cid;
   
   
   
     my $grplevel=$usercourseprefix.'.['.$cgroup.'].'.$what;      my $grplevel=$usercourseprefix.'.['.$cgroup.'].'.$what;
     my $grplevelr=$usercourseprefix.'.['.$cgroup.'].'.$symbparm;      my $grplevelr=$usercourseprefix.'.['.$cgroup.'].'.$symbparm;
     my $grplevelm=$usercourseprefix.'.['.$cgroup.'].'.$mapparm;      my $grplevelm=$usercourseprefix.'.['.$cgroup.'].'.$mapparm;
   
   
     my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what;      my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what;
     my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm;      my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm;
     my $seclevelm=$usercourseprefix.'.['.$csec.'].'.$mapparm;      my $seclevelm=$usercourseprefix.'.['.$csec.'].'.$mapparm;
   
   
     my $courselevel= $usercourseprefix.'.'.$what;      my $courselevel= $usercourseprefix.'.'.$what;
     my $courselevelr=$usercourseprefix.'.'.$symbparm;      my $courselevelr=$usercourseprefix.'.'.$symbparm;
     my $courselevelm=$usercourseprefix.'.'.$mapparm;      my $courselevelm=$usercourseprefix.'.'.$mapparm;
   
   
     my $useropt = $self->{USER_OPT};      my $useropt = $self->{USER_OPT};
     my $courseopt = $self->{COURSE_OPT};      my $courseopt = $self->{COURSE_OPT};
     my $parmhash = $self->{PARM_HASH};      my $parmhash = $self->{PARM_HASH};
Line 2574  sub parmval_real { Line 2743  sub parmval_real {
     return [''];      return [''];
 }  }
   
   sub recurseup_maps {
       my ($self,$mapname) = @_;
       my @recurseup;
       if ($mapname) {
           my $res = $self->getResourceByUrl($mapname);
           if (ref($res)) {
               my @pcs = split(/,/,$res->map_hierarchy());
               shift(@pcs);
               if (@pcs) {
                   @recurseup = map { &Apache::lonnet::declutter($self->getByMapPc($_)->src()); } reverse(@pcs);
               }
           }
       }
       return @recurseup;
   }
   
   sub recursed_crumbs {
       my ($self,$mapurl,$restitle) = @_;
       my (@revmapinfo,@revmapres);
       my $mapres = $self->getResourceByUrl($mapurl);
       if (ref($mapres)) {
           @revmapres = map { $self->getByMapPc($_); } split(/,/,$mapres->map_breadcrumbs());
           shift(@revmapres);
       }
       my $allowedlength = 60;
       my $minlength = 5;
       my $allowedtitle = 30;
       if (($env{'environment.icons'} eq 'iconsonly') && (!$env{'browser.mobile'})) {
           $allowedlength = 100;
           $allowedtitle = 70;
       }
       if (length($restitle) > $allowedtitle) {
           $restitle = &truncate_crumb_text($restitle,$allowedtitle);
       }
       my $totallength = length($restitle);
       my @links;
   
       foreach my $map (@revmapres) {
           my $pc = $map->map_pc();
           next if ((!$pc) || ($pc == 1));
           push(@links,$map);
           push(@revmapinfo,{'href' => $map->link().'?navmap=1','text' => $map->title(),'no_mt' => 1,});
           $totallength += length($map->title());
       }
       my $numlinks = scalar(@links);
       if ($numlinks) {
           if ($totallength - $allowedlength > 0) {
               my $available = $allowedlength - length($restitle);
               my $avg = POSIX::ceil($available/$numlinks);
               if ($avg < $minlength) {
                   $avg = $minlength;
               }
               @revmapinfo = ();
               foreach my $map (@links) {
                   my $showntitle = &truncate_crumb_text($map->title(),$avg);
                   if ($showntitle ne '') {
                       push(@revmapinfo,{'href' => $map->link().'?navmap=1','text' => $showntitle,'no_mt' => 1,});
                   }
               }
           }
       }
       if ($restitle ne '') {
           push(@revmapinfo,{'text' => $restitle, 'no_mt' => 1});
       }
       return @revmapinfo;
   }
   
   sub truncate_crumb_text {
       my ($title,$limit) = @_;
       my $showntitle = '';
       if (length($title) > $limit) {
           my @words = split(/\b\s*/,$title);
           if (@words == 1) {
               $showntitle = substr($title,0,$limit).' ...';
           } else {
               my $linklength = 0;
               my $num = 0;
               foreach my $word (@words) {
                   $linklength += 1+length($word);
                   if ($word eq '-') {
                       $showntitle =~ s/ $//;
                       $showntitle .= $word;
                   } elsif ($linklength > $limit) {
                       if ($num < @words) {
                           $showntitle .= $word.' ...';
                           last;
                       } else {
                           $showntitle .= $word;
                       }
                   } else {
                       $showntitle .= $word.' ';
                   }
               }
               $showntitle =~ s/ $//;
           }
           return $showntitle;
       } else {
           return $title;
       }
   }
   
   #
   #  Determines the open/close dates for printing a map that
   #  encloses a resource.
   #
   sub map_printdates {
       my ($self, $res, $part) = @_;
   
   
   
   
   
       my $opendate = $self->get_mapparam($res->symb(),'',"$part.printstartdate");
       my $closedate= $self->get_mapparam($res->symb(),'', "$part.printenddate");
   
   
       return ($opendate, $closedate);
   }
   
   sub get_mapparam {
       my ($self, $symb, $mapname, $what) = @_;
   
       # Ensure the course option hash is populated:
   
       $self->generate_course_user_opt();
   
       # Get the course id and section if there is one.
   
       my $cid=$env{'request.course.id'};
       my $csec=$env{'request.course.sec'};
       my $cgroup='';
       my @cgrps=split(/:/,$env{'request.course.groups'});
       if (@cgrps > 0) {
           @cgrps = sort(@cgrps);
           $cgroup = $cgrps[0];
       } 
       my $uname=$self->{USERNAME};
       my $udom=$self->{DOMAIN};
   
       unless ($symb || $mapname) { return; }
       my $result='';
       my ($recursed,@recurseup);
   
       # Figure out which map we are in.
   
       if ($symb && !$mapname) {
           my ($id,$fn);
           ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
           $mapname = &Apache::lonnet::deversion($mapname);
       }
   
       my $rwhat=$what;
       $what=~s/^parameter\_//;
       $what=~s/\_/\./;
   
       # Build the hash keys for the lookup:
   
       my $symbparm=$symb.'.'.$what;
       my $mapparm=$mapname.'___(all).'.$what;
       my $usercourseprefix=$cid;
   
   
       my $grplevel    = "$usercourseprefix.[$cgroup].$mapparm";
       my $seclevel    = "$usercourseprefix.[$csec].$mapparm";
       my $courselevel = "$usercourseprefix.$mapparm";
   
   
       # Get handy references to the hashes we need in $self:
   
       my $useropt = $self->{USER_OPT};
       my $courseopt = $self->{COURSE_OPT};
       my $parmhash = $self->{PARM_HASH};
   
       # Check per user 
   
   
   
       if ($uname and defined($useropt)) {
    if (defined($$useropt{$courselevel})) {
       return $$useropt{$courselevel};
    }
           if ($what =~ /\.(encrypturl|hiddenresource)$/) {
               unless ($recursed) {
                   @recurseup = $self->recurseup_maps($mapname);
                   $recursed = 1;
               }
               foreach my $item (@recurseup) {
                   my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what;
                   if (defined($$useropt{$norecursechk})) {
                       if ($what =~ /\.(encrypturl|hiddenresource)$/) {
                           return $$useropt{$norecursechk};
                       }
                   }
               }
           }
       }
   
       # Check course -- group
   
   
   
       if ($cgroup ne '' and defined ($courseopt)) {
    if (defined($$courseopt{$grplevel})) {
       return $$courseopt{$grplevel};
    }
           if ($what =~ /\.(encrypturl|hiddenresource)$/) {
               unless ($recursed) {
                   @recurseup = $self->recurseup_maps($mapname);
                   $recursed = 1;
               }
               foreach my $item (@recurseup) {
                   my $norecursechk=$usercourseprefix.'.['.$cgroup.'].'.$item.'___(all).'.$what;
                   if (defined($$courseopt{$norecursechk})) {
                       if ($what =~ /\.(encrypturl|hiddenresource)$/) {
                           return $$courseopt{$norecursechk};
                       }
                   }
               }
           }
       }
   
       # Check course -- section
   
   
   
   
   
       if ($csec and defined($courseopt)) {
    if (defined($$courseopt{$seclevel})) {
       return $$courseopt{$seclevel};
    }
           if ($what =~ /\.(encrypturl|hiddenresource)$/) {
               unless ($recursed) {
                   @recurseup = $self->recurseup_maps($mapname);
                   $recursed = 1;
               }
               foreach my $item (@recurseup) {
                   my $norecursechk=$usercourseprefix.'.['.$csec.'].'.$item.'___(all).'.$what;
                   if (defined($$courseopt{$norecursechk})) {
                       if ($what =~ /\.(encrypturl|hiddenresource)$/) {
                           return $$courseopt{$norecursechk};
                       }
                   }
               }
           }
       }
       # Check the map parameters themselves:
   
       if ($symb) {
           my $symbparm=$symb.'.'.$what;
           my $thisparm = $$parmhash{$symbparm};
           if (defined($thisparm)) {
               return $thisparm;
           }
       }
   
   
       # Additional course parameters:
   
       if (defined($courseopt)) {
    if (defined($$courseopt{$courselevel})) {
       return $$courseopt{$courselevel};
    }
           if ($what =~ /\.(encrypturl|hiddenresource)$/) {
               unless ($recursed) {
                   @recurseup = $self->recurseup_maps($mapname);
                   $recursed = 1;
               }
               foreach my $item (@recurseup) {
                   my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what;
                   if (defined($$courseopt{$norecursechk})) {
                       if ($what =~ /\.(encrypturl|hiddenresource)$/) {
                           return $$courseopt{$norecursechk};
                       }
                   }
               }
           }
       }
       return undef; # Unefined if we got here.
   }
   
   sub course_printdates {
       my ($self, $symb,  $part) = @_;
   
   
       my $opendate  = $self->getcourseparam($symb, $part . '.printstartdate');
       my $closedate = $self->getcourseparam($symb, $part . '.printenddate');
       return ($opendate, $closedate);
   
   }
   
   sub getcourseparam {
       my ($self, $symb, $what) = @_;
   
       $self->generate_course_user_opt(); # If necessary populate the hashes.
   
       my $uname = $self->{USERNAME};
       my $udom  = $self->{DOMAIN};
       
       # Course, section, group ids come from the env:
   
       my $cid   = $env{'request.course.id'};
       my $csec  = $env{'request.course.sec'};
       my $cgroup = ''; # Assume no group
   
       my @cgroups = split(/:/, $env{'request.course.groups'});
       if(@cgroups > 0) {
    @cgroups = sort(@cgroups);
    $cgroup  = $cgroups[0]; # There is a course group. 
      }
       my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
       $mapname = &Apache::lonnet::deversion($mapname);
   
       #
       # Make the various lookup keys:
       #
   
       $what=~s/^parameter\_//;
       $what=~s/\_/\./;
   
   
       my $symbparm = $symb . '.' . $what;
       my $mapparm=$mapname.'___(all).'.$what;
   
       # Local refs to the hashes we're going to look at:
   
       my $useropt   = $self->{USER_OPT};
       my $courseopt = $self->{COURSE_OPT};
   
       # 
       # We want the course level stuff from the way
       # parmval_real operates 
       # TODO: Factor some of this stuff out of
       # both parmval_real and here
       #
       my $courselevel = $cid . '.' .  $what;
       my $grplevel    = $cid . '.[' . $cgroup   . ']' . $what;
       my $seclevel    = $cid . '.[' . $csec     . ']' . $what;
   
   
       # Try for the user's course level option:
   
       if ($uname and defined($useropt)) {
    if (defined($$useropt{$courselevel})) {
       return $$useropt{$courselevel};
    }
       }
       # Try for the group's course level option:
   
       if ($cgroup ne '' and defined($courseopt)) {
    if (defined($$courseopt{$grplevel})) {
       return $$courseopt{$grplevel};
    }
       }
   
       #  Try for section level parameters:
   
       if ($csec ne '' and defined($courseopt)) {
    if (defined($$courseopt{$seclevel})) {
       return $$courseopt{$seclevel};
    }
       }
       # Try for 'additional' course parameters:
   
       if (defined($courseopt)) {
    if (defined($$courseopt{$courselevel})) {
       return $$courseopt{$courselevel};
    }
       }
       return undef;
   
   }
   
   
 =pod  =pod
   
 =item * B<getResourceByUrl>(url,multiple):  =item * B<getResourceByUrl>(url,multiple):
Line 2588  resource appears multiple times in the c Line 3131  resource appears multiple times in the c
 will be returned (useful for maps), unless the multiple parameter has  will be returned (useful for maps), unless the multiple parameter has
 been included, in which case all instances are returned in an array.  been included, in which case all instances are returned in an array.
   
 =item * B<retrieveResources>(map, filterFunc, recursive, bailout, showall):  =item * B<retrieveResources>(map, filterFunc, recursive, bailout, showall, noblockcheck):
   
 The map is a specification of a map to retreive the resources from,  The map is a specification of a map to retreive the resources from,
 either as a url or as an object. The filterFunc is a reference to a  either as a url or as an object. The filterFunc is a reference to a
Line 2597  true if the resource should be included, Line 3140  true if the resource should be included,
 be. If recursive is true, the map will be recursively examined,  be. If recursive is true, the map will be recursively examined,
 otherwise it will not be. If bailout is true, the function will return  otherwise it will not be. If bailout is true, the function will return
 as soon as it finds a resource, if false it will finish. If showall is  as soon as it finds a resource, if false it will finish. If showall is
 true it will not hide maps that contain nothing but one other map. By  true it will not hide maps that contain nothing but one other map. The 
 default, the map is the top-level map of the course, filterFunc is a  noblockcheck arg is propagated to become the sixth arg in the call to
 function that always returns 1, recursive is true, bailout is false,  lonnet::allowed when checking a resource's availability during collection
 showall is false. The resources will be returned in a list containing  of resources using the iterator. noblockcheck needs to be true if 
 the resource objects for the corresponding resources, with B<no  retrieveResources() was called by a routine that itself was called by 
 structure information> in the list; regardless of branching,  lonnet::allowed, in order to avoid recursion.  By default the map  
 recursion, etc., it will be a flat list.  is the top-level map of the course, filterFunc is a function that 
   always returns 1, recursive is true, bailout is false, showall is
   false. The resources will be returned in a list containing the
   resource objects for the corresponding resources, with B<no structure 
   information> in the list; regardless of branching, recursion, etc.,
   it will be a flat list.
   
 Thus, this is suitable for cases where you don't want the structure,  Thus, this is suitable for cases where you don't want the structure,
 just a list of all resources. It is also suitable for finding out how  just a list of all resources. It is also suitable for finding out how
Line 2626  in the filter function. Line 3174  in the filter function.
 Retrieves version infomation for a url. Returns the version (a number, or   Retrieves version infomation for a url. Returns the version (a number, or 
 the string "mostrecent") for resources which have version information in    the string "mostrecent") for resources which have version information in  
 the big hash.  the big hash.
       
 =cut  =cut
   
   
Line 2670  sub retrieveResources { Line 3218  sub retrieveResources {
     my $bailout = shift;      my $bailout = shift;
     if (!defined($bailout)) { $bailout = 0; }      if (!defined($bailout)) { $bailout = 0; }
     my $showall = shift;      my $showall = shift;
       my $noblockcheck = shift;
     # Create the necessary iterator.      # Create the necessary iterator.
     if (!ref($map)) { # assume it's a url of a map.      if (!ref($map)) { # assume it's a url of a map.
         $map = $self->getResourceByUrl($map);          $map = $self->getResourceByUrl($map);
Line 2699  sub retrieveResources { Line 3248  sub retrieveResources {
     # Run down the iterator and collect the resources.      # Run down the iterator and collect the resources.
     my $curRes;      my $curRes;
   
     while ($curRes = $it->next()) {      while ($curRes = $it->next(undef,$noblockcheck)) {
         if (ref($curRes)) {          if (ref($curRes)) {
             if (!&$filterFunc($curRes)) {              if (!&$filterFunc($curRes)) {
                 next;                  next;
Line 2850  Note that inside of the loop, it's frequ Line 3399  Note that inside of the loop, it's frequ
 resource objects will be references, and any non-references will   resource objects will be references, and any non-references will 
 be the tokens described above.  be the tokens described above.
   
 Also note there is some old code floating around that trys to track  The next() routine can take two (optional) arguments:
   closeAllPages - if true will not recurse down a .page
   noblockcheck - passed to browsePriv() for passing as sixth arg to
   call to lonnet::allowed. This needs to be set if retrieveResources
   was already called from another routine called within lonnet::allowed, 
   so as to prevent recursion.
   
   Also note there is some old code floating around that tries to track
 the depth of the iterator to see when it's done; do not copy that   the depth of the iterator to see when it's done; do not copy that 
 code. It is difficult to get right and harder to understand than  code. It is difficult to get right and harder to understand than
 this. They should be migrated to this new style.  this. They should be migrated to this new style.
Line 2881  sub new { Line 3437  sub new {
     weaken($self->{NAV_MAP} = shift);      weaken($self->{NAV_MAP} = shift);
     return undef unless ($self->{NAV_MAP});      return undef unless ($self->{NAV_MAP});
   
       $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME};
       $self->{DOMAIN}   = $self->{NAV_MAP}->{DOMAIN};
   
     # Handle the parameters      # Handle the parameters
     $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();      $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();
     $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();      $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();
Line 2994  sub new { Line 3553  sub new {
     if ($resourceCount == 1 && $resource->is_sequence() && !$self->{FORCE_TOP}) {       if ($resourceCount == 1 && $resource->is_sequence() && !$self->{FORCE_TOP}) { 
         my $firstResource = $resource->map_start();          my $firstResource = $resource->map_start();
         my $finishResource = $resource->map_finish();          my $finishResource = $resource->map_finish();
         return    return Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
             Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,   $finishResource, $self->{FILTER},
                                               $finishResource, $self->{FILTER},   $self->{ALREADY_SEEN}, 
                                               $self->{ALREADY_SEEN},    $self->{CONDITION},
                                               $self->{CONDITION},   $self->{FORCE_TOP});
       $self->{FORCE_TOP});  
           
     }      }
   
     # Set up some bookkeeping information.      # Set up some bookkeeping information.
Line 3019  sub new { Line 3576  sub new {
     $self->{ALREADY_SEEN}->{$self->{FIRST_RESOURCE}->{ID}} = 1;      $self->{ALREADY_SEEN}->{$self->{FIRST_RESOURCE}->{ID}} = 1;
   
     bless ($self);      bless ($self);
   
     return $self;      return $self;
 }  }
   
 sub next {  sub next {
     my $self = shift;      my $self = shift;
     my $closeAllPages=shift;      my $closeAllPages=shift;
       my $noblockcheck = shift;
     if ($self->{FINISHED}) {      if ($self->{FINISHED}) {
  return END_ITERATOR();   return END_ITERATOR();
     }      }
Line 3034  sub next { Line 3591  sub next {
     # do so.      # do so.
     if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0}) {      if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0}) {
         $self->{HAVE_RETURNED_0} = 1;          $self->{HAVE_RETURNED_0} = 1;
    my $nextTopLevel = $self->{NAV_MAP}->getById('0.0');
         return $self->{NAV_MAP}->getById('0.0');          return $self->{NAV_MAP}->getById('0.0');
     }      }
     if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0_BEGIN_MAP}) {      if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0_BEGIN_MAP}) {
Line 3053  sub next { Line 3611  sub next {
         if ($self->{RECURSIVE_DEPTH} == 0) {          if ($self->{RECURSIVE_DEPTH} == 0) {
             $self->{RECURSIVE_ITERATOR_FLAG} = 0;              $self->{RECURSIVE_ITERATOR_FLAG} = 0;
         }          }
   
         return $next;          return $next;
     }      }
   
Line 3129  sub next { Line 3686  sub next {
     # So we need to look at all the resources we can get to from here,      # So we need to look at all the resources we can get to from here,
     # categorize them if we haven't seen them, remember if we have a new      # categorize them if we haven't seen them, remember if we have a new
     my $nextUnfiltered = $here->getNext();      my $nextUnfiltered = $here->getNext();
   
   
     my $maxDepthAdded = -1;      my $maxDepthAdded = -1;
           
     for (@$nextUnfiltered) {      for (@$nextUnfiltered) {
Line 3162  sub next { Line 3721  sub next {
         $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();
   
         $self->{RECURSIVE_ITERATOR} =           $self->{RECURSIVE_ITERATOR} = 
             Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,              Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
                                               $finishResource, $self->{FILTER},                                                $finishResource, $self->{FILTER},
Line 3174  sub next { Line 3732  sub next {
     # If this is a blank resource, don't actually return it.      # If this is a blank resource, don't actually return it.
     # Should you ever find you need it, make sure to add an option to the code      # Should you ever find you need it, make sure to add an option to the code
     #  that you can use; other things depend on this behavior.      #  that you can use; other things depend on this behavior.
     my $browsePriv = $self->{HERE}->browsePriv();      my $browsePriv = $self->{HERE}->browsePriv($noblockcheck);
     if (!$self->{HERE}->src() ||       if (!$self->{HERE}->src() || 
         (!($browsePriv eq 'F') && !($browsePriv eq '2')) ) {          (!($browsePriv eq 'F') && !($browsePriv eq '2')) ) {
         return $self->next($closeAllPages);          return $self->next($closeAllPages);
Line 3254  sub new { Line 3812  sub new {
     weaken($self->{NAV_MAP} = shift);      weaken($self->{NAV_MAP} = shift);
     return undef unless ($self->{NAV_MAP});      return undef unless ($self->{NAV_MAP});
   
       $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME};
       $self->{DOMAIN}   = $self->{NAV_MAP}->{DOMAIN};
   
     $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();      $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();
     $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();      $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();
   
Line 3490  sub new { Line 4051  sub new {
     weaken($self->{NAV_MAP} = shift);      weaken($self->{NAV_MAP} = shift);
     $self->{ID} = shift;      $self->{ID} = shift;
   
       $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME};
       $self->{DOMAIN}   = $self->{NAV_MAP}->{DOMAIN};
   
     # Store this new resource in the parent nav map's cache.      # Store this new resource in the parent nav map's cache.
     $self->{NAV_MAP}->{RESOURCE_CACHE}->{$self->{ID}} = $self;      $self->{NAV_MAP}->{RESOURCE_CACHE}->{$self->{ID}} = $self;
     $self->{RESOURCE_ERROR} = 0;      $self->{RESOURCE_ERROR} = 0;
Line 3500  sub new { Line 4064  sub new {
     # about this resource in. Not used by the resource object      # about this resource in. Not used by the resource object
     # directly.      # directly.
     $self->{DATA} = {};      $self->{DATA} = {};
          
     bless($self);      bless($self);
           
       # This is a speed optimization, to avoid calling symb() too often.
       $self->{SYMB} = $self->symb();
      
     return $self;      return $self;
 }  }
   
Line 3514  sub navHash { Line 4081  sub navHash {
     my $param = shift;      my $param = shift;
     my $id = shift;      my $id = shift;
     my $arg = $param . ($id?$self->{ID}:"");      my $arg = $param . ($id?$self->{ID}:"");
     if (defined($arg)) {      if (ref($self) && ref($self->{NAV_MAP}) && defined($arg)) {
         return $self->{NAV_MAP}->navhash($arg);          return $self->{NAV_MAP}->navhash($arg);
     }      }
     return;      return;
Line 3614  sub src { Line 4181  sub src {
 }  }
 sub shown_symb {  sub shown_symb {
     my $self=shift;      my $self=shift;
     if ($self->encrypted()) {return &Apache::lonenc::encrypted($self->symb());}      if ($self->encrypted()) {return &Apache::lonenc::encrypted($self->{SYMB});}
     return $self->symb();      return $self->{SYMB};
 }  }
 sub id {  sub id {
     my $self=shift;      my $self=shift;
Line 3628  sub enclosing_map_src { Line 4195  sub enclosing_map_src {
 }  }
 sub symb {  sub symb {
     my $self=shift;      my $self=shift;
       if (defined($self->{SYMB})) { return $self->{SYMB}; }
     (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;      (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;
     my $symbSrc = &Apache::lonnet::declutter($self->src());      my $symbSrc = &Apache::lonnet::declutter($self->src());
     my $symb = &Apache::lonnet::declutter($self->navHash('map_id_'.$first))       my $symb = &Apache::lonnet::declutter($self->navHash('map_id_'.$first)) 
Line 3636  sub symb { Line 4204  sub symb {
 }  }
 sub wrap_symb {  sub wrap_symb {
     my $self = shift;      my $self = shift;
     return $self->{NAV_MAP}->wrap_symb($self->symb());      return $self->{NAV_MAP}->wrap_symb($self->{SYMB});
 }  }
 sub title {   sub title { 
     my $self=shift;       my $self=shift; 
Line 3755  sub is_practice { Line 4323  sub is_practice {
 sub is_problem {  sub is_problem {
     my $self=shift;      my $self=shift;
     my $src = $self->src();      my $src = $self->src();
     if ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) {      if ($src =~ /$LONCAPA::assess_re/) {
  return !($self->is_practice());   return !($self->is_practice());
     }      }
     return 0;      return 0;
Line 3778  my %incomplete_hash = Line 4346  my %incomplete_hash =
 sub is_incomplete {  sub is_incomplete {
     my $self = shift;      my $self = shift;
     if ($self->is_problem()) {      if ($self->is_problem()) {
  &Apache::lonnet::logthis('is problem');  
  foreach my $part (@{$self->parts()}) {   foreach my $part (@{$self->parts()}) {
     &Apache::lonnet::logthis("$part status ".$self->status($part));  
     if (exists($incomplete_hash{$self->status($part)})) {      if (exists($incomplete_hash{$self->status($part)})) {
  return 1;   return 1;
     }      }
Line 3792  sub is_incomplete { Line 4358  sub is_incomplete {
 sub is_raw_problem {  sub is_raw_problem {
     my $self=shift;      my $self=shift;
     my $src = $self->src();      my $src = $self->src();
     if ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) {      if ($src =~ /$LONCAPA::assess_re/) {
         return 1;          return 1;
     }      }
     return 0;      return 0;
Line 3861  sub parmval { Line 4427  sub parmval {
     if (!defined($part)) {       if (!defined($part)) { 
         $part = '0';           $part = '0'; 
     }      }
     return $self->{NAV_MAP}->parmval($part.'.'.$what, $self->symb());      return $self->{NAV_MAP}->parmval($part.'.'.$what, $self->{SYMB});
 }  }
   
 =pod  =pod
Line 3894  resource of the map. Line 4460  resource of the map.
   
 Returns a string with the type of the map in it.  Returns a string with the type of the map in it.
   
 =item *B<map_hierarchy>:  =item * B<map_hierarchy>:
   
 Returns a string with a comma-separated ordered list of map_pc IDs  Returns a string with a comma-separated ordered list of map_pc IDs
 for the hierarchy of maps containing a map, with the top level  for the hierarchy of maps containing a map, with the top level
 map first, then descending to deeper levels, with the enclosing map last.  map first, then descending to deeper levels, with the enclosing map last.
   
   =item * B<map_breadcrumbs>:
   
   Same as map_hierarchy, except maps containing only a single itemm if
   it's a map, or containing no items are omitted, unless it's the top
   level map (map_pc = 1), which is always included.
   
 =back  =back
   
 =cut  =cut
Line 3935  sub map_hierarchy { Line 4507  sub map_hierarchy {
     my $pc = $self->map_pc();      my $pc = $self->map_pc();
     return $self->navHash("map_hierarchy_$pc", 0);      return $self->navHash("map_hierarchy_$pc", 0);
 }  }
   sub map_breadcrumbs {
       my $self = shift;
       my $pc = $self->map_pc();
       return $self->navHash("map_breadcrumbs_$pc", 0);
   }
   
 #####  #####
 # Property queries  # Property queries
Line 3966  their code.) Line 4542  their code.)
   
 =over 4  =over 4
   
 =item * B<acc>:  
   =item * B<printable>
   
   returns true if the current date is such that the 
   specified resource part is printable.
   
   
   =item * B<resprintable>
   
   Returns true if all parts in the resource are printable making the
   entire resource printable.
   
   =item * B<acc>
   
 Get the Client IP/Name Access Control information.  Get the Client IP/Name Access Control information.
   
Line 4019  Get the weight for the problem. Line 4607  Get the weight for the problem.
   
 =cut  =cut
   
   
   
   
   sub printable {
   
       my ($self, $part) = @_;
   
       #  The following cases apply:
       #  - If a start date is not set, it is replaced by the open date.
       #  - Ditto for start/open replaced by content open.
       #  - If neither start nor printdates are set the part is printable.
       #  - Start date set but no end date: Printable if now >= start date.
       #  - End date set but no start date: Printable if now <= end date.
       #  - both defined: printable if start <= now <= end
       #
   
       # Get the print open/close dates for the resource.
   
       my $start = $self->parmval("printstartdate", $part);
       my $end   = $self->parmval("printenddate", $part);
   
       if (!$start) {
    $start = $self->parmval("opendate", $part);
       }
       if (!$start) {
    $start = $self->parmval("contentopen", $part);
       }
   
   
       my $now  = time();
   
   
       my $startok = 1;
       my $endok   = 1;
   
       if ((defined $start) && ($start ne '')) {
    $startok = $start <= $now;
       }
       if ((defined $end) && ($end != '')) {
    $endok = $end >= $now;
       }
       return $startok && $endok;
   }
   
   sub resprintable {
       my $self = shift;
   
       # get parts...or realize there are no parts.
   
       my $partsref = $self->parts();
       my @parts    = @$partsref;
   
       if (!@parts) {
    return $self->printable(0);
       } else {
    foreach my $part  (@parts) {
       if (!$self->printable($part)) { 
    return 0; 
       }
    }
    return 1;
       }
   }
   
 sub acc {  sub acc {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     my $acc = $self->parmval("acc", $part);      my $acc = $self->parmval("acc", $part);
Line 4039  sub awarded { Line 4691  sub awarded {
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
     $self->{NAV_MAP}->get_user_data();      $self->{NAV_MAP}->get_user_data();
     if (!defined($part)) { $part = '0'; }      if (!defined($part)) { $part = '0'; }
     return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.awarded'};      return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.awarded'};
 }  }
 sub taskversion {  sub taskversion {
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
     $self->{NAV_MAP}->get_user_data();      $self->{NAV_MAP}->get_user_data();
     if (!defined($part)) { $part = '0'; }      if (!defined($part)) { $part = '0'; }
     return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.version'};      return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.version'};
 }  }
 sub taskstatus {  sub taskstatus {
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
     $self->{NAV_MAP}->get_user_data();      $self->{NAV_MAP}->get_user_data();
     if (!defined($part)) { $part = '0'; }      if (!defined($part)) { $part = '0'; }
     return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$self->taskversion($part).'.'.$part.'.status'};      return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$self->taskversion($part).'.'.$part.'.status'};
 }  }
 sub solved {  sub solved {
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
     $self->{NAV_MAP}->get_user_data();      $self->{NAV_MAP}->get_user_data();
     if (!defined($part)) { $part = '0'; }      if (!defined($part)) { $part = '0'; }
     return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.solved'};      return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.solved'};
 }  }
 sub checkedin {  sub checkedin {
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
Line 4065  sub checkedin { Line 4717  sub checkedin {
     if (!defined($part)) { $part = '0'; }      if (!defined($part)) { $part = '0'; }
     if ($self->is_task()) {      if ($self->is_task()) {
         my $version = $self->taskversion($part);          my $version = $self->taskversion($part);
         return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$version .'.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$version .'.'.$part.'.checkedin.slot'});          return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$version .'.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$version .'.'.$part.'.checkedin.slot'});
     } else {      } else {
         return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.checkedin.slot'});          return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.checkedin.slot'});
     }      }
 }  }
 # this should work exactly like the copy in lonhomework.pm  # this should work exactly like the copy in lonhomework.pm
   # Why is there a copy in lonhomework?  Why not centralized?
   #
   #  TODO: Centralize duedate.
   #
   
 sub duedate {  sub duedate {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     if (defined ($self->{DUEDATE_CACHE}->{$part})) {      if (defined ($self->{DUEDATE_CACHE}->{$part})) {
Line 4079  sub duedate { Line 4736  sub duedate {
     my $date;      my $date;
     my @interval=$self->parmval("interval", $part);      my @interval=$self->parmval("interval", $part);
     my $due_date=$self->parmval("duedate", $part);      my $due_date=$self->parmval("duedate", $part);
     if ($interval[0] =~ /\d+/) {      if ($interval[0] =~ /^(\d+)/) {
        my $first_access=&Apache::lonnet::get_first_access($interval[1],          my $timelimit = $1;
                                                           $self->symb);          my $first_access=&Apache::lonnet::get_first_access($interval[1],
                                                             $self->{SYMB});
  if (defined($first_access)) {   if (defined($first_access)) {
            my $interval = $first_access+$interval[0];              my $interval = $first_access+$timelimit;
     $date = (!$due_date || $interval < $due_date) ? $interval       $date = (!$due_date || $interval < $due_date) ? $interval 
                                                           : $due_date;                                                            : $due_date;
  } else {   } else {
Line 4154  sub weight { Line 4812  sub weight {
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
     if (!defined($part)) { $part = '0'; }      if (!defined($part)) { $part = '0'; }
     my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight',      my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight',
                                 $self->symb(), $env{'user.domain'},                                  $self->{SYMB}, $self->{DOMAIN},
                                 $env{'user.name'},                                  $self->{USERNAME},
                                 $env{'request.course.sec'});                                  $env{'request.course.sec'});
     return $weight;      return $weight;
 }  }
Line 4163  sub part_display { Line 4821  sub part_display {
     my $self= shift(); my $partID = shift();      my $self= shift(); my $partID = shift();
     if (! defined($partID)) { $partID = '0'; }      if (! defined($partID)) { $partID = '0'; }
     my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',      my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',
                                      $self->symb);                                       $self->{SYMB});
     if (! defined($display) || $display eq '') {      if (! defined($display) || $display eq '') {
         $display = $partID;          $display = $partID;
     }      }
Line 4183  sub getReturnHash { Line 4841  sub getReturnHash {
     my $self = shift;      my $self = shift;
           
     if (!defined($self->{RETURN_HASH})) {      if (!defined($self->{RETURN_HASH})) {
         my %tmpHash  = &Apache::lonnet::restore($self->symb());          my %tmpHash  = &Apache::lonnet::restore($self->{SYMB},undef,$self->{DOMAIN},$self->{USERNAME});
         $self->{RETURN_HASH} = \%tmpHash;          $self->{RETURN_HASH} = \%tmpHash;
     }      }
 }         }       
Line 4248  and use the link as appropriate. Line 4906  and use the link as appropriate.
   
 sub hasDiscussion {  sub hasDiscussion {
     my $self = shift;      my $self = shift;
     return $self->{NAV_MAP}->hasDiscussion($self->symb());      return $self->{NAV_MAP}->hasDiscussion($self->{SYMB});
 }  }
   
 sub last_post_time {  sub last_post_time {
     my $self = shift;      my $self = shift;
     return $self->{NAV_MAP}->last_post_time($self->symb());      return $self->{NAV_MAP}->last_post_time($self->{SYMB});
 }  }
   
 sub discussion_info {  sub discussion_info {
     my ($self,$filter) = @_;      my ($self,$filter) = @_;
     return $self->{NAV_MAP}->discussion_info($self->symb(),$filter);      return $self->{NAV_MAP}->discussion_info($self->{SYMB},$filter);
 }  }
   
 sub getFeedback {  sub getFeedback {
     my $self = shift;      my $self = shift;
     my $source = $self->src();      my $source = $self->src();
     my $symb = $self->symb();      my $symb = $self->{SYMB};
     if ($source =~ /^\/res\//) { $source = substr $source, 5; }      if ($source =~ /^\/res\//) { $source = substr $source, 5; }
     return $self->{NAV_MAP}->getFeedback($symb,$source);      return $self->{NAV_MAP}->getFeedback($symb,$source);
 }  }
Line 4272  sub getFeedback { Line 4930  sub getFeedback {
 sub getErrors {  sub getErrors {
     my $self = shift;      my $self = shift;
     my $source = $self->src();      my $source = $self->src();
     my $symb = $self->symb();      my $symb = $self->{SYMB};
     if ($source =~ /^\/res\//) { $source = substr $source, 5; }      if ($source =~ /^\/res\//) { $source = substr $source, 5; }
     return $self->{NAV_MAP}->getErrors($symb,$source);      return $self->{NAV_MAP}->getErrors($symb,$source);
 }  }
Line 4422  sub extractParts { Line 5080  sub extractParts {
  if ($partorder) {   if ($partorder) {
     my @parts;      my @parts;
     for my $part (split (/,/,$partorder)) {      for my $part (split (/,/,$partorder)) {
  if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) {   if (!Apache::loncommon::check_if_partid_hidden($part, $self->{SYMB})) {
     push @parts, $part;      push @parts, $part;
     $parts{$part} = 1;      $parts{$part} = 1;
  }   }
Line 4440  sub extractParts { Line 5098  sub extractParts {
     my $part = $1;      my $part = $1;
     # This floods the logs if it blows up      # This floods the logs if it blows up
     if (defined($parts{$part})) {      if (defined($parts{$part})) {
  &Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->symb());   &Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->{SYMB});
     }      }
           
     # check to see if part is turned off.      # check to see if part is turned off.
           
     if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) {      if (!Apache::loncommon::check_if_partid_hidden($part, $self->{SYMB})) {
  $parts{$part} = 1;   $parts{$part} = 1;
     }      }
  }   }
     }      }
     my @sortedParts = sort keys %parts;      my @sortedParts = sort(keys(%parts));
     $self->{PARTS} = \@sortedParts;      $self->{PARTS} = \@sortedParts;
         }          }
                   
Line 4471  sub extractParts { Line 5129  sub extractParts {
         # So we have to use our knowlege of part names to figure out           # So we have to use our knowlege of part names to figure out 
         # where the part names begin and end, and even then, it is possible          # where the part names begin and end, and even then, it is possible
         # to construct ambiguous situations.          # to construct ambiguous situations.
         foreach my $data (split /,/, $metadata) {          foreach my $data (split(/,/, $metadata)) {
             if ($data =~ /^([a-zA-Z]+)response_(.*)/              if ($data =~ /^([a-zA-Z]+)response_(.*)/
  || $data =~ /^(Task)_(.*)/) {   || $data =~ /^(Task)_(.*)/) {
                 my $responseType = $1;                  my $responseType = $1;
                 my $partStuff = $2;                  my $partStuff = $2;
                 my $partIdSoFar = '';                  my $partIdSoFar = '';
                 my @partChunks = split /_/, $partStuff;                  my @partChunks = split(/_/, $partStuff);
                 my $i = 0;                  my $i = 0;
                 for ($i = 0; $i < scalar(@partChunks); $i++) {                  for ($i = 0; $i < scalar(@partChunks); $i++) {
                     if ($partIdSoFar) { $partIdSoFar .= '_'; }                      if ($partIdSoFar) { $partIdSoFar .= '_'; }
Line 4944  sub status { Line 5602  sub status {
     }      }
   
     # Otherwise, it's untried and open      # Otherwise, it's untried and open
     return OPEN;       return OPEN;
 }  }
   
 sub check_for_slot {  sub check_for_slot {
     my $self = shift;      my $self = shift;
     my $part = shift;      my $part = shift;
       my $symb = $self->{SYMB};
     my ($use_slots,$available,$availablestudent) = $self->slot_control($part);      my ($use_slots,$available,$availablestudent) = $self->slot_control($part);
     if (($use_slots ne '') && ($use_slots !~ /^\s*no\s*$/i)) {      if (($use_slots ne '') && ($use_slots !~ /^\s*no\s*$/i)) {
         my @slots = (split(/:/,$availablestudent),split(/:/,$available));          my @slots = (split(/:/,$availablestudent),split(/:/,$available));
Line 4957  sub check_for_slot { Line 5616  sub check_for_slot {
         my $cdom=$env{'course.'.$cid.'.domain'};          my $cdom=$env{'course.'.$cid.'.domain'};
         my $cnum=$env{'course.'.$cid.'.num'};          my $cnum=$env{'course.'.$cid.'.num'};
         my $now = time;          my $now = time;
           my $num_usable_slots = 0;
           my ($checkedin,$checkedinslot,%consumed_uniq,%slots);
         if (@slots > 0) {          if (@slots > 0) {
             my %slots=&Apache::lonnet::get('slots',[@slots],$cdom,$cnum);              %slots=&Apache::lonnet::get('slots',[@slots],$cdom,$cnum);
             if (&Apache::lonnet::error(%slots)) {              if (&Apache::lonnet::error(%slots)) {
                 return (UNKNOWN);                  return (UNKNOWN);
             }              }
             my @sorted_slots = &Apache::loncommon::sorted_slots(\@slots,\%slots);              my @sorted_slots = &Apache::loncommon::sorted_slots(\@slots,\%slots,'starttime');
             my ($checkedin,$checkedinslot);  
             foreach my $slot_name (@sorted_slots) {              foreach my $slot_name (@sorted_slots) {
                 next if (!defined($slots{$slot_name}) ||                  next if (!defined($slots{$slot_name}) || !ref($slots{$slot_name}));
                          !ref($slots{$slot_name}));  
                 my $end = $slots{$slot_name}->{'endtime'};                  my $end = $slots{$slot_name}->{'endtime'};
                 my $start = $slots{$slot_name}->{'starttime'};                  my $start = $slots{$slot_name}->{'starttime'};
                 my $ip = $slots{$slot_name}->{'ip'};                  my $ip = $slots{$slot_name}->{'ip'};
                 if ($self->simpleStatus() == OPEN) {                  if ($self->simpleStatus() == OPEN) {
                     my $startreserve = $slots{$slot_name}->{'startreserve'};  
                     my @proctors;  
                     if ($slots{$slot_name}->{'proctor'} ne '') {  
                         @proctors = split(',',$slots{$slot_name}->{'proctor'});  
                     }  
                     if ($end > $now) {                      if ($end > $now) {
                         ($checkedin,$checkedinslot) = $self->checkedin();                          if ($start > $now) {
                         if ($startreserve < $now) {                              return (RESERVED_LATER,$start,$slot_name);
                             if ($start > $now) {                          } else {
                                 return (RESERVED_LATER,$start,$slot_name);                              if ($ip ne '') {
                             } else {                                  if (!&Apache::loncommon::check_ip_acc($ip)) {
                                 if ($ip ne '') {                                      return (RESERVED_LOCATION,$end,$slot_name);
                                     if (!&Apache::loncommon::check_ip_acc($ip)) {  
                                         return (RESERVED_LOCATION,$ip,$slot_name);  
                                     }  
                                 }   
                                 if (@proctors > 0) {  
                                     unless ((grep(/^\Q$checkedin\E/,@proctors)) &&  
                                         ($checkedinslot eq $slot_name)) {  
                                         return (NEEDS_CHECKIN,undef,$slot_name);   
                                     }  
                                 }                                  }
                                 return (RESERVED,$end,$slot_name);  
                             }                              }
                         } else {                              my @proctors;
                             if ($start > $now) {                              if ($slots{$slot_name}->{'proctor'} ne '') {
                                 return (RESERVABLE,$startreserve,$slot_name);                                  @proctors = split(',',$slots{$slot_name}->{'proctor'});
                             }                              }
                               if (@proctors > 0) {
                                   ($checkedin,$checkedinslot) = $self->checkedin();
                                   unless ((grep(/^\Q$checkedin\E/,@proctors)) &&
                                           ($checkedinslot eq $slot_name)) {
                                       return (NEEDS_CHECKIN,undef,$slot_name); 
                                   }
                               }
                               return (RESERVED,$end,$slot_name);
                         }                          }
                     }                      }
                   } elsif ($end > $now) {
                       $num_usable_slots ++;
                 }                  }
             }              }
             my ($is_correct,$got_grade);              my ($is_correct,$wait_for_grade);
             if ($self->is_task()) {              if ($self->is_task()) {
                 my $taskstatus = $self->taskstatus();                  my $taskstatus = $self->taskstatus();
                 $is_correct = (($taskstatus eq 'pass') ||                   $is_correct = (($taskstatus eq 'pass') || 
                                ($self->solved() =~ /^correct_/));                                 ($self->solved() =~ /^correct_/));
                 $got_grade = ($taskstatus =~ /^(?:pass|fail)$/);                  unless ($taskstatus =~ /^(?:pass|fail)$/) {
                       $wait_for_grade = 1;
                   }
             } else {              } else {
                 $got_grade = 1;                  unless ($self->completable()) {
                 $is_correct = ($self->solved() =~ /^correct_/);                         $wait_for_grade = 1;
                   }
                   unless (($self->problemstatus($part) eq 'no') ||
                           ($self->problemstatus($part) eq 'no_feedback_ever')) {
                       $is_correct = ($self->solved($part) =~ /^correct_/);
                       $wait_for_grade = 0;
                   }
             }              }
             ($checkedin,$checkedinslot) = $self->checkedin();              ($checkedin,$checkedinslot) = $self->checkedin();
             if ($checkedin) {              if ($checkedin) {
                 if (!$got_grade) {                  if (ref($slots{$checkedinslot}) eq 'HASH') {
                       $consumed_uniq{$checkedinslot} = $slots{$checkedinslot}{'uniqueperiod'};
                   }
                   if ($wait_for_grade) {
                     return (WAITING_FOR_GRADE);                      return (WAITING_FOR_GRADE);
                 } elsif ($is_correct) {                  } elsif ($is_correct) {
                     return (CORRECT);                       return (CORRECT); 
                 }                  }
             }              }
             return(NOT_IN_A_SLOT);              if ($num_usable_slots) {
         } else {  
             if (!$future_slots_checked) {  
                 $future_slots = &get_future_slots($cdom,$cnum,$now);  
                 $future_slots_checked = 1;  
             }  
             if ($future_slots) {  
                 return(NOT_IN_A_SLOT);                  return(NOT_IN_A_SLOT);
             }              }
             return(NOTRESERVABLE);  
         }          }
     }          my $reservable = &Apache::lonnet::get_reservable_slots($cnum,$cdom,$env{'user.name'},
     return;                                                                 $env{'user.domain'});
 }          if (ref($reservable) eq 'HASH') {
               if ((ref($reservable->{'now_order'}) eq 'ARRAY') && (ref($reservable->{'now'}) eq 'HASH')) {
 sub get_future_slots {                  foreach my $slot (reverse (@{$reservable->{'now_order'}})) {
     my ($cdom,$cnum,$now) = @_;                      my $canuse;
     my %slots=&Apache::lonnet::dump('slots',$cdom,$cnum);                      if (($reservable->{'now'}{$slot}{'symb'} eq '') ||
     my $future_slots = 0;                          ($reservable->{'now'}{$slot}{'symb'} eq $symb)) {
     foreach my $slot (keys(%slots)) {                          $canuse = 1;
         if (($slots{$slot}->{'starttime'} > $now) &&                      }
             ($slots{$slot}->{'endtime'} > $now)) {                      if ($canuse) {
             $future_slots ++;                          if ($checkedin) {
                               if (ref($consumed_uniq{$checkedinslot}) eq 'ARRAY') {
                                   my ($uniqstart,$uniqend)=@{$consumed_uniq{$checkedinslot}};
                                   if ($reservable->{'now'}{$slot}{'uniqueperiod'} =~ /^(\d+),(\d+)$/) {
                                       my ($new_uniq_start,$new_uniq_end) = ($1,$2);
                                       next if (!
                                           ($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) ||
                                           ($uniqstart > $new_uniq_end   &&  $uniqend > $new_uniq_end  ));
                                   }
                               }
                           }
                           return(RESERVABLE,$reservable->{'now'}{$slot}{'endreserve'});
                       }
                   }
               }
               if ((ref($reservable->{'future_order'}) eq 'ARRAY') && (ref($reservable->{'future'}) eq 'HASH')) {
                   foreach my $slot (@{$reservable->{'future_order'}}) {
                       my $canuse;
                       if (($reservable->{'future'}{$slot}{'symb'} eq '') ||
                           ($reservable->{'future'}{$slot}{'symb'} eq $symb)) {
                           $canuse = 1;
                       }
                       if ($canuse) {
                           if ($checkedin) {
                               if (ref($consumed_uniq{$checkedinslot}) eq 'ARRAY') {
                                   my ($uniqstart,$uniqend)=@{$consumed_uniq{$checkedinslot}};
                                   if ($reservable->{'future'}{$slot}{'uniqueperiod'} =~ /^(\d+),(\d+)$/) {
                                       my ($new_uniq_start,$new_uniq_end) = ($1,$2);
                                       next if (!
                                           ($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) ||
                                           ($uniqstart > $new_uniq_end   &&  $uniqend > $new_uniq_end  ));
                                   }
                               }
                           }
                           return(RESERVABLE_LATER,$reservable->{'future'}{$slot}{'startreserve'});
                       }
                   }
               }
         }          }
           return(NOTRESERVABLE);
     }      }
     return $future_slots;      return;
 }  }
   
 sub CLOSED { return 23; }  sub CLOSED { return 23; }
Line 5172  sub completable { Line 5868  sub completable {
         # "If any of the parts are open, or have tries left (implies open),          # "If any of the parts are open, or have tries left (implies open),
         # and it is not "attempted" (manually graded problem), it is          # and it is not "attempted" (manually graded problem), it is
         # not "complete"          # not "complete"
         if ($self->getCompletionStatus($part) == ATTEMPTED() ||   if ($self->getCompletionStatus($part) == ATTEMPTED() ||
             $self->getCompletionStatus($part) == CREDIT_ATTEMPTED() ||              $self->getCompletionStatus($part) == CREDIT_ATTEMPTED() ||
             $status == ANSWER_SUBMITTED() ) {      $status == ANSWER_SUBMITTED() ) {
     # did this part already, as well as we can      # did this part already, as well as we can
     next;      next;
  }   }
Line 5189  sub completable { Line 5885  sub completable {
   
 =pod  =pod
   
   B<Answerable>
   
   The answerable method differs from the completable method in its handling of problem parts
   for which feedback on correctness is suppressed, but the student still has tries left, and
   the problem part is not past due, (i.e., the student could submit a different answer if
   he/she so chose). For that case completable will return 0, whereas answerable will return 1.
   
   =cut
   
   sub answerable {
       my $self = shift;
       if (!$self->is_problem()) { return 0; }
       my $partCount = $self->countParts();
       foreach my $part (@{$self->parts()}) {
           if ($part eq '0' && $partCount != 1) { next; }
           my $status = $self->status($part);
           if ($self->getCompletionStatus($part) == ATTEMPTED() ||
               $self->getCompletionStatus($part) == CREDIT_ATTEMPTED() ||
               $status == ANSWER_SUBMITTED() ) {
               if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) {
                   return 1;
               }
           }
           if ($status == OPEN() || $status == TRIES_LEFT() || $status == NETWORK_FAILURE()) {
               return 1;
           }
       }
       # None of the parts were answerable, so neither is this problem.
       return 0;
   }
   
   =pod
   
 =head2 Resource/Nav Map Navigation  =head2 Resource/Nav Map Navigation
   
 =over 4  =over 4
Line 5224  sub getPrevious { Line 5953  sub getPrevious {
     my $self = shift;      my $self = shift;
     my @branches;      my @branches;
     my $from = $self->from();      my $from = $self->from();
     foreach my $branch ( split /,/, $from) {      foreach my $branch ( split(/,/, $from)) {
         my $choice = $self->{NAV_MAP}->getById($branch);          my $choice = $self->{NAV_MAP}->getById($branch);
         my $prev = $choice->comesfrom();          my $prev = $choice->comesfrom();
         $prev = $self->{NAV_MAP}->getById($prev);          $prev = $self->{NAV_MAP}->getById($prev);
Line 5236  sub getPrevious { Line 5965  sub getPrevious {
   
 sub browsePriv {  sub browsePriv {
     my $self = shift;      my $self = shift;
       my $noblockcheck = shift;
     if (defined($self->{BROWSE_PRIV})) {      if (defined($self->{BROWSE_PRIV})) {
         return $self->{BROWSE_PRIV};          return $self->{BROWSE_PRIV};
     }      }
   
     $self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre',$self->src(),      $self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre',$self->src(),
     $self->symb());      $self->{SYMB},undef,
                                                       undef,$noblockcheck);
 }  }
   
 =pod  =pod

Removed from v.1.444.2.4.2.1  
changed lines
  Added in v.1.509.2.5.4.2


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