Diff for /loncom/interface/lonnavmaps.pm between versions 1.349.2.5 and 1.408

version 1.349.2.5, 2005/12/29 18:50:35 version 1.408, 2008/01/10 16:49:58
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 Time::HiRes qw( gettimeofday tv_interval );  use Time::HiRes qw( gettimeofday tv_interval );
   use LONCAPA;
   use DateTime();
   
 # symbolic constants  # symbolic constants
 sub SYMB { return 1; }  sub SYMB { return 1; }
Line 86  my %colormap = Line 85  my %colormap =
       $resObj->PARTIALLY_CORRECT      => '#006600'        $resObj->PARTIALLY_CORRECT      => '#006600'
       );        );
 # And a special case in the nav map; what to do when the assignment  # And a special case in the nav map; what to do when the assignment
 # is not yet done and due in less then 24 hours  # is not yet done and due in less than 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 116  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 $html=&Apache::lonxml::xmlbegin();  
  $r->print(<<"ENDSUBM");  
  $html  
         <head>  
   <script type="text/javascript">  
      function submitthis() {  
     $menu  
     self.close();  
     }  
   
    </script>  
         </head>  
  <body bgcolor="#FFFFFF" onLoad="submitthis()"></body>  
         </html>  
 ENDSUBM  
         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;  
     my $html=&Apache::lonxml::xmlbegin();  
     $r->print("$html<head>\n");  
     $r->print("<title>".&mt('Navigate Course Contents')."</title>");  
 # ------------------------------------------------------------ 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='';  
     if ($env{'environment.remotenavmap'} eq 'on') {  
  $r->print('<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('</head>'.  
               &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>' .  
                   '</body></html>');  
         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("</body></html>");  
     $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 472  sub getLinkForResource { Line 148  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 490  sub getLinkForResource { Line 166  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());      if ($res) {
    return ($res->link(),$res->shown_symb());
       }
       return;
 }  }
   
 # Convenience function: This separates the logic of how to create  # Convenience function: This separates the logic of how to create
Line 508  sub getDescription { Line 187  sub getDescription {
     my $part = shift;      my $part = shift;
     my $status = $res->status($part);      my $status = $res->status($part);
   
       my $open = $res->opendate($part);
       my $due = $res->duedate($part);
       my $answer = $res->answerdate($part);
   
     if ($status == $res->NETWORK_FAILURE) {       if ($status == $res->NETWORK_FAILURE) { 
         return &mt("Having technical difficulties; please check status later");           return &mt("Having technical difficulties; please check status later"); 
     }      }
Line 515  sub getDescription { Line 198  sub getDescription {
         return &mt("Not currently assigned.");          return &mt("Not currently assigned.");
     }      }
     if ($status == $res->OPEN_LATER) {      if ($status == $res->OPEN_LATER) {
         return "Open " . timeToHumanString($res->opendate($part),'start');          return "Open " .timeToHumanString($open,'start');
     }      }
     if ($status == $res->OPEN) {      if ($status == $res->OPEN) {
         if ($res->duedate($part)) {          if ($due) {
             return &mt("Due")."  " .timeToHumanString($res->duedate($part),'end');      if ($res->is_practice()) {
    return &mt("Closes ")."  " .timeToHumanString($due,'start');
       } else {
    return &mt("Due")."  " .timeToHumanString($due,'end');
       }
         } else {          } else {
             return &mt("Open, no due date");              return &mt("Open, no due date");
         }          }
     }      }
     if ($status == $res->PAST_DUE_ANSWER_LATER) {      if ($status == $res->PAST_DUE_ANSWER_LATER) {
         return &mt("Answer open")." " . timeToHumanString($res->answerdate($part),'start');          return &mt("Answer open")." " .timeToHumanString($answer,'start');
     }      }
     if ($status == $res->PAST_DUE_NO_ANSWER) {      if ($status == $res->PAST_DUE_NO_ANSWER) {
         return &mt("Was due")." " . timeToHumanString($res->duedate($part),'end');   if ($res->is_practice()) {
       return &mt("Closed")." " . timeToHumanString($due,'start');
    } else {
       return &mt("Was due")." " . timeToHumanString($due,'end');
    }
     }      }
     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') {
Line 550  sub getDescription { Line 241  sub getDescription {
                 $triesString = "<b>$triesString</b>";                  $triesString = "<b>$triesString</b>";
             }              }
         }          }
         if ($res->duedate($part)) {          if ($due) {
             return &mt("Due")." " . timeToHumanString($res->duedate($part),'end') .              return &mt("Due")." " . timeToHumanString($due,'end') .
                 " $triesString";                  " $triesString";
         } else {          } else {
             return &mt("No due date")." $triesString";              return &mt("No due date")." $triesString";
Line 562  sub getDescription { Line 253  sub getDescription {
     }      }
 }  }
   
 # Convenience function, so others can use it: Is the problem due in less then  # Convenience function, so others can use it: Is the problem due in less than
 # 24 hours, and still can be done?  # 24 hours, and still can be done?
   
 sub dueInLessThan24Hours {  sub dueInLessThan24Hours {
Line 577  sub dueInLessThan24Hours { Line 268  sub dueInLessThan24Hours {
 }  }
   
 # Convenience function, so others can use it: Is there only one try remaining for the  # Convenience function, so others can use it: Is there only one try remaining for the
 # part, with more then one try to begin with, not due yet and still can be done?  # part, with more than one try to begin with, not due yet and still can be done?
 sub lastTry {  sub lastTry {
     my $res = shift;      my $res = shift;
     my $part = shift;      my $part = shift;
Line 618  sub timeToHumanString { Line 309  sub timeToHumanString {
     }       } 
     my $now = time();      my $now = time();
   
     my @time = localtime($time);  
     my @now = localtime($now);  
   
     # Positive = future      # Positive = future
     my $delta = $time - $now;      my $delta = $time - $now;
   
Line 646  sub timeToHumanString { Line 334  sub timeToHumanString {
         my $tense = $inPast ? " ago" : "";          my $tense = $inPast ? " ago" : "";
         my $prefix = $inPast ? "" : "in ";          my $prefix = $inPast ? "" : "in ";
                   
         # Less then a minute          # Less than a minute
         if ( $delta < $minute ) {          if ( $delta < $minute ) {
             if ($delta == 1) { return "${prefix}1 second$tense"; }              if ($delta == 1) { return "${prefix}1 second$tense"; }
             return "$prefix$delta seconds$tense";              return "$prefix$delta seconds$tense";
         }          }
   
         # Less then an hour          # Less than an hour
         if ( $delta < $hour ) {          if ( $delta < $hour ) {
             # If so, use minutes              # If so, use minutes
             my $minutes = floor($delta / 60);              my $minutes = floor($delta / 60);
Line 660  sub timeToHumanString { Line 348  sub timeToHumanString {
             return "$prefix$minutes minutes$tense";              return "$prefix$minutes minutes$tense";
         }          }
                   
         # Is it less then 24 hours away? If so,          # Is it less than 24 hours away? If so,
         # display hours + minutes          # display hours + minutes
         if ( $delta < $hour * 24) {          if ( $delta < $hour * 24) {
             my $hours = floor($delta / $hour);              my $hours = floor($delta / $hour);
Line 679  sub timeToHumanString { Line 367  sub timeToHumanString {
             return "$prefix$hourString$minuteString$tense";              return "$prefix$hourString$minuteString$tense";
         }          }
   
    my $dt = DateTime->from_epoch(epoch => $time)
                    ->set_time_zone(&Apache::lonlocal::gettimezone());
   
  # If there's a caller supplied format, use it.   # If there's a caller supplied format, use it.
   
  if($format ne '') {   if ($format ne '') {
     my $timeStr = strftime($format, localtime($time));      my $timeStr = $dt->strftime($format);
     return $timeStr.&Apache::lonlocal::gettimezone();      return $timeStr.' ('.$dt->time_zone_short_name().')';
  }   }
   
         # Less then 5 days away, display day of the week and          # Less than 5 days away, display day of the week and
         # HH:MM          # HH:MM
   
         if ( $delta < $day * 5 ) {          if ( $delta < $day * 5 ) {
             my $timeStr = strftime("%A, %b %e at %I:%M %P", localtime($time));              my $timeStr = $dt->strftime("%A, %b %e at %I:%M %P (%Z)");
             $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;
         }          }
                   
  my $conjunction='on';   my $conjunction='on';
Line 704  sub timeToHumanString { Line 395  sub timeToHumanString {
     $conjunction='by';      $conjunction='by';
  }   }
         # Is it this year?          # Is it this year?
         if ( $time[5] == $now[5]) {   my $dt_now = DateTime->from_epoch(epoch => $now)
                        ->set_time_zone(&Apache::lonlocal::gettimezone());
           if ( $dt->year() == $dt_now->year()) {
             # Return on Month Day, HH:MM meridian              # Return on Month Day, HH:MM meridian
             my $timeStr = strftime("$conjunction %A, %b %e at %I:%M %P", localtime($time));              my $timeStr = $dt->strftime("$conjunction %A, %b %e at %I:%M %P (%Z)");
             $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;
         }          }
   
         # 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 = 
       $dt->strftime("$conjunction %A, %b %e %Y at %I:%M %P (%Z)");
         $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;
     }      }
 }  }
   
Line 751  to compute due to the amount of data tha Line 445  to compute due to the amount of data tha
 processed.  processed.
   
 Apache::lonnavmaps provides an object model for manipulating this  Apache::lonnavmaps provides an object model for manipulating this
 information in a higher-level fashion then directly manipulating   information in a higher-level fashion than directly manipulating 
 the hash. It also provides access to several auxilary functions   the hash. It also provides access to several auxilary functions 
 that aren't necessarily stored in the Big Hash, but are a per-  that aren't necessarily stored in the Big Hash, but are a per-
 resource sort of value, like whether there is any feedback on   resource sort of value, like whether there is any feedback on 
Line 797  Apache::lonnavmaps::render({}). Line 491  Apache::lonnavmaps::render({}).
 =head2 Overview of Columns  =head2 Overview of Columns
   
 The renderer will build an HTML table for the navmap and return  The renderer will build an HTML table for the navmap and return
 it. The table is consists of several columns, and a row for each  it. The table consists of several columns, and a row for each
 resource (or possibly each part). You tell the renderer how many  resource (or possibly each part). You tell the renderer how many
 columns to create and what to place in each column, optionally using  columns to create and what to place in each column, optionally using
 one or more of the prepared columns, and the renderer will assemble  one or more of the prepared columns, and the renderer will assemble
Line 928  instruct the renderer to render only a p Line 622  instruct the renderer to render only a p
 the source of the map you want to process, like  the source of the map you want to process, like
 '/res/103/jerf/navmap.course.sequence'.  '/res/103/jerf/navmap.course.sequence'.
   
   =item * B<include_top_level_map>: default: false
   
   If you need to include the top level map (meaning the course) in the
   rendered output set this to true
   
 =item * B<navmap>: default: constructs one from %env  =item * B<navmap>: default: constructs one from %env
   
 A reference to a navmap, used only if an iterator is not passed in. If  A reference to a navmap, used only if an iterator is not passed in. If
Line 1095  sub render_resource { Line 794  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="&nbsp;&nbsp;" 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 1112  sub render_resource { Line 817  sub render_resource {
         }          }
   
  my $folderType = $resource->is_sequence() ? 'folder' : 'page';   my $folderType = $resource->is_sequence() ? 'folder' : 'page';
           my $title=$resource->title;
           $title=~s/\"/\&quot;/g;
         if (!$params->{'resource_no_folder_link'}) {          if (!$params->{'resource_no_folder_link'}) {
             $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';              $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';
     $icon = "<img src='$location/$icon' alt='".      $icon = "<img src='$location/$icon' alt=\"".
  ($nowOpen ? 'Open Folder' : 'Close Folder')."' 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
             $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') .              $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') .
                 '.nomanip.gif';                  '.nomanip.gif';
             $icon = "<img src='$location/$icon' alt='".              $icon = "<img src='$location/$icon' alt=\"".
  ($nowOpen ? 'Open Folder' : 'Close Folder')."' border='0' />";   ($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" border='0' />";
   
             $linkopen = "";              $linkopen = "";
             $linkclose = "";              $linkclose = "";
Line 1143  sub render_resource { Line 849  sub render_resource {
     }      }
   
     if ($resource->randomout()) {      if ($resource->randomout()) {
         $nonLinkedText .= ' <i>(hidden)</i> ';          $nonLinkedText .= ' <i>('.&mt('hidden').')</i> ';
     }      }
     if (!$resource->condval()) {      if (!$resource->condval()) {
         $nonLinkedText .= ' <i>(conditionally hidden)</i> ';          $nonLinkedText .= ' <i>('.&mt('conditionally hidden').')</i> ';
     }      }
           
     # 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 1178  sub render_resource { Line 884  sub render_resource {
     if ($resource->is_problem() && $part ne '0' &&       if ($resource->is_problem() && $part ne '0' && 
         !$params->{'condensed'}) {          !$params->{'condensed'}) {
  my $displaypart=$resource->part_display($part);   my $displaypart=$resource->part_display($part);
         $partLabel = " (Part: $displaypart)";          $partLabel = " (".&mt('Part: [_1]', $displaypart).")";
  if ($link!~/\#/) { $link.='#'.&Apache::lonnet::escape($part); }   if ($link!~/\#/) { $link.='#'.&escape($part); }
         $title = "";          $title = "";
     }      }
   
     if ($params->{'condensed'} && $resource->countParts() > 1) {      if ($params->{'condensed'} && $resource->countParts() > 1) {
         $nonLinkedText .= ' (' . $resource->countParts() . ' parts)';          $nonLinkedText .= ' ('.&mt('[_1] parts', $resource->countParts()).')';
     }      }
   
     my $target;      my $target;
Line 1214  sub render_communication_status { Line 920  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 939  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 955  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 980  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 992  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";
                                   
     my $color;      my $color;
     if ($resource->is_problem()) {      if ($resource->is_problem() || $resource->is_practice()) {
         $color = $colormap{$resource->status};          $color = $colormap{$resource->status};
                   
         if (dueInLessThan24Hours($resource, $part) ||          if (dueInLessThan24Hours($resource, $part) ||
Line 1301  sub render_long_status { Line 1007  sub render_long_status {
     }      }
           
     if ($resource->kind() eq "res" &&      if ($resource->kind() eq "res" &&
         $resource->is_problem() &&          ($resource->is_problem() || $resource->is_practice()) &&
         !$firstDisplayed) {          !$firstDisplayed) {
         if ($color) {$result .= "<font color=\"$color\"><b>"; }          if ($color) {$result .= "<font color=\"$color\"><b>"; }
         $result .= getDescription($resource, $part);          $result .= getDescription($resource, $part);
         if ($color) {$result .= "</b></font>"; }          if ($color) {$result .= "</b></font>"; }
     }      }
     if ($resource->is_map() && advancedUser() && $resource->randompick()) {      if ($resource->is_map() && &advancedUser() && $resource->randompick()) {
         $result .= '(randomly select ' . $resource->randompick() .')';          $result .= &mt('(randomly select [_1])', $resource->randompick());
       }
       if ($resource->is_map() && &advancedUser() && $resource->randomorder()) {
           $result .= &mt('(randomly ordered)');
     }      }
   
     # Debugging code      # Debugging code
Line 1343  my %statusStrings = Line 1052  my %statusStrings =
      );       );
 my @statuses = ($resObj->CORRECT, $resObj->ATTEMPTED, $resObj->INCORRECT, $resObj->OPEN, $resObj->CLOSED, $resObj->ERROR);  my @statuses = ($resObj->CORRECT, $resObj->ATTEMPTED, $resObj->INCORRECT, $resObj->OPEN, $resObj->CLOSED, $resObj->ERROR);
   
 use Data::Dumper;  
 sub render_parts_summary_status {  sub render_parts_summary_status {
     my ($resource, $part, $params) = @_;      my ($resource, $part, $params) = @_;
     if (!$resource->is_problem() && !$resource->contains_problem) { return '<td></td>'; }      if (!$resource->is_problem() && !$resource->contains_problem) { return '<td></td>'; }
Line 1442  sub render { Line 1150  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 1180  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">No course selected</font><br />   return '<span class="LC_error">'.&mt('No course selected').'</span><br />
                         <a href="/adm/roles">Select a course</a><br />';                          <a href="/adm/roles">'.&mt('Select a course').'</a><br />';
     }      }
  }   }
   
Line 1550  sub render { Line 1258  sub render {
   
             $args->{'iterator'} = $it = $navmap->getIterator($firstResource, $finishResource, $filterHash, $condition);              $args->{'iterator'} = $it = $navmap->getIterator($firstResource, $finishResource, $filterHash, $condition);
         } else {          } else {
             $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition);              $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition,undef,$args->{'include_top_level_map'});
         }          }
     }      }
   
Line 1587  sub render { Line 1295  sub render {
     # Print key?      # Print key?
     if ($printKey) {      if ($printKey) {
         $result .= '<table border="0" cellpadding="2" cellspacing="0">';          $result .= '<table border="0" cellpadding="2" cellspacing="0">';
         my $date=localtime;  
         $result.='<tr><td align="right" valign="bottom">Key:&nbsp;&nbsp;</td>';          $result.='<tr><td align="right" valign="bottom">Key:&nbsp;&nbsp;</td>';
  my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");   my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");
         if ($navmap->{LAST_CHECK}) {          if ($navmap->{LAST_CHECK}) {
Line 1610  sub render { Line 1317  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 1368  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 1633  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 2066  In order of increasing complexity and po Line 1773  In order of increasing complexity and po
   
 =over 4  =over 4
   
 =item * C<$navmap-E<gt>getByX>, where X is B<Id>, B<Symb>, B<Url> or B<MapPc>. This provides  =item * C<$navmap-E<gt>getByX>, where X is B<Id>, B<Symb> or B<MapPc> and getResourceByUrl. This provides
     various ways to obtain resource objects, based on various identifiers.      various ways to obtain resource objects, based on various identifiers.
     Use this when you want to request information about one object or       Use this when you want to request information about one object or 
     a handful of resources you already know the identities of, from some      a handful of resources you already know the identities of, from some
     other source. For more about Ids, Symbs, and MapPcs, see the      other source. For more about Ids, Symbs, and MapPcs, see the
     Resource documentation. Note that Url should be a B<last resort>,      Resource documentation. Note that Url should be a B<last resort>,
     not your first choice; it only works when there is only one      not your first choice; it only really works when there is only one
     instance of the resource in the course, which only applies to      instance of the resource in the course, which only applies to
     maps, and even that may change in the future.      maps, and even that may change in the future (see the B<getResourceByUrl>
       documentation for more details.)
   
 =item * C<my @resources = $navmap-E<gt>retrieveResources(args)>. This  =item * C<my @resources = $navmap-E<gt>retrieveResources(args)>. This
     retrieves resources matching some criterion and returns them      retrieves resources matching some criterion and returns them
Line 2117  See iterator documentation below. Line 1825  See iterator documentation below.
 use strict;  use strict;
 use GDBM_File;  use GDBM_File;
 use Apache::lonnet;  use Apache::lonnet;
   use LONCAPA;
   
 sub new {  sub new {
     # magic invocation to create a class instance      # magic invocation to create a class instance
Line 2216  sub generate_email_discuss_status { Line 1925  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 2229  sub generate_email_discuss_status { Line 1938  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=~/ \[([^\]]+)\]\:/) {              &Apache::lonenc::check_decrypt(\$symb); 
  my $url=$1;              if (($fromcid ne '') && ($fromcid ne $cid)) {
  if ($plain=~/\:Error \[/) {                  next;
     $error{$url}.=','.$msgid;               }
  } else {              if (defined($symb)) {
     $feedback{$url}.=','.$msgid;                  if (defined($error) && $error == 1) {
  }                      $error{$symb}.=','.$msgid;
     }                  } else {
                       $feedback{$symb}.=','.$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 2269  sub get_user_data { Line 1992  sub get_user_data {
 sub get_discussion_data {  sub get_discussion_data {
     my $self = shift;      my $self = shift;
     if ($self->{RETRIEVED_DISCUSSION_DATA}) {      if ($self->{RETRIEVED_DISCUSSION_DATA}) {
          return  $self->{DISCUSSION_DATA};   return $self->{DISCUSSION_DATA};
     }      }
                                                                                   
       $self->generate_email_discuss_status();    
   
     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'};
                                                                                   
     # Retrieve discussion data for resources in course      # Retrieve discussion data for resources in course
     my %discussion_data = &Apache::lonnet::dump($cid,$cdom,$cnum);      my %discussion_data = &Apache::lonnet::dumpstore($cid,$cdom,$cnum);
                                                                                   
   
     $self->{DISCUSSION_DATA} = \%discussion_data;      $self->{DISCUSSION_DATA} = \%discussion_data;
     $self->{RETRIEVED_DISCUSSION_DATA} = 1;      $self->{RETRIEVED_DISCUSSION_DATA} = 1;
     return $self->{DISCUSSION_DATA};      return $self->{DISCUSSION_DATA};
Line 2340  sub hasDiscussion { Line 2065  sub hasDiscussion {
     }      }
 }  }
   
   sub last_post_time {
       my $self = shift;
       my $symb = shift;
       my $ressymb = $self->wrap_symb($symb);
       return $self->{DISCUSSION_TIME}->{$ressymb};
   }
   
   sub discussion_info {
       my $self = shift;
       my $symb = shift;
       my $filter = shift;
   
       $self->get_discussion_data();
   
       my $ressymb = $self->wrap_symb($symb);
       # keys used to store bulletinboard postings use 'unwrapped' symb. 
       my $discsymb = &escape($self->unwrap_symb($ressymb));
       my $version = $self->{DISCUSSION_DATA}{'version:'.$discsymb};
       if (!$version) { return; }
   
       my $prevread = $self->{LAST_READ}{$ressymb};
   
       my $count = 0;
       my $hiddenflag = 0;
       my $deletedflag = 0;
       my ($hidden,$deleted,%info);
   
       for (my $id=$version; $id>0; $id--) {
    my $vkeys=$self->{DISCUSSION_DATA}{$id.':keys:'.$discsymb};
    my @keys=split(/:/,$vkeys);
    if (grep(/^hidden$/ ,@keys)) {
       if (!$hiddenflag) {
    $hidden = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':hidden'};
    $hiddenflag = 1;
       }
    } elsif (grep(/^deleted$/,@keys)) {
       if (!$deletedflag) {
    $deleted = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':deleted'};
    $deletedflag = 1;
       }
    } else {
       if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./)) {
                   if ($filter eq 'unread') {
       if ($prevread >= $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'}) {
                           next;
                       }
                   }
    $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) {
    return ($count,%info);
       }
       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 2351  sub wrap_symb { Line 2137  sub wrap_symb {
     return $symb;      return $symb;
 }  }
   
   sub unwrap_symb {
       my $self = shift;
       my $ressymb = shift;
       my $discsymb = $ressymb;
       if ($ressymb =~ m-^(bulletin___\d+___)adm/wrapper/(adm/[^/]+/[^/]+/\d+/bulletinboard)$-) {
            $discsymb = $1.$2;
       }
       return $discsymb;
   }
   
 # Private method: Does the given resource (as a symb string) have  # Private method: Does the given resource (as a symb string) have
 # current feedback? Returns the string in the feedback hash, which  # current feedback? Returns the string in the feedback hash, which
 # will be false if it does not exist.  # will be false if it does not exist.
Line 2358  sub wrap_symb { Line 2154  sub wrap_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 2400  the given map. This is one of the proper Line 2221  the given map. This is one of the proper
   
 # The strategy here is to cache the resource objects, and only construct them  # The strategy here is to cache the resource objects, and only construct them
 # as we use them. The real point is to prevent reading any more from the tied  # as we use them. The real point is to prevent reading any more from the tied
 # hash then we have to, which should hopefully alleviate speed problems.  # hash than we have to, which should hopefully alleviate speed problems.
   
 sub getById {  sub getById {
     my $self = shift;      my $self = shift;
Line 2474  sub finishResource { Line 2295  sub finishResource {
 # the actual lookup; parmval caches the results.  # the actual lookup; parmval caches the results.
 sub parmval {  sub parmval {
     my $self = shift;      my $self = shift;
     my ($what,$symb)=@_;      my ($what,$symb,$recurse)=@_;
     my $hashkey = $what."|||".$symb;      my $hashkey = $what."|||".$symb;
   
     if (defined($self->{PARM_CACHE}->{$hashkey})) {      if (defined($self->{PARM_CACHE}->{$hashkey})) {
         return $self->{PARM_CACHE}->{$hashkey};         if (wantarray) {
              return @{$self->{PARM_CACHE}->{$hashkey}};
          } else {
              return $self->{PARM_CACHE}->{$hashkey}->[0];
          }
     }      }
   
     my $result = $self->parmval_real($what, $symb);      my $result = $self->parmval_real($what, $symb, $recurse);
     $self->{PARM_CACHE}->{$hashkey} = $result;      $self->{PARM_CACHE}->{$hashkey} = $result;
     return $result;      if (wantarray) {
          return @{$result};
       }
       return $result->[0];
 }  }
   
 sub parmval_real {  sub parmval_real {
Line 2495  sub parmval_real { Line 2323  sub parmval_real {
   
     my $cid=$env{'request.course.id'};      my $cid=$env{'request.course.id'};
     my $csec=$env{'request.course.sec'};      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=$env{'user.name'};      my $uname=$env{'user.name'};
     my $udom=$env{'user.domain'};      my $udom=$env{'user.domain'};
   
     unless ($symb) { return ''; }      unless ($symb) { return ['']; }
     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 2512  sub parmval_real { Line 2346  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 $grplevelr=$usercourseprefix.'.['.$cgroup.'].'.$symbparm;
       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;
Line 2526  sub parmval_real { Line 2364  sub parmval_real {
   
 # ---------------------------------------------------------- first, check user  # ---------------------------------------------------------- first, check user
     if ($uname and defined($useropt)) {      if ($uname and defined($useropt)) {
         if (defined($$useropt{$courselevelr})) { return $$useropt{$courselevelr}; }          if (defined($$useropt{$courselevelr})) { return [$$useropt{$courselevelr},'resource']; }
         if (defined($$useropt{$courselevelm})) { return $$useropt{$courselevelm}; }          if (defined($$useropt{$courselevelm})) { return [$$useropt{$courselevelm},'map']; }
         if (defined($$useropt{$courselevel})) { return $$useropt{$courselevel}; }          if (defined($$useropt{$courselevel})) { return [$$useropt{$courselevel},'course']; }
     }      }
   
 # ------------------------------------------------------- second, check course  # ------------------------------------------------------- second, check course
       if ($cgroup ne '' and defined($courseopt)) {
           if (defined($$courseopt{$grplevelr})) { return [$$courseopt{$grplevelr},'resource']; }
           if (defined($$courseopt{$grplevelm})) { return [$$courseopt{$grplevelm},'map']; }
           if (defined($$courseopt{$grplevel})) { return [$$courseopt{$grplevel},'course']; }
       }
   
     if ($csec and defined($courseopt)) {      if ($csec and defined($courseopt)) {
         if (defined($$courseopt{$seclevelr})) { return $$courseopt{$seclevelr}; }          if (defined($$courseopt{$seclevelr})) { return [$$courseopt{$seclevelr},'resource']; }
         if (defined($$courseopt{$seclevelm})) { return $$courseopt{$seclevelm}; }          if (defined($$courseopt{$seclevelm})) { return [$$courseopt{$seclevelm},'map']; }
         if (defined($$courseopt{$seclevel})) { return $$courseopt{$seclevel}; }          if (defined($$courseopt{$seclevel})) { return [$$courseopt{$seclevel},'course']; }
     }      }
   
     if (defined($courseopt)) {      if (defined($courseopt)) {
         if (defined($$courseopt{$courselevelr})) { return $$courseopt{$courselevelr}; }          if (defined($$courseopt{$courselevelr})) { return [$$courseopt{$courselevelr},'resource']; }
     }      }
   
 # ----------------------------------------------------- third, check map parms  # ----------------------------------------------------- third, check map parms
   
     my $thisparm=$$parmhash{$symbparm};      my $thisparm=$$parmhash{$symbparm};
     if (defined($thisparm)) { return $thisparm; }      if (defined($thisparm)) { return [$thisparm,'map']; }
   
 # ----------------------------------------------------- fourth , check default  # ----------------------------------------------------- fourth , check default
   
     my $meta_rwhat=$rwhat;      my $meta_rwhat=$rwhat;
     $meta_rwhat=~s/\./_/g;      $meta_rwhat=~s/\./_/g;
     my $default=&Apache::lonnet::metadata($fn,$meta_rwhat);      my $default=&Apache::lonnet::metadata($fn,$meta_rwhat);
     if (defined($default)) { return $default}      if (defined($default)) { return [$default,'resource']}
     $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat);      $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat);
     if (defined($default)) { return $default}      if (defined($default)) { return [$default,'resource']}
   
 # --------------------------------------------------- fifth, check more course  # --------------------------------------------------- fifth, check more course
     if (defined($courseopt)) {      if (defined($courseopt)) {
         if (defined($$courseopt{$courselevelm})) { return $$courseopt{$courselevelm}; }          if (defined($$courseopt{$courselevelm})) { return [$$courseopt{$courselevelm},'map']; }
         if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; }          if (defined($$courseopt{$courselevel})) {
              my $ret = [$$courseopt{$courselevel},'course'];
              return $ret;
          }
     }      }
   
 # --------------------------------------------------- sixth , cascade up parts  # --------------------------------------------------- sixth , cascade up parts
   
     my ($space,@qualifier)=split(/\./,$rwhat);      my ($space,@qualifier)=split(/\./,$rwhat);
Line 2571  sub parmval_real { Line 2416  sub parmval_real {
  my $id=pop(@parts);   my $id=pop(@parts);
  my $part=join('_',@parts);   my $part=join('_',@parts);
  if ($part eq '') { $part='0'; }   if ($part eq '') { $part='0'; }
  my $partgeneral=$self->parmval($part.".$qualifier",$symb,1);         my @partgeneral=$self->parmval($part.".$qualifier",$symb,1);
  if (defined($partgeneral)) { return $partgeneral; }         if (defined($partgeneral[0])) { return \@partgeneral; }
     }      }
     if ($recurse) { return undef; }      if ($recurse) { return []; }
     my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$what);      my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$rwhat);
     if (defined($pack_def)) { return $pack_def; }      if (defined($pack_def)) { return [$pack_def,'resource']; }
     return '';      return [''];
 }  }
   
 =pod  =pod
Line 2585  sub parmval_real { Line 2430  sub parmval_real {
 =item * B<getResourceByUrl>(url,multiple):  =item * B<getResourceByUrl>(url,multiple):
   
 Retrieves a resource object by URL of the resource, unless the optional  Retrieves a resource object by URL of the resource, unless the optional
 multiple parameter is included in wahich caes an array of resource   multiple parameter is included in which case an array of resource 
 objects is returned. If passed a resource object, it will simply return    objects is returned. If passed a resource object, it will simply return  
 it, so it is safe to use this method in code like  it, so it is safe to use this method in code like
 "$res = $navmap->getResourceByUrl($res)"  "$res = $navmap->getResourceByUrl($res)"
Line 2620  all matching resources. Line 2465  all matching resources.
   
 =item * B<hasResource>(map, filterFunc, recursive, showall):  =item * B<hasResource>(map, filterFunc, recursive, showall):
   
 Convience method for  Convenience method for
   
  scalar(retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0   scalar(retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0
   
Line 2698  sub retrieveResources { Line 2543  sub retrieveResources {
   
     my @resources = ();      my @resources = ();
   
       if (&$filterFunc($map)) {
    push(@resources, $map);
       }
   
     # Run down the iterator and collect the resources.      # Run down the iterator and collect the resources.
     my $curRes;      my $curRes;
   
Line 2707  sub retrieveResources { Line 2556  sub retrieveResources {
                 next;                  next;
             }              }
   
             push @resources, $curRes;              push(@resources, $curRes);
   
             if ($bailout) {              if ($bailout) {
                 return @resources;                  return @resources;
Line 2738  sub usedVersion { Line 2587  sub usedVersion {
 1;  1;
   
 package Apache::lonnavmaps::iterator;  package Apache::lonnavmaps::iterator;
 use WeakRef;  use Scalar::Util qw(weaken);
 use Apache::lonnet;  use Apache::lonnet;
   
 =pod  =pod
Line 2854  be the tokens described above. Line 2703  be the tokens described above.
   
 Also note there is some old code floating around that trys to track  Also note there is some old code floating around that trys to track
 the depth of the iterator to see when it's done; do not copy that   the depth of the iterator to see when it's done; do not copy that 
 code. It is difficult to get right and harder to understand then  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.
   
 =cut  =cut
Line 3038  sub next { Line 2887  sub next {
         $self->{HAVE_RETURNED_0} = 1;          $self->{HAVE_RETURNED_0} = 1;
         return $self->{NAV_MAP}->getById('0.0');          return $self->{NAV_MAP}->getById('0.0');
     }      }
       if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0_BEGIN_MAP}) {
    $self->{HAVE_RETURNED_0_BEGIN_MAP} = 1;
    return $self->BEGIN_MAP();
       }
   
     if ($self->{RECURSIVE_ITERATOR_FLAG}) {      if ($self->{RECURSIVE_ITERATOR_FLAG}) {
         # grab the next from the recursive iterator           # grab the next from the recursive iterator 
Line 3139  sub next { Line 2992  sub next {
     }      }
   
     # Is this the end of a branch? If so, all of the resources examined above      # Is this the end of a branch? If so, all of the resources examined above
     # led to lower levels then the one we are currently at, so we push a END_BRANCH      # led to lower levels than the one we are currently at, so we push a END_BRANCH
     # marker onto the stack so we don't forget.      # marker onto the stack so we don't forget.
     # Example: For the usual A(BC)(DE)F case, when the iterator goes down the      # Example: For the usual A(BC)(DE)F case, when the iterator goes down the
     # BC branch and gets to C, it will see F as the only next resource, but it's      # BC branch and gets to C, it will see F as the only next resource, but it's
Line 3219  sub populateStack { Line 3072  sub populateStack {
 1;  1;
   
 package Apache::lonnavmaps::DFSiterator;  package Apache::lonnavmaps::DFSiterator;
 use WeakRef;  use Scalar::Util qw(weaken);
 use Apache::lonnet;  use Apache::lonnet;
   
 # Not documented in the perldoc: This is a simple iterator that just walks  # Not documented in the perldoc: This is a simple iterator that just walks
Line 3348  sub next { Line 3201  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 3403  sub populateStack { Line 3256  sub populateStack {
 1;  1;
   
 package Apache::lonnavmaps::resource;  package Apache::lonnavmaps::resource;
 use WeakRef;  use Scalar::Util qw(weaken);
 use Apache::lonnet;  use Apache::lonnet;
   
 =pod  =pod
Line 3475  X<symb> X<resource, symb> Line 3328  X<symb> X<resource, symb>
 All resources also have B<symb>s, which uniquely identify a resource  All resources also have B<symb>s, which uniquely identify a resource
 in a course. Many internal LON-CAPA functions expect a symb. A symb  in a course. Many internal LON-CAPA functions expect a symb. A symb
 carries along with it the URL of the resource, and the map it appears  carries along with it the URL of the resource, and the map it appears
 in. Symbs are much larger then resource IDs.  in. Symbs are much larger than resource IDs.
   
 =cut  =cut
   
Line 3551  false. Line 3404  false.
   
 =item * B<randompick>:  =item * B<randompick>:
   
 Returns true for a map if the randompick feature is being used on the  Returns the number of randomly picked items for a map if the randompick
 map. (?)  feature is being used on the map. 
   
   =item * B<randomorder>:
   
   Returns true for a map if the randomorder feature is being used on the
   map.
   
 =item * B<src>:  =item * B<src>:
   
Line 3582  sub kind { my $self=shift; return $self- Line 3440  sub kind { my $self=shift; return $self-
 sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }  sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }
 sub randompick {   sub randompick { 
     my $self = shift;      my $self = shift;
     return $self->{NAV_MAP}->{PARM_HASH}->{$self->symb .      return $self->parmval('randompick');
                                                '.0.parameter_randompick'};  }
   sub randomorder { 
       my $self = shift;
       return ($self->parmval('randomorder') =~ /^yes$/i);
 }  }
 sub link {  sub link {
     my $self=shift;      my $self=shift;
Line 3641  sub condition { Line 3502  sub condition {
 }  }
 sub condval {  sub condval {
     my $self=shift;      my $self=shift;
     my $uri=&Apache::lonnet::deversion(&Apache::lonnet::declutter($self->src()));      my ($pathname,$filename) = 
     my ($pathname,$filename)=($uri=~m|(.*)/([^/]*)|);   &Apache::lonnet::split_uri_for_cond($self->src());
     $pathname=~s/^adm\/wrapper\///;      
   
     my $match=($env{'acc.res.'.$env{'request.course.id'}.'.'.$pathname}=~      my $match=($env{'acc.res.'.$env{'request.course.id'}.'.'.$pathname}=~
        /\&\Q$filename\E\:([\d\|]+)\&/);         /\&\Q$filename\E\:([\d\|]+)\&/);
Line 3662  sub compTitle { Line 3522  sub compTitle {
     }      }
     return $title;      return $title;
 }  }
   
 =pod  =pod
   
 B<Predicate Testing the Resource>  B<Predicate Testing the Resource>
Line 3702  sub retrieveResources { Line 3563  sub retrieveResources {
    return $self->{NAV_MAP}->retrieveResources(@_);     return $self->{NAV_MAP}->retrieveResources(@_);
 }  }
   
   sub is_exam {
       my ($self,$part) = @_;
       if ($self->parmval('type',$part) eq 'exam') {
           return 1;
       }
       if ($self->src() =~ /\.(exam)$/) {
           return 1;
       }
       return 0;
   }
 sub is_html {  sub is_html {
     my $self=shift;      my $self=shift;
     my $src = $self->src();      my $src = $self->src();
Line 3714  sub is_page { Line 3585  sub is_page {
     return $self->navHash("is_map_", 1) &&       return $self->navHash("is_map_", 1) && 
  $self->navHash("map_type_" . $self->map_pc()) eq 'page';   $self->navHash("map_type_" . $self->map_pc()) eq 'page';
 }  }
   sub is_practice {
       my $self=shift;
       my ($part) = @_;
       if ($self->parmval('type',$part) eq 'practice') {
           return 1;
       }
       return 0;
   }
 sub is_problem {  sub is_problem {
     my $self=shift;      my $self=shift;
     my $src = $self->src();      my $src = $self->src();
     return ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/)      if ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) {
    return !($self->is_practice());
       }
       return 0;
 }  }
 sub contains_problem {  sub contains_problem {
     my $self=shift;      my $self=shift;
Line 3727  sub contains_problem { Line 3609  sub contains_problem {
     }      }
     return 0;      return 0;
 }  }
   sub map_contains_problem {
       my $self=shift;
       if ($self->is_map()) {
    my $has_problem=
       $self->hasResource($self,sub { $_[0]->is_problem() },1);
    return $has_problem;
       }
       return 0;
   }
 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 3744  sub is_survey { Line 3634  sub is_survey {
     }      }
     return 0;      return 0;
 }  }
   sub is_task {
       my $self=shift;
       my $src = $self->src();
       return ($src =~ /\.(task)$/)
   }
   
 sub is_empty_sequence {  sub is_empty_sequence {
     my $self=shift;      my $self=shift;
Line 3799  Returns a string with the type of the ma Line 3694  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 3812  sub map_pc { Line 3707  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 3829  sub map_type { Line 3724  sub map_type {
   
 # These functions will be responsible for returning the CORRECT  # These functions will be responsible for returning the CORRECT
 # VALUE for the parameter, no matter what. So while they may look  # VALUE for the parameter, no matter what. So while they may look
 # like direct calls to parmval, they can be more then that.  # like direct calls to parmval, they can be more than that.
 # So, for instance, the duedate function should use the "duedatetype"  # So, for instance, the duedate function should use the "duedatetype"
 # information, rather then the resource object user.  # information, rather than the resource object user.
   
 =pod  =pod
   
Line 3924  sub awarded { Line 3819  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 $interval=$self->parmval("interval", $part);      my $date;
     if ($interval) {      my @interval=$self->parmval("interval", $part);
  my $first_access=&Apache::lonnet::get_first_access('map',$self->symb);      my $due_date=$self->parmval("duedate", $part);
  if ($first_access) { return ($first_access+$interval); }      if ($interval[0] =~ /\d+/) {
          my $first_access=&Apache::lonnet::get_first_access($interval[1],
                                                             $self->symb);
    if (defined($first_access)) {
              my $interval = $first_access+$interval[0];
       $date = (!$due_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) = @_;
       my @response_ids = $self->responseIds($part);
       if (@response_ids) {
    foreach my $response_id (@response_ids) {
       if (lc($self->parmval("handgrade",$part.'_'.$response_id))
    eq 'yes') {
    return 'yes';
       }
    }
       }
     return $self->parmval("handgrade", $part);      return $self->parmval("handgrade", $part);
 }  }
 sub maxtries {  sub maxtries {
Line 4028  Returns a false value if there has been Line 3944  Returns a false value if there has been
 logged in, true if there has. Always returns false if the discussion  logged in, true if there has. Always returns false if the discussion
 data was not extracted when the nav map was constructed.  data was not extracted when the nav map was constructed.
   
   =item * B<last_post_time>:
   
   Returns a false value if there hasn't been discussion otherwise returns
   unix timestamp of last time a discussion posting (or edit) was made.
   
   =item * B<discussion_info>:
   
   optional argument is a filter (currently can be 'unread');
   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
   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>:
   
 Gets the feedback for the resource and returns the raw feedback string  Gets the feedback for the resource and returns the raw feedback string
Line 4035  for the resource, or the null string if Line 3966  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 4048  sub hasDiscussion { Line 3979  sub hasDiscussion {
     return $self->{NAV_MAP}->hasDiscussion($self->symb());      return $self->{NAV_MAP}->hasDiscussion($self->symb());
 }  }
   
   sub last_post_time {
       my $self = shift;
       return $self->{NAV_MAP}->last_post_time($self->symb());
   }
   
   sub discussion_info {
       my ($self,$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();
     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 4145  sub countResponses { Line 4088  sub countResponses {
 sub responseTypes {  sub responseTypes {
     my $self = shift;      my $self = shift;
     my %responses;      my %responses;
     foreach my $part ($self->parts()) {      foreach my $part (@{$self->parts()}) {
         foreach my $responsetype ($self->responseType($part)) {          foreach my $responsetype ($self->responseType($part)) {
             $responses{$responsetype}++ if (defined($responsetype));              $responses{$responsetype}++ if (defined($responsetype));
         }          }
Line 4220  sub extractParts { Line 4163  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 4246  sub extractParts { Line 4189  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 4256  sub extractParts { Line 4199  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_(.*)/
    || $data =~ /^(Task)_(.*)/) {
                 my $responseType = $1;                  my $responseType = $1;
                 my $partStuff = $2;                  my $partStuff = $2;
                 my $partIdSoFar = '';                  my $partIdSoFar = '';
Line 4269  sub extractParts { Line 4213  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 4319  the completion information. Line 4270  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 ...
  }   }
Line 4616  sub status { Line 4567  sub status {
     #if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; }      #if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; }
     if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; }      if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; }
   
     my $suppressFeedback = $self->problemstatus($part) eq 'no';      my $suppressFeedback = 0;
       if (($self->problemstatus($part) eq 'no') ||
           ($self->problemstatus($part) eq 'no_feedback_ever')) {
           $suppressFeedback = 1;
       }
     # If there's an answer date and we're past it, don't      # If there's an answer date and we're past it, don't
     # suppress the feedback; student should know      # suppress the feedback; student should know
     if ($self->duedate($part) && $self->duedate($part) < time() &&      if ($self->duedate($part) && $self->duedate($part) < time() &&

Removed from v.1.349.2.5  
changed lines
  Added in v.1.408


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