Diff for /loncom/interface/lonnavmaps.pm between versions 1.372 and 1.395

version 1.372, 2006/03/17 21:33:17 version 1.395, 2006/12/24 22:13:19
Line 31  package Apache::lonnavmaps; Line 31  package Apache::lonnavmaps;
   
 use strict;  use strict;
 use GDBM_File;  use GDBM_File;
 use Apache::Constants qw(:common :http);  
 use Apache::loncommon();  use Apache::loncommon();
 use Apache::lonmenu();  
 use Apache::lonenc();  use Apache::lonenc();
 use Apache::lonlocal;  use Apache::lonlocal;
 use Apache::lonnet;  use Apache::lonnet;
 use POSIX qw (floor strftime);  use POSIX qw (floor strftime);
 use Data::Dumper; # for debugging, not always   use Data::Dumper; # for debugging, not always 
 use Time::HiRes qw( gettimeofday tv_interval );  use Time::HiRes qw( gettimeofday tv_interval );
   use lib '/home/httpd/lib/perl/';
   use LONCAPA;
   
 # symbolic constants  # symbolic constants
 sub SYMB { return 1; }  sub SYMB { return 1; }
Line 89  my %colormap = Line 89  my %colormap =
 # is not yet done and due in less then 24 hours  # is not yet done and due in less then 24 hours
 my $hurryUpColor = "#FF0000";  my $hurryUpColor = "#FF0000";
   
 sub launch_win {  
     my ($mode,$script,$toplinkitems,$firsttime)=@_;  
     my $result;  
     if ($script ne 'no') {  
  $result.='<script type="text/javascript">';  
     }  
     if ($firsttime) {  
  $result.='function launch_navmapwin() {  
                  newWindow=open(\'/adm/navmaps?launchExternalRoles\',\'loncapanav\',\'width=400,height=600,scrollbars=1\');  
                }';  
     } else {  
  $result.='function launch_navmapwin() {  
                  newWindow=open(\'/adm/navmaps?launchExternal\',\'loncapanav\',\'width=400,height=600,scrollbars=1\');  
                }';  
     }  
     if ($mode eq 'now') {  
  $result.="\nlaunch_navmapwin();\n";  
     }  
     if ($script ne 'no') {  
  $result.='</script>';  
     }  
     if ($mode eq 'link') {  
  &add_linkitem($toplinkitems,'launchnav','launch_navmapwin()',  
       "Launch navigation window");  
     }  
     return $result;  
 }  
   
 sub close {  sub close {
     if ($env{'environment.remotenavmap'} ne 'on') { return ''; }      if ($env{'environment.remotenavmap'} ne 'on') { return ''; }
     return(<<ENDCLOSE);      return(<<ENDCLOSE);
Line 145  this.document.navform.submit(); Line 117  this.document.navform.submit();
 ENDUPDATE  ENDUPDATE
 }  }
   
 sub handler {  
     my $r = shift;  
     real_handler($r);  
 }  
   
 sub real_handler {  
     my $r = shift;  
     #my $t0=[&gettimeofday()];  
     # Handle header-only request  
     if ($r->header_only) {  
         if ($env{'browser.mathml'}) {  
             &Apache::loncommon::content_type($r,'text/xml');  
         } else {  
             &Apache::loncommon::content_type($r,'text/html');  
         }  
         $r->send_http_header;  
         return OK;  
     }  
   
     # Send header, don't cache this page  
     if ($env{'browser.mathml'}) {  
         &Apache::loncommon::content_type($r,'text/xml');  
     } else {  
         &Apache::loncommon::content_type($r,'text/html');  
     }  
     &Apache::loncommon::no_cache($r);  
   
     my %toplinkitems=();  
     &add_linkitem(\%toplinkitems,'blank','',"Select Action");  
     if ($ENV{QUERY_STRING} eq 'collapseExternal') {  
  &Apache::lonnet::put('environment',{'remotenavmap' => 'off'});  
  &Apache::lonnet::appenv('environment.remotenavmap' => 'off');  
  my $menu=&Apache::lonmenu::reopenmenu();  
  my $navstatus=&Apache::lonmenu::get_nav_status();  
  if ($menu) {  
     $menu=(<<MENU)  
              swmenu=$menu  
              swmenu.clearTimeout(swmenu.menucltim);  
      $navstatus  
 MENU  
         } else {  
     my $nothing = &Apache::lonhtmlcommon::javascript_nothing();  
     my $mainwindow='window.open('.$nothing.',"loncapaclient","",false);';  
     $menu=(<<MENU)  
              swmenu=$mainwindow  
      $navstatus  
 MENU  
  }  
         $r->send_http_header;  
  my $js =<<"ENDSUBM";  
   <script type="text/javascript">  
      function submitthis() {  
     $menu  
     self.close();  
     }  
   
    </script>  
 ENDSUBM  
         $r->print(&Apache::lonxml::xmlbegin().  
   &Apache::loncommon::head(undef,$js).  
   '<body bgcolor="#FFFFFF" onLoad="submitthis()">'.  
   &Apache::loncommon::end_page(undef,$js));  
   
         return OK;  
     }  
     if ($ENV{QUERY_STRING} =~ /^launchExternal/) {  
  &Apache::lonnet::put('environment',{'remotenavmap' => 'on'});  
  &Apache::lonnet::appenv('environment.remotenavmap' => 'on');  
   my $menu=&Apache::lonmenu::reopenmenu();  
  my $navstatus=&Apache::lonmenu::get_nav_status();  
  if ($menu) {  
     $r->print(<<MENU);  
              <script type="text/javascript">  
              swmenu=$menu  
              swmenu.clearTimeout(swmenu.menucltim);  
      $navstatus  
              </script>  
 MENU  
         }  
    }  
     if ($ENV{QUERY_STRING} eq 'turningOffExternal') {  
  $env{'environment.remotenavmap'}='off';  
     }  
   
     # Create the nav map  
     my $navmap = Apache::lonnavmaps::navmap->new();  
   
     if (!defined($navmap)) {  
         my $requrl = $r->uri;  
         $env{'user.error.msg'} = "$requrl:bre:0:0:Course not initialized";  
         return HTTP_NOT_ACCEPTABLE;  
     }  
     $r->send_http_header;  
   
 # ------------------------------------------------------------ Get query string  
     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register','sort','showOnlyHomework','postsymb']);  
       
 # ----------------------------------------------------- Force menu registration  
     my $addentries='';  
     my $more_unload;  
     my $body_only='';  
     my $js;  
     if ($env{'environment.remotenavmap'} eq 'on') {  
  $js='<script type="text/javascript">  
                 function collapse() {  
                    this.document.location="/adm/navmaps?collapseExternal";  
                 }  
              </script>';  
 # FIXME need to be smarter to only catch window close events  
 # $more_unload="collapse()"  
  $body_only=1;  
     }  
     if ($env{'form.register'}) {  
  $addentries=' onLoad="'.&Apache::lonmenu::loadevents().  
     '" onUnload="'.&Apache::lonmenu::unloadevents().';'.  
     $more_unload.'"';  
  $r->print(&Apache::lonmenu::registerurl(1));  
     } else {  
  $addentries=' onUnload="'.$more_unload.'"';  
     }  
   
     # Header  
     $r->print(&Apache::lonxml::xmlbegin().  
       &Apache::loncommon::head('Navigate Course Contents',$js).  
               &Apache::loncommon::bodytag('Navigate Course Contents','',  
   $addentries,$body_only,'',  
   $env{'form.register'}));  
     $r->print('<script>window.focus();</script>');  
        
     $r->rflush();  
   
     # Check that it's defined  
     if (!($navmap->courseMapDefined())) {  
  $r->print(&Apache::loncommon::help_open_menu('','Navigation Screen','Navigation_Screen','',undef,'RAT'));  
         $r->print('<font size="+2" color="red">Coursemap undefined.</font>' .  
                   &Apache::loncommon::end_page());  
         return OK;  
     }  
   
     # See if there's only one map in the top-level, if we don't  
     # already have a filter... if so, automatically display it  
     # (older code; should use retrieveResources)  
     if ($ENV{QUERY_STRING} !~ /filter/) {  
         my $iterator = $navmap->getIterator(undef, undef, undef, 0);  
         my $curRes;  
         my $sequenceCount = 0;  
         my $sequenceId;  
         while ($curRes = $iterator->next()) {  
             if (ref($curRes) && $curRes->is_sequence()) {  
                 $sequenceCount++;  
                 $sequenceId = $curRes->map_pc();  
             }  
         }  
           
         if ($sequenceCount == 1) {  
             # The automatic iterator creation in the render call   
             # will pick this up. We know the condition because  
             # the defined($env{'form.filter'}) also ensures this  
             # is a fresh call.  
             $env{'form.filter'} = "$sequenceId";  
         }  
     }  
   
     if ($ENV{QUERY_STRING} eq 'launchExternal') {  
  $r->print('  
           <form name="returnwin" action="/adm/flip?postdata=navlaunch%3a"   
                 method="post" target="loncapaclient">  
           </form>');  
  $r->print('  
           <script type="text/javascript">  
               this.document.returnwin.submit();  
           </script>');  
     }  
   
     if ($env{'environment.remotenavmap'} ne 'on') {  
  $r->print(&launch_win('link','yes',\%toplinkitems));  
     }   
     if ($env{'environment.remotenavmap'} eq 'on') {  
  &add_linkitem(\%toplinkitems,'closenav','collapse()',  
       "Close navigation window");  
     }   
   
     my $jumpToFirstHomework = 0;  
     # Check to see if the student is jumping to next open, do-able problem  
     if ($ENV{QUERY_STRING} =~ /^jumpToFirstHomework/) {  
         $jumpToFirstHomework = 1;  
         # Find the next homework problem that they can do.  
         my $iterator = $navmap->getIterator(undef, undef, undef, 1);  
         my $curRes;  
         my $foundDoableProblem = 0;  
         my $problemRes;  
           
         while (($curRes = $iterator->next()) && !$foundDoableProblem) {  
             if (ref($curRes) && $curRes->is_problem()) {  
                 my $status = $curRes->status();  
                 if ($curRes->completable()) {  
                     $problemRes = $curRes;  
                     $foundDoableProblem = 1;  
   
                     # Pop open all previous maps  
                     my $stack = $iterator->getStack();  
                     pop @$stack; # last resource in the stack is the problem  
                                  # itself, which we don't need in the map stack  
                     my @mapPcs = map {$_->map_pc()} @$stack;  
                     $env{'form.filter'} = join(',', @mapPcs);  
   
                     # Mark as both "here" and "jump"  
                     $env{'form.postsymb'} = $curRes->symb();  
                 }  
             }  
         }  
   
         # If we found no problems, print a note to that effect.  
         if (!$foundDoableProblem) {  
             $r->print("<font size='+2'>All homework assignments have been completed.</font><br /><br />");  
         }  
     } else {  
  &add_linkitem(\%toplinkitems,'firsthomework',  
       'location.href="navmaps?jumpToFirstHomework"',  
       "Show Me My First Homework Problem");  
     }  
   
     my $suppressEmptySequences = 0;  
     my $filterFunc = undef;  
     my $resource_no_folder_link = 0;  
   
     # Display only due homework.  
     my $showOnlyHomework = 0;  
     if ($env{'form.showOnlyHomework'} eq "1") {  
         $showOnlyHomework = 1;  
         $suppressEmptySequences = 1;  
         $filterFunc = sub { my $res = shift;   
                             return $res->completable() || $res->is_map();  
                         };  
  &add_linkitem(\%toplinkitems,'everything',  
      'location.href="navmaps?sort='.$env{'form.sort'}.'"',  
       "Show Everything");  
         $r->print("<p><font size='+2'>".&mt("Uncompleted Homework")."</font></p>");  
         $env{'form.filter'} = '';  
         $env{'form.condition'} = 1;  
  $resource_no_folder_link = 1;  
     } else {  
  &add_linkitem(\%toplinkitems,'uncompleted',  
       'location.href="navmaps?sort='.$env{'form.sort'}.  
           '&showOnlyHomework=1"',  
       "Show Only Uncompleted Homework");  
     }  
   
     my %selected=($env{'form.sort'} => 'selected=on');  
     my $sort_html=("<form>  
                  <nobr>  
                     <input type=\"hidden\" name=\"showOnlyHomework\" value=\"".$env{'form.showOnlyHomework'}."\" />  
                     <input type=\"submit\" value=\"".&mt('Sort by:')."\" />  
                     <select name=\"sort\">  
                        <option value=\"default\" $selected{'default'}>".&mt('Default')."</option>  
                        <option value=\"title\"   $selected{'title'}  >".&mt('Title')."</option>  
                        <option value=\"duedate\" $selected{'duedate'}>".&mt('Duedate')."</option>  
                        <option value=\"discussion\" $selected{'discussion'}>".&mt('Has New Discussion')."</option>  
                     </select>  
                  </nobr>  
                </form>");  
     # renderer call  
     my $renderArgs = { 'cols' => [0,1,2,3],  
        'sort' => $env{'form.sort'},  
                        'url' => '/adm/navmaps',  
                        'navmap' => $navmap,  
                        'suppressNavmap' => 1,  
                        'suppressEmptySequences' => $suppressEmptySequences,  
                        'filterFunc' => $filterFunc,  
        'resource_no_folder_link' => $resource_no_folder_link,  
        'sort_html'=> $sort_html,  
                        'r' => $r,  
                        'caller' => 'navmapsdisplay',  
                        'linkitems' => \%toplinkitems};  
     my $render = render($renderArgs);  
   
     # If no resources were printed, print a reassuring message so the  
     # user knows there was no error.  
     if ($renderArgs->{'counter'} == 0) {  
         if ($showOnlyHomework) {  
             $r->print("<p><font size='+1'>".&mt("All homework is currently completed").".</font></p>");  
         } else { # both jumpToFirstHomework and normal use the same: course must be empty  
             $r->print("<p><font size='+1'>This course is empty.</font></p>");  
         }  
     }  
     #my $td=&tv_interval($t0);  
     #$r->print("<br />$td");  
   
     $r->print(&Apache::loncommon::end_page());  
     $r->rflush();  
   
     return OK;  
 }  
   
 # Convenience functions: Returns a string that adds or subtracts  # Convenience functions: Returns a string that adds or subtracts
 # the second argument from the first hash, appropriate for the   # the second argument from the first hash, appropriate for the 
 # query string that determines which folders to recurse on  # query string that determines which folders to recurse on
Line 471  sub getLinkForResource { Line 149  sub getLinkForResource {
         if (defined($res)) {          if (defined($res)) {
     my $anchor;      my $anchor;
     if ($res->is_page()) {      if ($res->is_page()) {
  foreach (@$stack) { if (defined($_)) { $anchor = $_; }  }   foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; }  }
  $anchor=&Apache::lonnet::escape($anchor->shown_symb());   $anchor=&escape($anchor->shown_symb());
  return ($res->link(),$res->shown_symb(),$anchor);   return ($res->link(),$res->shown_symb(),$anchor);
     }      }
             # in case folder was skipped over as "only sequence"              # in case folder was skipped over as "only sequence"
     my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb());      my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb());
     if ($map=~/\.page$/) {      if ($map=~/\.page$/) {
  my $url=&Apache::lonnet::clutter($map);   my $url=&Apache::lonnet::clutter($map);
  $anchor=&Apache::lonnet::escape($src->shown_symb());   $anchor=&escape($src->shown_symb());
  return ($url,$res->shown_symb(),$anchor);   return ($url,$res->shown_symb(),$anchor);
     }      }
         }          }
Line 489  sub getLinkForResource { Line 167  sub getLinkForResource {
     # (when we first recurse on a map, it puts an undefined resource      # (when we first recurse on a map, it puts an undefined resource
     # on the bottom because $self->{HERE} isn't defined yet, and we      # on the bottom because $self->{HERE} isn't defined yet, and we
     # want the src for the map anyhow)      # want the src for the map anyhow)
     foreach (@$stack) {      foreach my $item (@$stack) {
         if (defined($_)) { $res = $_; }          if (defined($item)) { $res = $item; }
     }      }
   
     return ($res->link(),$res->shown_symb());      return ($res->link(),$res->shown_symb());
Line 682  sub timeToHumanString { Line 360  sub timeToHumanString {
   
  if($format ne '') {   if($format ne '') {
     my $timeStr = strftime($format, localtime($time));      my $timeStr = strftime($format, localtime($time));
     return $timeStr.&Apache::lonlocal::gettimezone();      return $timeStr.&Apache::lonlocal::gettimezone($time);
  }   }
   
         # Less then 5 days away, display day of the week and          # Less then 5 days away, display day of the week and
Line 693  sub timeToHumanString { Line 371  sub timeToHumanString {
             $timeStr =~ s/12:00 am/00:00/;              $timeStr =~ s/12:00 am/00:00/;
             $timeStr =~ s/12:00 pm/noon/;              $timeStr =~ s/12:00 pm/noon/;
             return ($inPast ? "last " : "this ") .              return ($inPast ? "last " : "this ") .
                 $timeStr.&Apache::lonlocal::gettimezone();                  $timeStr.&Apache::lonlocal::gettimezone($time);
         }          }
                   
  my $conjunction='on';   my $conjunction='on';
Line 708  sub timeToHumanString { Line 386  sub timeToHumanString {
             my $timeStr = strftime("$conjunction %A, %b %e at %I:%M %P", localtime($time));              my $timeStr = strftime("$conjunction %A, %b %e at %I:%M %P", localtime($time));
             $timeStr =~ s/12:00 am/00:00/;              $timeStr =~ s/12:00 am/00:00/;
             $timeStr =~ s/12:00 pm/noon/;              $timeStr =~ s/12:00 pm/noon/;
             return $timeStr.&Apache::lonlocal::gettimezone();              return $timeStr.&Apache::lonlocal::gettimezone($time);
         }          }
   
         # Not this year, so show the year          # Not this year, so show the year
         my $timeStr = strftime("$conjunction %A, %b %e %Y at %I:%M %P", localtime($time));          my $timeStr = strftime("$conjunction %A, %b %e %Y at %I:%M %P", localtime($time));
         $timeStr =~ s/12:00 am/00:00/;          $timeStr =~ s/12:00 am/00:00/;
         $timeStr =~ s/12:00 pm/noon/;          $timeStr =~ s/12:00 pm/noon/;
         return $timeStr.&Apache::lonlocal::gettimezone();          return $timeStr.&Apache::lonlocal::gettimezone($time);
     }      }
 }  }
   
Line 1094  sub render_resource { Line 772  sub render_resource {
           
     if ($resource->is_problem()) {      if ($resource->is_problem()) {
         if ($part eq '0' || $params->{'condensed'}) {          if ($part eq '0' || $params->{'condensed'}) {
             $icon ='<img src="'.$location.'/problem.gif" alt="'.&mt('Problem').'" border="0" />';      $icon = '<img src="'.$location.'/';
       if ($resource->is_task()) {
    $icon .= 'task.gif" alt="'.&mt('Task');
       } else {
    $icon .= 'problem.gif" alt="'.&mt('Problem');
       }
       $icon .='" border="0" />';
         } else {          } else {
             $icon = $params->{'indentString'};              $icon = $params->{'indentString'};
         }          }
Line 1119  sub render_resource { Line 803  sub render_resource {
  ($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" border='0' />";   ($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" border='0' />";
   
             $linkopen = "<a href=\"" . $params->{'url'} . '?' .               $linkopen = "<a href=\"" . $params->{'url'} . '?' . 
                 $params->{'queryString'} . '&filter=';                  $params->{'queryString'} . '&amp;filter=';
             $linkopen .= ($nowOpen xor $it->{CONDITION}) ?              $linkopen .= ($nowOpen xor $it->{CONDITION}) ?
                 addToFilter($filter, $mapId) :                  addToFilter($filter, $mapId) :
                 removeFromFilter($filter, $mapId);                  removeFromFilter($filter, $mapId);
             $linkopen .= "&condition=" . $it->{CONDITION} . '&hereType='              $linkopen .= "&amp;condition=" . $it->{CONDITION} . '&amp;hereType='
                 . $params->{'hereType'} . '&here=' .                  . $params->{'hereType'} . '&amp;here=' .
                 &Apache::lonnet::escape($params->{'here'}) .                   &escape($params->{'here'}) . 
                 '&jump=' .                  '&amp;jump=' .
                 &Apache::lonnet::escape($resource->symb()) .                   &escape($resource->symb()) . 
                 "&folderManip=1\">";                  "&amp;folderManip=1\">";
   
         } else {          } else {
             # Don't allow users to manipulate folder              # Don't allow users to manipulate folder
Line 1150  sub render_resource { Line 834  sub render_resource {
     }      }
           
     # We're done preparing and finally ready to start the rendering      # We're done preparing and finally ready to start the rendering
     my $result = "<td align='left' valign='center'>";      my $result = "<td align='left' valign='middle'>";
   
     my $indentLevel = $params->{'indentLevel'};      my $indentLevel = $params->{'indentLevel'};
     if ($newBranchText) { $indentLevel--; }      if ($newBranchText) { $indentLevel--; }
Line 1179  sub render_resource { Line 863  sub render_resource {
         !$params->{'condensed'}) {          !$params->{'condensed'}) {
  my $displaypart=$resource->part_display($part);   my $displaypart=$resource->part_display($part);
         $partLabel = " (".&mt('Part: [_1]', $displaypart).")";          $partLabel = " (".&mt('Part: [_1]', $displaypart).")";
  if ($link!~/\#/) { $link.='#'.&Apache::lonnet::escape($part); }   if ($link!~/\#/) { $link.='#'.&escape($part); }
         $title = "";          $title = "";
     }      }
   
Line 1214  sub render_communication_status { Line 898  sub render_communication_status {
     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 border="0" src="'.$location.'/chat.gif" />' .              '<img alt="'.&mt('New Discussion').'" border="0" src="'.$location.'/chat.gif" />' .
             $linkclose;              $linkclose;
     }      }
           
     if ($resource->getFeedback()) {      if ($resource->getFeedback()) {
         my $feedback = $resource->getFeedback();          my $feedback = $resource->getFeedback();
         foreach (split(/\,/, $feedback)) {          foreach my $msgid (split(/\,/, $feedback)) {
             if ($_) {              if ($msgid) {
                 $feedbackHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='                  $feedbackHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='
                     . &Apache::lonnet::escape($_) . '">'                      . &escape($msgid) . '">'
                     . '<img src="'.$location.'/feedback.gif" '                      . '<img alt="'.&mt('New Email').'" src="'.$location.'/feedback.gif" '
                     . 'border="0" /></a>';                      . 'border="0" /></a>';
             }              }
         }          }
Line 1233  sub render_communication_status { Line 917  sub render_communication_status {
     if ($resource->getErrors()) {      if ($resource->getErrors()) {
         my $errors = $resource->getErrors();          my $errors = $resource->getErrors();
         my $errorcount = 0;          my $errorcount = 0;
         foreach (split(/,/, $errors)) {          foreach my $msgid (split(/,/, $errors)) {
             last if ($errorcount>=10); # Only output 10 bombs maximum              last if ($errorcount>=10); # Only output 10 bombs maximum
             if ($_) {              if ($msgid) {
                 $errorcount++;                  $errorcount++;
                 $errorHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='                  $errorHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='
                     . &Apache::lonnet::escape($_) . '">'                      . &escape($msgid) . '">'
                     . '<img src="'.$location.'/bomb.gif" '                      . '<img alt="'.&mt('New Error').'" src="'.$location.'/bomb.gif" '
                     . 'border="0" /></a>';                      . 'border="0" /></a>';
             }              }
         }          }
Line 1249  sub render_communication_status { Line 933  sub render_communication_status {
  $discussionHTML = $feedbackHTML = $errorHTML = '';   $discussionHTML = $feedbackHTML = $errorHTML = '';
     }      }
   
     return "<td width=\"75\" align=\"left\" valign=\"center\">$discussionHTML$feedbackHTML$errorHTML&nbsp;</td>";      return "<td width=\"75\" align=\"left\" valign=\"middle\">$discussionHTML$feedbackHTML$errorHTML&nbsp;</td>";
   
 }  }
 sub render_quick_status {  sub render_quick_status {
Line 1274  sub render_quick_status { Line 958  sub render_quick_status {
         if ($icon) {          if ($icon) {
     my $location=      my $location=
  &Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon");   &Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon");
             $result .= "<td width='30' valign='center' width='50' align='right'>$linkopen<img width='25' height='25' src='$location' border='0' alt='$alt' />$linkclose</td>\n";              $result .= "<td valign='middle' width='50' align='right'>$linkopen<img width='25' height='25' src='$location' border='0' alt='$alt' />$linkclose</td>\n";
         } else {          } else {
             $result .= "<td width='30'>&nbsp;</td>\n";              $result .= "<td width='30'>&nbsp;</td>\n";
         }          }
Line 1286  sub render_quick_status { Line 970  sub render_quick_status {
 }  }
 sub render_long_status {  sub render_long_status {
     my ($resource, $part, $params) = @_;      my ($resource, $part, $params) = @_;
     my $result = "<td align='right' valign='center'>\n";      my $result = "<td align='right' valign='middle'>\n";
     my $firstDisplayed = !$params->{'condensed'} &&       my $firstDisplayed = !$params->{'condensed'} && 
         $params->{'multipart'} && $part eq "0";          $params->{'multipart'} && $part eq "0";
                                   
Line 1442  sub render { Line 1126  sub render {
     # marker      # marker
     my $filterHash = {};      my $filterHash = {};
     # Figure out what we're not displaying      # Figure out what we're not displaying
     foreach (split(/\,/, $env{"form.filter"})) {      foreach my $item (split(/\,/, $env{"form.filter"})) {
         if ($_) {          if ($item) {
             $filterHash->{$_} = "1";              $filterHash->{$item} = "1";
         }          }
     }      }
   
Line 1472  sub render { Line 1156  sub render {
             $navmap = Apache::lonnavmaps::navmap->new();              $navmap = Apache::lonnavmaps::navmap->new();
     if (!defined($navmap)) {      if (!defined($navmap)) {
  # no londer in course   # no londer in course
  return '<font color="red">'.&mt('No course selected').'</font><br />   return '<span class="LC_error">'.&mt('No course selected').'</span><br />
                         <a href="/adm/roles">'.&mt('Select a course').'</a><br />';                          <a href="/adm/roles">'.&mt('Select a course').'</a><br />';
     }      }
  }   }
Line 1610  sub render { Line 1294  sub render {
     if ($printCloseAll && !$args->{'resource_no_folder_link'}) {      if ($printCloseAll && !$args->{'resource_no_folder_link'}) {
  my ($link,$text);   my ($link,$text);
         if ($condition) {          if ($condition) {
     $link='"navmaps?condition=0&filter=&'.$queryString.      $link='"navmaps?condition=0&amp;filter=&amp;'.$queryString.
  '&here='.&Apache::lonnet::escape($here).'"';   '&here='.&escape($here).'"';
     $text='Close All Folders';      $text='Close all folders';
         } else {          } else {
     $link='"navmaps?condition=1&filter=&'.$queryString.      $link='"navmaps?condition=1&amp;filter=&amp;'.$queryString.
  '&here='.&Apache::lonnet::escape($here).'"';   '&here='.&escape($here).'"';
     $text='Open All Folders';      $text='Open all folders';
         }          }
  if ($args->{'caller'} eq 'navmapsdisplay') {   if ($args->{'caller'} eq 'navmapsdisplay') {
     &add_linkitem($args->{'linkitems'},'changefolder',      &add_linkitem($args->{'linkitems'},'changefolder',
Line 1661  END Line 1345  END
   
     if ($args->{'caller'} eq 'navmapsdisplay') {      if ($args->{'caller'} eq 'navmapsdisplay') {
         $result .= '<table><tr><td>'.          $result .= '<table><tr><td>'.
                    &Apache::loncommon::help_open_menu('','Navigation Screen','Navigation_Screen','',undef,'RAT').'</td>';                     &Apache::loncommon::help_open_menu('Navigation Screen','Navigation_Screen',undef,'RAT').'</td>';
  if ($env{'environment.remotenavmap'} ne 'on') {   if ($env{'environment.remotenavmap'} ne 'on') {
     $result .= '<td>&nbsp;</td>';       $result .= '<td>&nbsp;</td>'; 
         } else {          } else {
Line 1926  END Line 1610  END
     my $srcHasQuestion = $src =~ /\?/;      my $srcHasQuestion = $src =~ /\?/;
     $args->{"resourceLink"} = $src.      $args->{"resourceLink"} = $src.
  ($srcHasQuestion?'&':'?') .   ($srcHasQuestion?'&':'?') .
  'symb=' . &Apache::lonnet::escape($symb).$anchor;   '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 2217  sub generate_email_discuss_status { Line 1901  sub generate_email_discuss_status {
     my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',      my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',
                                         $env{'user.domain'},$env{'user.name'},'lastread');                                          $env{'user.domain'},$env{'user.name'},'lastread');
     my %lastreadtime = ();      my %lastreadtime = ();
     foreach (keys %lastread) {      foreach my $key (keys %lastread) {
         my $key = $_;          my $shortkey = $key;
         $key =~ s/_lastread$//;          $shortkey =~ s/_lastread$//;
         $lastreadtime{$key} = $lastread{$_};          $lastreadtime{$shortkey} = $lastread{$key};
     }      }
   
     my %feedback=();      my %feedback=();
Line 2230  sub generate_email_discuss_status { Line 1914  sub generate_email_discuss_status {
           
     foreach my $msgid (@keys) {      foreach my $msgid (@keys) {
  if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {   if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {
     my $plain=              my ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$fromcid,
  &Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid));                  $symb,$error) = &Apache::lonmsg::unpackmsgid($msgid);
     if ($plain=~/ \[([^\]]+)\]\:/) {              if (defined($symb)) {
  my $url=$1;                  if (defined($error) && $error == 1) {
  if ($plain=~/\:Error \[/) {                      $error{$symb}.=','.$msgid;
     $error{$url}.=','.$msgid;                   } else {
  } else {                      $feedback{$symb}.=','.$msgid;
     $feedback{$url}.=','.$msgid;                  }
  }              } else {
     }                  my $plain=
                       &LONCAPA::unescape(&LONCAPA::unescape($msgid));
                   if ($plain=~/ \[([^\]]+)\]\:/) {
                       my $url=$1;
                       if ($plain=~/\:Error \[/) {
                           $error{$url}.=','.$msgid;
                       } else {
                           $feedback{$url}.=','.$msgid;
                       }
                   }
               }
  }   }
     }      }
           
     #url's of resources that have feedbacks      #symbs of resources that have feedbacks (will be urls pre-2.3)
     $self->{FEEDBACK} = \%feedback;      $self->{FEEDBACK} = \%feedback;
     #or errors      #or errors (will be urls pre 2.3)
     $self->{ERROR_MSG} = \%error;      $self->{ERROR_MSG} = \%error;
     $self->{DISCUSSION_TIME} = \%discussiontime;      $self->{DISCUSSION_TIME} = \%discussiontime;
     $self->{EMAIL_STATUS} = \%emailstatus;      $self->{EMAIL_STATUS} = \%emailstatus;
Line 2350  sub last_post_time { Line 2044  sub last_post_time {
     return $self->{DISCUSSION_TIME}->{$ressymb};      return $self->{DISCUSSION_TIME}->{$ressymb};
 }  }
   
 sub unread_discussion {  sub discussion_info {
     my $self = shift;      my $self = shift;
     my $symb = shift;      my $symb = shift;
       my $filter = shift;
   
     $self->get_discussion_data();      $self->get_discussion_data();
   
Line 2364  sub unread_discussion { Line 2059  sub unread_discussion {
   
     my $prevread = $self->{LAST_READ}{$ressymb};      my $prevread = $self->{LAST_READ}{$ressymb};
   
     my $unreadcount = 0;      my $count = 0;
     my $hiddenflag = 0;      my $hiddenflag = 0;
     my $deletedflag = 0;      my $deletedflag = 0;
     my ($hidden,$deleted);      my ($hidden,$deleted,%info);
   
     my %subjects;  
   
     for (my $id=$version; $id>0; $id--) {      for (my $id=$version; $id>0; $id--) {
  my $vkeys=$self->{DISCUSSION_DATA}{$id.':keys:'.$discsymb};   my $vkeys=$self->{DISCUSSION_DATA}{$id.':keys:'.$discsymb};
Line 2385  sub unread_discussion { Line 2078  sub unread_discussion {
  $deletedflag = 1;   $deletedflag = 1;
     }      }
  } else {   } else {
     if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./)      if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./)) {
  && $prevread < $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'}) {                  if ($filter eq 'unread') {
     $unreadcount++;      if ($prevread >= $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'}) {
     $subjects{$unreadcount}=                          next;
  $id.': '.$self->{DISCUSSION_DATA}{$id.':'.$discsymb.':subject'};                      }
  }                  }
    $count++;
    $info{$count}{'subject'} =
       $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':subject'};
                   $info{$count}{'id'} = $id;
                   $info{$count}{'timestamp'} = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'};
               }
  }   }
     }      }
     if (wantarray) {      if (wantarray) {
  return ($unreadcount,\%subjects);   return ($count,%info);
     }      }
     return $unreadcount      return $count;
 }  }
   
 sub wrap_symb {  sub wrap_symb {
     my $self = shift;      my $self = shift;
     my $symb = shift;      my $symb = shift;
     if ($symb =~ m-___(adm/\w+/\w+/)(\d+)(/bulletinboard)$-) {      if ($symb =~ m-___(adm/[^/]+/[^/]+/)(\d+)(/bulletinboard)$-) {
         unless ($symb =~ m|adm/wrapper/adm|) {          unless ($symb =~ m|adm/wrapper/adm|) {
             $symb = 'bulletin___'.$2.'___adm/wrapper/'.$1.$2.$3;              $symb = 'bulletin___'.$2.'___adm/wrapper/'.$1.$2.$3;
         }          }
Line 2414  sub unwrap_symb { Line 2113  sub unwrap_symb {
     my $self = shift;      my $self = shift;
     my $ressymb = shift;      my $ressymb = shift;
     my $discsymb = $ressymb;      my $discsymb = $ressymb;
     if ($ressymb =~ m-^(bulletin___\d+___)adm/wrapper/(adm/\w+/\w+/\d+/bulletinboard)$-) {      if ($ressymb =~ m-^(bulletin___\d+___)adm/wrapper/(adm/[^/]+/[^/]+/\d+/bulletinboard)$-) {
          $discsymb = $1.$2;           $discsymb = $1.$2;
     }      }
     return $discsymb;      return $discsymb;
Line 2427  sub unwrap_symb { Line 2126  sub unwrap_symb {
 sub getFeedback {   sub getFeedback { 
     my $self = shift;      my $self = shift;
     my $symb = shift;      my $symb = shift;
       my $source = shift;
   
     $self->generate_email_discuss_status();      $self->generate_email_discuss_status();
   
     if (!defined($self->{FEEDBACK})) { return ""; }      if (!defined($self->{FEEDBACK})) { return ""; }
           
     return $self->{FEEDBACK}->{$symb};      my $feedback;
       if ($self->{FEEDBACK}->{$symb}) {
           $feedback = $self->{FEEDBACK}->{$symb};
           if ($self->{FEEDBACK}->{$source}) {
               $feedback .= ','.$self->{FEEDBACK}->{$source};
           }
       } else {
           if ($self->{FEEDBACK}->{$source}) {
               $feedback = $self->{FEEDBACK}->{$source};
           }
       }
       return $feedback;
 }  }
   
 # Private method: Get the errors for that resource (by source).  # Private method: Get the errors for that resource (by source).
 sub getErrors {   sub getErrors { 
     my $self = shift;      my $self = shift;
       my $symb = shift;
     my $src = shift;      my $src = shift;
   
     $self->generate_email_discuss_status();      $self->generate_email_discuss_status();
   
     if (!defined($self->{ERROR_MSG})) { return ""; }      if (!defined($self->{ERROR_MSG})) { return ""; }
     return $self->{ERROR_MSG}->{$src};  
       my $errors;
       if ($self->{ERROR_MSG}->{$symb}) {
           $errors = $self->{ERROR_MSG}->{$symb};
           if ($self->{ERROR_MSG}->{$src}) {
               $errors .= ','.$self->{ERROR_MSG}->{$src};
           }
       } else {
           if ($self->{ERROR_MSG}->{$src}) {
               $errors = $self->{ERROR_MSG}->{$src};
           }
       }
       return $errors;
 }  }
   
 =pod  =pod
Line 2577  sub parmval_real { Line 2301  sub parmval_real {
     my $result='';      my $result='';
   
     my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);      my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
       $mapname = &Apache::lonnet::deversion($mapname);
 # ----------------------------------------------------- Cascading lookup scheme  # ----------------------------------------------------- Cascading lookup scheme
     my $rwhat=$what;      my $rwhat=$what;
     $what=~s/^parameter\_//;      $what=~s/^parameter\_//;
Line 3433  sub next { Line 3157  sub next {
   
     # filter the next possibilities to remove things we've       # filter the next possibilities to remove things we've 
     # already seen.      # already seen.
     foreach (@$nextUnfiltered) {      foreach my $item (@$nextUnfiltered) {
         if (!defined($self->{ALREADY_SEEN}->{$_->{ID}})) {          if (!defined($self->{ALREADY_SEEN}->{$item->{ID}})) {
             push @$next, $_;              push @$next, $item;
         }          }
     }      }
   
Line 3833  sub contains_problem { Line 3557  sub contains_problem {
 }  }
 sub is_sequence {  sub is_sequence {
     my $self=shift;      my $self=shift;
     my $src = $self->src();  
     return $self->navHash("is_map_", 1) &&       return $self->navHash("is_map_", 1) && 
  $self->navHash("map_type_" . $self->map_pc()) eq 'sequence';   $self->navHash("map_type_" . $self->map_pc()) eq 'sequence';
 }  }
Line 3908  Returns a string with the type of the ma Line 3631  Returns a string with the type of the ma
 sub map_finish {  sub map_finish {
     my $self = shift;      my $self = shift;
     my $src = $self->src();      my $src = $self->src();
     $src = Apache::lonnet::clutter($src);      $src = &Apache::lonnet::clutter($src);
     my $res = $self->navHash("map_finish_$src", 0);      my $res = $self->navHash("map_finish_$src", 0);
     $res = $self->{NAV_MAP}->getById($res);      $res = $self->{NAV_MAP}->getById($res);
     return $res;      return $res;
Line 3921  sub map_pc { Line 3644  sub map_pc {
 sub map_start {  sub map_start {
     my $self = shift;      my $self = shift;
     my $src = $self->src();      my $src = $self->src();
     $src = Apache::lonnet::clutter($src);      $src = &Apache::lonnet::clutter($src);
     my $res = $self->navHash("map_start_$src", 0);      my $res = $self->navHash("map_start_$src", 0);
     $res = $self->{NAV_MAP}->getById($res);      $res = $self->{NAV_MAP}->getById($res);
     return $res;      return $res;
Line 4033  sub awarded { Line 3756  sub awarded {
     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'};
 }  }
   # this should work exactly like the copy in lonhomework.pm
 sub duedate {  sub duedate {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
       my $date;
     my $interval=$self->parmval("interval", $part);      my $interval=$self->parmval("interval", $part);
     if ($interval) {      my $due_date=$self->parmval("duedate", $part);
       if ($interval =~ /\d+/) {
  my $first_access=&Apache::lonnet::get_first_access('map',$self->symb);   my $first_access=&Apache::lonnet::get_first_access('map',$self->symb);
  if ($first_access) { return ($first_access+$interval); }   if (defined($first_access)) {
       $interval = $first_access+$interval;
       $date = ($interval < $due_date)? $interval : $due_date;
    } else {
       $date = $due_date;
    }
       } else {
    $date = $due_date;
     }      }
     return $self->parmval("duedate", $part);      return $date;
 }  }
 sub handgrade {  sub handgrade {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
Line 4142  data was not extracted when the nav map Line 3875  data was not extracted when the nav map
 Returns a false value if there hasn't been discussion otherwise returns  Returns a false value if there hasn't been discussion otherwise returns
 unix timestamp of last time a discussion posting (or edit) was made.  unix timestamp of last time a discussion posting (or edit) was made.
   
 =item * B<unread_discussion>:  =item * B<discussion_info>:
   
 returns in scalar context the count of the number of unread discussion  optional argument is a filter (currently can be 'unread');
 postings  returns in scalar context the count of the number of discussion postings.
   
 returns in list context both the count of postings and a hash ref  returns in list context both the count of postings and a hash ref
 containing the subjects of all unread postings  containing information about the postings (subject, id, timestamp) in a hash.
   
   Default is to return counts for all postings.  However if called with a second argument set to 'unread', will return information about only unread postings.
   
 =item * B<getFeedback>:  =item * B<getFeedback>:
   
Line 4157  for the resource, or the null string if Line 3892  for the resource, or the null string if
 email data was not extracted when the nav map was constructed. Usually  email data was not extracted when the nav map was constructed. Usually
 used like this:  used like this:
   
  for (split(/\,/, $res->getFeedback())) {   for my $url (split(/\,/, $res->getFeedback())) {
     my $link = &Apache::lonnet::escape($_);      my $link = &escape($url);
     ...      ...
   
 and use the link as appropriate.  and use the link as appropriate.
Line 4175  sub last_post_time { Line 3910  sub last_post_time {
     return $self->{NAV_MAP}->last_post_time($self->symb());      return $self->{NAV_MAP}->last_post_time($self->symb());
 }  }
   
 sub unread_discussion {  sub discussion_info {
     my $self = shift;      my ($self,$filter) = @_;
     return $self->{NAV_MAP}->unread_discussion($self->symb());      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();
     if ($source =~ /^\/res\//) { $source = substr $source, 5; }      if ($source =~ /^\/res\//) { $source = substr $source, 5; }
     return $self->{NAV_MAP}->getFeedback($source);      return $self->{NAV_MAP}->getFeedback($symb,$source);
 }  }
   
 sub getErrors {  sub getErrors {
     my $self = shift;      my $self = shift;
     my $source = $self->src();      my $source = $self->src();
       my $symb = $self->symb();
     if ($source =~ /^\/res\//) { $source = substr $source, 5; }      if ($source =~ /^\/res\//) { $source = substr $source, 5; }
     return $self->{NAV_MAP}->getErrors($source);      return $self->{NAV_MAP}->getErrors($symb,$source);
 }  }
   
 =pod  =pod
Line 4352  sub extractParts { Line 4089  sub extractParts {
  $self->{PART_TYPE} = {};   $self->{PART_TYPE} = {};
  return;   return;
     }      }
     foreach (split(/\,/,$metadata)) {      foreach my $entry (split(/\,/,$metadata)) {
  if ($_ =~ /^(?:part|Task)_(.*)$/) {   if ($entry =~ /^(?:part|Task)_(.*)$/) {
     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})) {
Line 4378  sub extractParts { Line 4115  sub extractParts {
   
   
         # Init the responseIdHash          # Init the responseIdHash
         foreach (@{$self->{PARTS}}) {          foreach my $part (@{$self->{PARTS}}) {
             $responseIdHash{$_} = [];              $responseIdHash{$part} = [];
         }          }
   
         # Now, the unfortunate thing about this is that parts, part name, and          # Now, the unfortunate thing about this is that parts, part name, and
Line 4388  sub extractParts { Line 4125  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 (split /,/, $metadata) {          foreach my $data (split /,/, $metadata) {
             if ($_ =~ /^([a-zA-Z]+)response_(.*)/              if ($data =~ /^([a-zA-Z]+)response_(.*)/
  || $_ =~ /^(Task)_(.*)/) {   || $data =~ /^(Task)_(.*)/) {
                 my $responseType = $1;                  my $responseType = $1;
                 my $partStuff = $2;                  my $partStuff = $2;
                 my $partIdSoFar = '';                  my $partIdSoFar = '';
Line 4402  sub extractParts { Line 4139  sub extractParts {
                     if ($parts{$partIdSoFar}) {                      if ($parts{$partIdSoFar}) {
                         my @otherChunks = @partChunks[$i+1..$#partChunks];                          my @otherChunks = @partChunks[$i+1..$#partChunks];
                         my $responseId = join('_', @otherChunks);                          my $responseId = join('_', @otherChunks);
                         push @{$responseIdHash{$partIdSoFar}}, $responseId;   if ($self->is_task()) {
                         push @{$responseTypeHash{$partIdSoFar}}, $responseType;      push(@{$responseIdHash{$partIdSoFar}},
    $partIdSoFar);
    } else {
       push(@{$responseIdHash{$partIdSoFar}},
    $responseId);
    }
                           push(@{$responseTypeHash{$partIdSoFar}},
        $responseType);
                     }                      }
                 }                  }
             }              }
Line 4452  the completion information. Line 4196  the completion information.
 Idiomatic usage of these two methods would probably look something  Idiomatic usage of these two methods would probably look something
 like  like
   
  foreach ($resource->parts()) {   foreach my $part ($resource->parts()) {
     my $dateStatus = $resource->getDateStatus($_);      my $dateStatus = $resource->getDateStatus($part);
     my $completionStatus = $resource->getCompletionStatus($_);      my $completionStatus = $resource->getCompletionStatus($part);
   
     or      or
   
     my $status = $resource->status($_);      my $status = $resource->status($part);
   
     ... use it here ...      ... use it here ...
  }   }

Removed from v.1.372  
changed lines
  Added in v.1.395


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