Diff for /loncom/interface/lonnavmaps.pm between versions 1.262 and 1.414

version 1.262, 2004/06/15 15:50:03 version 1.414, 2008/09/11 17:09:48
Line 30 Line 30
 package Apache::lonnavmaps;  package Apache::lonnavmaps;
   
 use strict;  use strict;
 use Apache::Constants qw(:common :http);  use GDBM_File;
 use Apache::loncommon();  use Apache::loncommon();
 use Apache::lonmenu();  use Apache::lonenc();
 use Apache::lonlocal;  use Apache::lonlocal;
   use Apache::lonnet;
 use POSIX qw (floor strftime);  use POSIX qw (floor strftime);
 use Data::Dumper; # for debugging, not always used  use Time::HiRes qw( gettimeofday tv_interval );
   use LONCAPA;
   use DateTime();
   
 # symbolic constants  # symbolic constants
 sub SYMB { return 1; }  sub SYMB { return 1; }
Line 53  my %statusIconMap = Line 56  my %statusIconMap =
      $resObj->CLOSED       => '',       $resObj->CLOSED       => '',
      $resObj->OPEN         => 'navmap.open.gif',       $resObj->OPEN         => 'navmap.open.gif',
      $resObj->CORRECT      => 'navmap.correct.gif',       $resObj->CORRECT      => 'navmap.correct.gif',
        $resObj->PARTIALLY_CORRECT      => 'navmap.partial.gif',
      $resObj->INCORRECT    => 'navmap.wrong.gif',       $resObj->INCORRECT    => 'navmap.wrong.gif',
      $resObj->ATTEMPTED    => 'navmap.ellipsis.gif',       $resObj->ATTEMPTED    => 'navmap.ellipsis.gif',
      $resObj->ERROR        => ''       $resObj->ERROR        => ''
Line 77  my %colormap = Line 81  my %colormap =
       $resObj->OPEN                   => '',        $resObj->OPEN                   => '',
       $resObj->NOTHING_SET            => '',        $resObj->NOTHING_SET            => '',
       $resObj->ATTEMPTED              => '',        $resObj->ATTEMPTED              => '',
       $resObj->ANSWER_SUBMITTED       => ''        $resObj->ANSWER_SUBMITTED       => '',
         $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 handler {  sub close {
     my $r = shift;      if ($env{'environment.remotenavmap'} ne 'on') { return ''; }
     real_handler($r);      return(<<ENDCLOSE);
 }  <script type="text/javascript">
   window.status='Accessing Nav Control';
 sub real_handler {  menu=window.open("/adm/rat/empty.html","loncapanav",
     my $r = shift;                   "height=600,width=400,scrollbars=1");
   window.status='Closing Nav Control';
     # Handle header-only request  menu.close();
     if ($r->header_only) {  window.status='Done.';
         if ($ENV{'browser.mathml'}) {  </script>
             &Apache::loncommon::content_type($r,'text/xml');  ENDCLOSE
         } else {  }
             &Apache::loncommon::content_type($r,'text/html');  
         }  sub update {
         $r->send_http_header;      if ($env{'environment.remotenavmap'} ne 'on') { return ''; }
         return OK;      if (!$env{'request.course.id'}) { return ''; }
     }      if ($ENV{'REQUEST_URI'}=~m|^/adm/navmaps|) { return ''; }
       return(<<ENDUPDATE);
     # Send header, don't cache this page  <form name="navform"></form>
     if ($ENV{'browser.mathml'}) {  <script type="text/javascript">
         &Apache::loncommon::content_type($r,'text/xml');  this.document.navform.action='/adm/navmaps#curloc';
     } else {  this.document.navform.target='loncapanav';
         &Apache::loncommon::content_type($r,'text/html');  this.document.navform.submit();
     }  </script>
     &Apache::loncommon::no_cache($r);  ENDUPDATE
     $r->send_http_header;  
   
     # 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->print("<html><head>\n");  
     $r->print("<title>".&mt('Navigate Course Contents')."</title>");  
 # ------------------------------------------------------------ Get query string  
     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register']);  
       
 # ----------------------------------------------------- Force menu registration  
     my $addentries='';  
     if ($ENV{'form.register'}) {  
        $addentries=' onLoad="'.&Apache::lonmenu::loadevents().  
    '" onUnload="'.&Apache::lonmenu::unloadevents().'"';  
        $r->print(&Apache::lonmenu::registerurl(1));  
     }  
   
     # Header  
     $r->print('</head>'.  
               &Apache::loncommon::bodytag('Navigate Course Contents','',  
                                     $addentries,'','',$ENV{'form.register'}));  
     $r->print('<script>window.focus();</script>'.  
         &Apache::loncommon::help_open_topic('Navigation_Screen').  
  &Apache::loncommon::help_open_bug('RAT'));  
   
     $r->rflush();  
   
     # Check that it's defined  
     if (!($navmap->courseMapDefined())) {  
         $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";  
         }  
     }  
   
     my $jumpToFirstHomework = 0;  
     # Check to see if the student is jumping to next open, do-able problem  
     if ($ENV{QUERY_STRING} eq '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 {  
         $r->print("<a href='navmaps?jumpToFirstHomework'>" .  
        &mt("Go To My First Homework Problem")."</a>&nbsp;&nbsp;&nbsp;&nbsp;");  
     }  
   
     my $suppressEmptySequences = 0;  
     my $filterFunc = undef;  
     my $resource_no_folder_link = 0;  
   
     # Display only due homework.  
     my $showOnlyHomework = 0;  
     if ($ENV{QUERY_STRING} eq 'showOnlyHomework') {  
         $showOnlyHomework = 1;  
         $suppressEmptySequences = 1;  
         $filterFunc = sub { my $res = shift;   
                             return $res->completable() || $res->is_map();  
                         };  
         $r->print("<p><font size='+2'>".&mt("Uncompleted Homework")."</font></p>");  
         $ENV{'form.filter'} = '';  
         $ENV{'form.condition'} = 1;  
  $resource_no_folder_link = 1;  
     } else {  
         $r->print("<a href='navmaps?showOnlyHomework'>" .  
        &mt("Show Only Uncompleted Homework")."</a>&nbsp;&nbsp;&nbsp;&nbsp;");  
     }  
   
     # renderer call  
     my $renderArgs = { 'cols' => [0,1,2,3],  
                        'url' => '/adm/navmaps',  
                        'navmap' => $navmap,  
                        'suppressNavmap' => 1,  
                        'suppressEmptySequences' => $suppressEmptySequences,  
                        'filterFunc' => $filterFunc,  
        'resource_no_folder_link' => $resource_no_folder_link,  
                        'r' => $r};  
     my $render = render($renderArgs);  
     $navmap->untieHashes();  
   
     # 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>");  
         }  
     }  
   
     $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
Line 285  sub removeFromFilter { Line 139  sub removeFromFilter {
   
 # Convenience function: Given a stack returned from getStack on the iterator,  # Convenience function: Given a stack returned from getStack on the iterator,
 # return the correct src() value.  # return the correct src() value.
 # Later, this should add an anchor when we start putting anchors in pages.  
 sub getLinkForResource {  sub getLinkForResource {
     my $stack = shift;      my $stack = shift;
     my $res;      my $res;
Line 293  sub getLinkForResource { Line 146  sub getLinkForResource {
     # Check to see if there are any pages in the stack      # Check to see if there are any pages in the stack
     foreach $res (@$stack) {      foreach $res (@$stack) {
         if (defined($res)) {          if (defined($res)) {
       my $anchor;
     if ($res->is_page()) {      if ($res->is_page()) {
  return $res->src();   foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; }  }
    $anchor=&escape($anchor->shown_symb());
    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$/) {
  return &Apache::lonnet::clutter($map).'#'.   my $url=&Apache::lonnet::clutter($map);
     &Apache::lonnet::escape(&Apache::lonnet::declutter($src));   $anchor=&escape($src->shown_symb());
    return ($url,$res->shown_symb(),$anchor);
     }      }
         }          }
     }      }
Line 309  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->src();      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 327  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 334  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));          return &mt("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));      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));          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));   if ($res->is_practice()) {
       return &mt("Closed")." " . timeToHumanString($due,'start');
    } else {
       return &mt("Was due")." " . timeToHumanString($due,'end');
    }
     }      }
     if ($status == $res->ANSWER_OPEN) {      if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT)
    && $res->handgrade($part) ne 'yes') {
         return &mt("Answer available");          return &mt("Answer available");
     }      }
     if ($status == $res->EXCUSED) {      if ($status == $res->EXCUSED) {
Line 368  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)) .              return &mt("Due")." " . timeToHumanString($due,'end') .
                 " $triesString";                  " $triesString";
         } else {          } else {
             return &mt("No due date")." $triesString";              return &mt("No due date")." $triesString";
Line 380  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 395  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 407  sub lastTry { Line 280  sub lastTry {
         $res->duedate($part) > time();          $res->duedate($part) > time();
 }  }
   
 # This puts a human-readable name on the ENV variable.  # This puts a human-readable name on the env variable.
   
 sub advancedUser {  sub advancedUser {
     return $ENV{'request.role.adv'};      return $env{'request.role.adv'};
 }  }
   
   
Line 422  sub advancedUser { Line 295  sub advancedUser {
 # print "Answer available $timestring"  # print "Answer available $timestring"
 # Very, very, very, VERY English-only... goodness help a localizer on  # Very, very, very, VERY English-only... goodness help a localizer on
 # this func...  # this func...
   
   
 sub timeToHumanString {  sub timeToHumanString {
     my ($time) = @_;      my ($time,$type,$format) = @_;
   
     # zero, '0' and blank are bad times      # zero, '0' and blank are bad times
     if (!$time) {      if (!$time) {
         return &mt('never');          return &mt('never');
Line 433  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 461  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 475  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 494  sub timeToHumanString { Line 367  sub timeToHumanString {
             return "$prefix$hourString$minuteString$tense";              return "$prefix$hourString$minuteString$tense";
         }          }
   
         # Less then 5 days away, display day of the week and   my $dt = DateTime->from_epoch(epoch => $time)
                    ->set_time_zone(&Apache::lonlocal::gettimezone());
   
    # If there's a caller supplied format, use it.
   
    if ($format ne '') {
       my $timeStr = $dt->strftime($format);
       return $timeStr.' ('.$dt->time_zone_short_name().')';
    }
   
           # 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 " : "next ") .              return ($inPast ? "last " : "this ") .
                 $timeStr;                  $timeStr;
         }          }
                   
    my $conjunction='on';
    if ($type eq 'start') {
       $conjunction='at';
    } elsif ($type eq 'end') {
       $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("on %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;              return $timeStr;
         }          }
   
         # Not this year, so show the year          # Not this year, so show the year
         my $timeStr = strftime("on %A, %b %e %G 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;          return $timeStr;
Line 552  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 598  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 713  automatically. Line 606  automatically.
   
 =over 4  =over 4
   
 =item * B<iterator>: default: constructs one from %ENV  =item * B<iterator>: default: constructs one from %env
   
 A reference to a fresh ::iterator to use from the navmaps. The  A reference to a fresh ::iterator to use from the navmaps. The
 rendering will reflect the options passed to the iterator, so you can  rendering will reflect the options passed to the iterator, so you can
 use that to just render a certain part of the course, if you like. If  use that to just render a certain part of the course, if you like. If
 one is not passed, the renderer will attempt to construct one from  one is not passed, the renderer will attempt to construct one from
 ENV{'form.filter'} and ENV{'form.condition'} information, plus the  env{'form.filter'} and env{'form.condition'} information, plus the
 'iterator_map' parameter if any.  'iterator_map' parameter if any.
   
 =item * B<iterator_map>: default: not used  =item * B<iterator_map>: default: not used
Line 729  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<navmap>: default: constructs one from %ENV  =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
   
 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
 this is necessary to make an iterator but it is not passed in, a new  this is necessary to make an iterator but it is not passed in, a new
 one will be constructed based on ENV info. This is useful to do basic  one will be constructed based on env info. This is useful to do basic
 error checking before passing it off to render.  error checking before passing it off to render.
   
 =item * B<r>: default: must be passed in  =item * B<r>: default: must be passed in
Line 759  then only one line will be displayed for Line 657  then only one line will be displayed for
 all parts will always be displayed. If showParts is 0, this is  all parts will always be displayed. If showParts is 0, this is
 ignored.  ignored.
   
 =item * B<jumpCount>: default: determined from %ENV  =item * B<jumpCount>: default: determined from %env
   
 A string identifying the URL to place the anchor 'curloc' at.  A string identifying the URL to place the anchor 'curloc' at.
 It is the responsibility of the renderer user to  It is the responsibility of the renderer user to
 ensure that the #curloc is in the URL. By default, determined through  ensure that the #curloc is in the URL. By default, determined through
 the use of the ENV{} 'jump' information, and should normally "just  the use of the env{} 'jump' information, and should normally "just
 work" correctly.  work" correctly.
   
 =item * B<here>: default: empty string  =item * B<here>: default: empty string
Line 792  are allowing the user to open and close Line 690  are allowing the user to open and close
   
 Describes the currently-open row number to cause the browser to jump  Describes the currently-open row number to cause the browser to jump
 to, because the user just opened that folder. By default, pulled from  to, because the user just opened that folder. By default, pulled from
 the Jump information in the ENV{'form.*'}.  the Jump information in the env{'form.*'}.
   
 =item * B<printKey>: default: false  =item * B<printKey>: default: false
   
Line 861  sub render_resource { Line 759  sub render_resource {
     my $nonLinkedText = ''; # stuff after resource title not in link      my $nonLinkedText = ''; # stuff after resource title not in link
   
     my $link = $params->{"resourceLink"};      my $link = $params->{"resourceLink"};
   
       #  The URL part is not escaped at this point, but the symb is... 
       #  The stuff to the left of the ? must have ' replaced by \' since
       #  it will be quoted with ' in the href.
   
       my ($left,$right) = split(/\?/, $link);
       $link = $left.'?'.$right;
   
     my $src = $resource->src();      my $src = $resource->src();
     my $it = $params->{"iterator"};      my $it = $params->{"iterator"};
     my $filter = $it->{FILTER};      my $filter = $it->{FILTER};
Line 869  sub render_resource { Line 775  sub render_resource {
   
     my $partLabel = "";      my $partLabel = "";
     my $newBranchText = "";      my $newBranchText = "";
           my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons");
     # If this is a new branch, label it so      # If this is a new branch, label it so
     if ($params->{'isNewBranch'}) {      if ($params->{'isNewBranch'}) {
         $newBranchText = "<img src='/adm/lonIcons/branch.gif' border='0' />";          $newBranchText = "<img src='$location/branch.gif' border='0' alt='Branch' />";
     }      }
   
     # links to open and close the folder      # links to open and close the folder
     my $linkopen = "<a href='$link'>";  
       
       my $linkopen = "<a href=\"$link\">";
   
   
     my $linkclose = "</a>";      my $linkclose = "</a>";
   
     # Default icon: unknown page      # Default icon: unknown page
     my $icon = "<img src='/adm/lonIcons/unknown.gif' alt='' border='0' />";      my $icon = "<img src='$location/unknown.gif' alt='' border='0' alt='&nbsp;&nbsp;' ' />";
           
     if ($resource->is_problem()) {      if ($resource->is_problem()) {
         if ($part eq '0' || $params->{'condensed'}) {          if ($part eq '0' || $params->{'condensed'}) {
             $icon = '<img src="/adm/lonIcons/problem.gif" alt="" 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'};
         }          }
     } else {      } else {
  $icon = "<img src='".&Apache::loncommon::icon($resource->src).   $icon = "<img src='".&Apache::loncommon::icon($resource->src)."' alt='&nbsp;&nbsp;' border='0' />";
     "' alt='' border='0' />";  
     }      }
   
     # Display the correct map icon to open or shut map      # Display the correct map icon to open or shut map
Line 902  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='/adm/lonIcons/$icon' alt='' border='0' />";      $icon = "<img src='$location/$icon' alt=\"".
    ($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='/adm/lonIcons/$icon' alt='' border='0' />";              $icon = "<img src='$location/$icon' alt=\"".
    ($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" border='0' />";
   
             $linkopen = "";              $linkopen = "";
             $linkclose = "";              $linkclose = "";
Line 930  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()) {
           $nonLinkedText .= ' <i>('.&mt('conditionally hidden').')</i> ';
       }
       if (($resource->is_practice()) && ($resource->is_raw_problem())) {
           $nonLinkedText .=' <font color="green"><b>'.&mt('not graded').'</b></font>';
       }
    
     # 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 945  sub render_resource { Line 870  sub render_resource {
     }      }
   
     # Decide what to display      # Decide what to display
   
     $result .= "$newBranchText$linkopen$icon$linkclose";      $result .= "$newBranchText$linkopen$icon$linkclose";
           
     my $curMarkerBegin = '';      my $curMarkerBegin = '';
Line 953  sub render_resource { Line 879  sub render_resource {
     # Is this the current resource?      # Is this the current resource?
     if (!$params->{'displayedHereMarker'} &&       if (!$params->{'displayedHereMarker'} && 
         $resource->symb() eq $params->{'here'} ) {          $resource->symb() eq $params->{'here'} ) {
         $curMarkerBegin = '<font color="red" size="+2">&gt; </font>';          $curMarkerBegin = '<font color="red" size="+2">&gt;</font>';
         $curMarkerEnd = '<font color="red" size="+2">&lt;</font>';          $curMarkerEnd = '<font color="red" size="+2">&lt;</font>';
         $params->{'displayedHereMarker'} = 1;          $params->{'displayedHereMarker'} = 1;
     }      }
   
     if ($resource->is_problem() && $part ne '0' &&       if ($resource->is_problem() && $part ne '0' && 
         !$params->{'condensed'}) {          !$params->{'condensed'}) {
  my $displaypart=&Apache::lonnet::EXT('resource.'.$part.'.display',   my $displaypart=$resource->part_display($part);
      $resource->symb());          $partLabel = " (".&mt('Part: [_1]', $displaypart).")";
  unless ($displaypart) { $displaypart=$part; }   if ($link!~/\#/) { $link.='#'.&escape($part); }
         $partLabel = " (Part: $displaypart)";  
  $link.='#'.&Apache::lonnet::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()).')';
     }      }
   
     if (!$params->{'resource_nolink'} && !$resource->is_sequence()) {      my $target;
         $result .= "  $curMarkerBegin<a href='$link'>$title$partLabel</a>$curMarkerEnd $nonLinkedText</td>";      if ($env{'environment.remotenavmap'} eq 'on') {
    $target=' target="loncapaclient" ';
       }
       if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) {
           $result .= "  $curMarkerBegin<a $target href=\"$link\">$title$partLabel</a>$curMarkerEnd $nonLinkedText</td>";
     } else {      } else {
         $result .= "  $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText</td>";          $result .= "  $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText</td>";
     }      }
Line 986  sub render_communication_status { Line 914  sub render_communication_status {
     my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = "";      my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = "";
   
     my $link = $params->{"resourceLink"};      my $link = $params->{"resourceLink"};
     my $linkopen = "<a href='$link'>";      my $target;
       if ($env{'environment.remotenavmap'} eq 'on') {
    $target=' target="loncapaclient" ';
       }
       my $linkopen = "<a $target href=\"$link\">";
     my $linkclose = "</a>";      my $linkclose = "</a>";
       my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");
     if ($resource->hasDiscussion()) {      if ($resource->hasDiscussion()) {
         $discussionHTML = $linkopen .          $discussionHTML = $linkopen .
             '<img border="0" src="/adm/lonMisc/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 href="/adm/email?display='                  $feedbackHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='
                     . &Apache::lonnet::escape($_) . '">'                      . &escape($msgid) . '">'
                     . '<img src="/adm/lonMisc/feedback.gif" '                      . '<img alt="'.&mt('New Email').'" src="'.$location.'/feedback.gif" '
                     . 'border="0" /></a>';                      . 'border="0" /></a>';
             }              }
         }          }
Line 1010  sub render_communication_status { Line 942  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 href="/adm/email?display='                  $errorHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='
                     . &Apache::lonnet::escape($_) . '">'                      . &escape($msgid) . '">'
                     . '<img src="/adm/lonMisc/bomb.gif" '                      . '<img alt="'.&mt('New Error').'" src="'.$location.'/bomb.gif" '
                     . 'border="0" /></a>';                      . 'border="0" /></a>';
             }              }
         }          }
Line 1026  sub render_communication_status { Line 958  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 1036  sub render_quick_status { Line 968  sub render_quick_status {
         $params->{'multipart'} && $part eq "0";          $params->{'multipart'} && $part eq "0";
   
     my $link = $params->{"resourceLink"};      my $link = $params->{"resourceLink"};
     my $linkopen = "<a href='$link'>";      my $target;
       if ($env{'environment.remotenavmap'} eq 'on') {
    $target=' target="loncapaclient" ';
       }
       my $linkopen = "<a $target href=\"$link\">";
     my $linkclose = "</a>";      my $linkclose = "</a>";
   
     if ($resource->is_problem() &&      if ($resource->is_problem() &&
Line 1045  sub render_quick_status { Line 981  sub render_quick_status {
         my $icon = $statusIconMap{$resource->simpleStatus($part)};          my $icon = $statusIconMap{$resource->simpleStatus($part)};
         my $alt = $iconAltTags{$icon};          my $alt = $iconAltTags{$icon};
         if ($icon) {          if ($icon) {
             $result .= "<td width='30' valign='center' width='50' align='right'>$linkopen<img width='25' height='25' src='/adm/lonIcons/$icon' border='0' alt='$alt' />$linkclose</td>\n";      my $location=
    &Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon");
               $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 1057  sub render_quick_status { Line 995  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 1072  sub render_long_status { Line 1010  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 1114  my %statusStrings = Line 1055  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 1176  sub setDefault { Line 1116  sub setDefault {
     return $val;      return $val;
 }  }
   
   sub cmp_title {
       my ($atitle,$btitle) = (lc($_[0]->compTitle),lc($_[1]->compTitle));
       $atitle=~s/^\s*//;
       $btitle=~s/^\s*//;
       return $atitle cmp $btitle;
   }
   
 sub render {  sub render {
     my $args = shift;      my $args = shift;
     &Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});      &Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});
     my $result = '';      my $result = '';
   
     # Configure the renderer.      # Configure the renderer.
     my $cols = $args->{'cols'};      my $cols = $args->{'cols'};
     if (!defined($cols)) {      if (!defined($cols)) {
         # no columns, no nav maps.          # no columns, no nav maps.
         return '';          return '';
     }      }
     my $mustCloseNavMap = 0;  
     my $navmap;      my $navmap;
     if (defined($args->{'navmap'})) {      if (defined($args->{'navmap'})) {
         $navmap = $args->{'navmap'};          $navmap = $args->{'navmap'};
Line 1208  sub render { Line 1153  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 1228  sub render { Line 1173  sub render {
     }      }
   
     my $condition = 0;      my $condition = 0;
     if ($ENV{'form.condition'}) {      if ($env{'form.condition'}) {
         $condition = 1;          $condition = 1;
     }      }
   
     if (!$ENV{'form.folderManip'} && !defined($args->{'iterator'})) {      if (!$env{'form.folderManip'} && !defined($args->{'iterator'})) {
         # Step 1: Check to see if we have a navmap          # Step 1: Check to see if we have a navmap
         if (!defined($navmap)) {          if (!defined($navmap)) {
             $navmap = Apache::lonnavmaps::navmap->new();              $navmap = Apache::lonnavmaps::navmap->new();
             $mustCloseNavMap = 1;      if (!defined($navmap)) {
         }   # no londer in course
    return '<span class="LC_error">'.&mt('No course selected').'</span><br />
                           <a href="/adm/roles">'.&mt('Select a course').'</a><br />';
       }
    }
   
         # Step two: Locate what kind of here marker is necessary          # Step two: Locate what kind of here marker is necessary
         # Determine where the "here" marker is and where the screen jumps to.          # Determine where the "here" marker is and where the screen jumps to.
   
         if ($ENV{'form.postsymb'}) {          if ($env{'form.postsymb'} ne '') {
             $here = $jump = $ENV{'form.postsymb'};              $here = $jump = &Apache::lonnet::symbclean($env{'form.postsymb'});
         } elsif ($ENV{'form.postdata'}) {          } elsif ($env{'form.postdata'} ne '') {
             # couldn't find a symb, is there a URL?              # couldn't find a symb, is there a URL?
             my $currenturl = $ENV{'form.postdata'};              my $currenturl = $env{'form.postdata'};
             #$currenturl=~s/^http\:\/\///;              #$currenturl=~s/^http\:\/\///;
             #$currenturl=~s/^[^\/]+//;              #$currenturl=~s/^[^\/]+//;
                           
             $here = $jump = &Apache::lonnet::symbread($currenturl);              $here = $jump = &Apache::lonnet::symbread($currenturl);
         }   }
    if ($here eq '') {
       my $last;
       if (tie(my %hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db',
                       &GDBM_READER(),0640)) {
    $last=$hash{'last_known'};
    untie(%hash);
       }
       if ($last) { $here = $jump = $last; }
    }
   
         # Step three: Ensure the folders are open          # Step three: Ensure the folders are open
         my $mapIterator = $navmap->getIterator(undef, undef, undef, 1);          my $mapIterator = $navmap->getIterator(undef, undef, undef, 1);
Line 1261  sub render { Line 1219  sub render {
         # We only need to do this if we need to open the maps to show the          # We only need to do this if we need to open the maps to show the
         # current position. This will change the counter so we can't count          # current position. This will change the counter so we can't count
         # for the jump marker with this loop.          # for the jump marker with this loop.
         while (($curRes = $mapIterator->next()) && !$found) {          while ($here && ($curRes = $mapIterator->next()) && !$found) {
             if (ref($curRes) && $curRes->symb() eq $here) {              if (ref($curRes) && $curRes->symb() eq $here) {
                 my $mapStack = $mapIterator->getStack();                  my $mapStack = $mapIterator->getStack();
                                   
Line 1278  sub render { Line 1236  sub render {
         }                      }            
     }              }        
   
     if ( !defined($args->{'iterator'}) && $ENV{'form.folderManip'} ) { # we came from a user's manipulation of the nav page      if ( !defined($args->{'iterator'}) && $env{'form.folderManip'} ) { # we came from a user's manipulation of the nav page
         # If this is a click on a folder or something, we want to preserve the "here"          # If this is a click on a folder or something, we want to preserve the "here"
         # from the querystring, and get the new "jump" marker          # from the querystring, and get the new "jump" marker
         $here = $ENV{'form.here'};          $here = $env{'form.here'};
         $jump = $ENV{'form.jump'};          $jump = $env{'form.jump'};
     }       } 
           
     my $it = $args->{'iterator'};      my $it = $args->{'iterator'};
     if (!defined($it)) {      if (!defined($it)) {
         # Construct a default iterator based on $ENV{'form.'} information          # Construct a default iterator based on $env{'form.'} information
                   
         # Step 1: Check to see if we have a navmap          # Step 1: Check to see if we have a navmap
         if (!defined($navmap)) {          if (!defined($navmap)) {
             $navmap = Apache::lonnavmaps::navmap->new();              $navmap = Apache::lonnavmaps::navmap->new();
             $mustCloseNavMap = 1;  
         }          }
   
         # See if we're being passed a specific map          # See if we're being passed a specific map
Line 1304  sub render { Line 1261  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 1337  sub render { Line 1294  sub render {
     my $printKey = $args->{'printKey'};      my $printKey = $args->{'printKey'};
     my $printCloseAll = $args->{'printCloseAll'};      my $printCloseAll = $args->{'printCloseAll'};
     if (!defined($printCloseAll)) { $printCloseAll = 1; }      if (!defined($printCloseAll)) { $printCloseAll = 1; }
       
     # 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");
         if ($navmap->{LAST_CHECK}) {          if ($navmap->{LAST_CHECK}) {
             $result .=               $result .= 
                 '<img src="/adm/lonMisc/chat.gif"> '.&mt('New discussion since').' '.                  '<img src="'.$location.'/chat.gif"> '.&mt('New discussion since').' '.
                 strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})).                  strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})).
                 '</td><td align="center" valign="bottom">&nbsp;&nbsp;'.                  '</td><td align="center" valign="bottom">&nbsp;&nbsp;'.
                 '<img src="/adm/lonMisc/feedback.gif"> '.&mt('New message (click to open)').'<p>'.                  '<img src="'.$location.'/feedback.gif"> '.&mt('New message (click to open)').'<p>'.
                 '</td>';                   '</td>'; 
         } else {          } else {
             $result .= '<td align="center" valign="bottom">&nbsp;&nbsp;'.              $result .= '<td align="center" valign="bottom">&nbsp;&nbsp;'.
                 '<img src="/adm/lonMisc/chat.gif"> '.&mt('Discussions').'</td><td align="center" valign="bottom">'.                  '<img src="'.$location.'/chat.gif"> '.&mt('Discussions').'</td><td align="center" valign="bottom">'.
                 '&nbsp;&nbsp;<img src="/adm/lonMisc/feedback.gif"> '.&mt('New message (click to open)').                  '&nbsp;&nbsp;<img src="'.$location.'/feedback.gif"> '.&mt('New message (click to open)').
                 '</td>';                   '</td>'; 
         }          }
   
Line 1361  sub render { Line 1318  sub render {
     }      }
   
     if ($printCloseAll && !$args->{'resource_no_folder_link'}) {      if ($printCloseAll && !$args->{'resource_no_folder_link'}) {
    my ($link,$text);
         if ($condition) {          if ($condition) {
             $result.="<a href=\"navmaps?condition=0&filter=&$queryString" .      $link='"navmaps?condition=0&amp;filter=&amp;'.$queryString.
                 "&here=" . Apache::lonnet::escape($here) .   '&here='.&escape($here).'"';
                 "\">".&mt('Close All Folders')."</a>";      $text='Close all folders';
           } else {
       $link='"navmaps?condition=1&amp;filter=&amp;'.$queryString.
    '&here='.&escape($here).'"';
       $text='Open all folders';
           }
    if ($args->{'caller'} eq 'navmapsdisplay') {
       &add_linkitem($args->{'linkitems'},'changefolder',
     'location.href='.$link,$text);
    } else {
       $result.='<a href='.$link.'>'.&mt($text).'</a>';
    }
           $result .= "\n";
       }
   
       # Check for any unread discussions in all resources.
       if ($args->{'caller'} eq 'navmapsdisplay') {
    &add_linkitem($args->{'linkitems'},'clearbubbles',
         'document.clearbubbles.submit()',
         'Mark all posts read');
    my $time=time;
    $result .= (<<END);
       <form name="clearbubbles" method="post" action="/adm/feedback">
    <input type="hidden" name="navurl" value="$ENV{'QUERY_STRING'}" />
    <input type="hidden" name="navtime" value="$time" />
   END
           if ($args->{'sort'} eq 'discussion') { 
       my $totdisc = 0;
       my $haveDisc = '';
       my @allres=$navmap->retrieveResources();
       foreach my $resource (@allres) {
    if ($resource->hasDiscussion()) {
       $haveDisc .= $resource->wrap_symb().':';
       $totdisc ++;
    }
       }
       if ($totdisc > 0) {
    $haveDisc =~ s/:$//;
    $result .= (<<END);
    <input type="hidden" name="navmaps" value="$haveDisc" />
       </form>
   END
               }
    }
    $result.='</form>';
       }
   
       if ($args->{'caller'} eq 'navmapsdisplay') {
           $result .= '<table><tr><td>'.
                      &Apache::loncommon::help_open_menu('Navigation Screen','Navigation_Screen',undef,'RAT').'</td>';
    if ($env{'environment.remotenavmap'} ne 'on') {
       $result .= '<td>&nbsp;</td>'; 
         } else {          } else {
             $result.="<a href=\"navmaps?condition=1&filter=&$queryString" .      $result .= '</tr><tr>'; 
                 "&here=" . Apache::lonnet::escape($here) .   
                 "\">".&mt('Open All Folders')."</a>";  
         }          }
         $result .= "<br /><br />\n";   $result.=&show_linkitems($args->{'linkitems'});
     }              if ($args->{'sort_html'}) {
       if ($env{'environment.remotenavmap'} ne 'on') {
    $result.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>'.
       '<td align="right">'.$args->{'sort_html'}.'</td></tr>';
       } else {
    $result.='</tr><tr><td align="left"><br />'.
       $args->{'sort_html'}.'</td></tr>';
       }
    }
           $result .= '</table>';
       } elsif ($args->{'sort_html'}) { 
           $result.=$args->{'sort_html'}; 
       }
   
       $result .= "<br />\n";
     if ($r) {      if ($r) {
         $r->print($result);          $r->print($result);
         $r->rflush();          $r->rflush();
Line 1394  sub render { Line 1414  sub render {
     $args->{'indentLevel'} = 0;      $args->{'indentLevel'} = 0;
     $args->{'isNewBranch'} = 0;      $args->{'isNewBranch'} = 0;
     $args->{'condensed'} = 0;          $args->{'condensed'} = 0;    
     $args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='/adm/lonIcons/whitespace1.gif' width='25' height='1' alt='' border='0' />");      my $location=
    &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace1.gif");
       $args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='$location' width='25' height='1' alt='&nbsp;&nbsp;' border='0' />");
     $args->{'displayedHereMarker'} = 0;      $args->{'displayedHereMarker'} = 0;
   
     # If we're suppressing empty sequences, look for them here. Use DFS for speed,      # If we're suppressing empty sequences, look for them here. Use DFS for speed,
Line 1444  sub render { Line 1466  sub render {
     $args->{'here'} = $here;      $args->{'here'} = $here;
   
     $args->{'indentLevel'} = -1; # first BEGIN_MAP takes this to 0      $args->{'indentLevel'} = -1; # first BEGIN_MAP takes this to 0
     while ($curRes = $it->next($closeAllPages)) {      my @resources;
       my $code='';# sub { !(shift->is_map();) };
       if ($args->{'sort'} eq 'title') {
           my $oldFilterFunc = $filterFunc;
    my $filterFunc= 
       sub {
    my ($res)=@_;
    if ($res->is_map()) { return 0;}
    return &$oldFilterFunc($res);
       };
    @resources=$navmap->retrieveResources(undef,$filterFunc);
    @resources= sort { &cmp_title($a,$b) } @resources;
       } elsif ($args->{'sort'} eq 'duedate') {
    my $oldFilterFunc = $filterFunc;
    my $filterFunc= 
       sub {
    my ($res)=@_;
    if (!$res->is_problem()) { return 0;}
    return &$oldFilterFunc($res);
       };
    @resources=$navmap->retrieveResources(undef,$filterFunc);
    @resources= sort {
       if ($a->duedate ne $b->duedate) {
           return $a->duedate cmp $b->duedate;
       }
       my $value=&cmp_title($a,$b);
       return $value;
    } @resources;
       } elsif ($args->{'sort'} eq 'discussion') {
    my $oldFilterFunc = $filterFunc;
    my $filterFunc= 
       sub {
    my ($res)=@_;
    if (!$res->hasDiscussion() &&
       !$res->getFeedback() &&
       !$res->getErrors()) { return 0;}
    return &$oldFilterFunc($res);
       };
    @resources=$navmap->retrieveResources(undef,$filterFunc);
    @resources= sort { &cmp_title($a,$b) } @resources;
       } else {
    #unknow sort mechanism or default
    undef($args->{'sort'});
       }
   
   
       while (1) {
    if ($args->{'sort'}) {
       $curRes = shift(@resources);
    } else {
       $curRes = $it->next($closeAllPages);
    }
    if (!$curRes) { last; }
   
         # Maintain indentation level.          # Maintain indentation level.
         if ($curRes == $it->BEGIN_MAP() ||          if ($curRes == $it->BEGIN_MAP() ||
             $curRes == $it->BEGIN_BRANCH() ) {              $curRes == $it->BEGIN_BRANCH() ) {
Line 1494  sub render { Line 1569  sub render {
             $args->{'multipart'} = $curRes->multipart();              $args->{'multipart'} = $curRes->multipart();
                           
             if ($condenseParts) { # do the condensation              if ($condenseParts) { # do the condensation
                 if (!$curRes->opendate("0")) {  
                     @parts = ();  
                     $args->{'condensed'} = 1;  
                 }  
                 if (!$args->{'condensed'}) {                  if (!$args->{'condensed'}) {
                     # Decide whether to condense based on similarity                      # Decide whether to condense based on similarity
                     my $status = $curRes->status($parts[0]);                      my $status = $curRes->status($parts[0]);
Line 1547  sub render { Line 1618  sub render {
             # Add part 0 so we display it correctly.              # Add part 0 so we display it correctly.
             unshift @parts, '0';              unshift @parts, '0';
         }          }
   
    {
       my ($src,$symb,$anchor,$stack);
       if ($args->{'sort'}) {
    my $it = $navmap->getIterator(undef, undef, undef, 1);
    while ( my $res=$it->next()) {
       if (ref($res) &&
    $res->symb() eq  $curRes->symb()) { last; }
    }
    $stack=$it->getStack();
       } else {
    $stack=$it->getStack();
       }
       ($src,$symb,$anchor)=getLinkForResource($stack);
       if (defined($anchor)) { $anchor='#'.$anchor; }
       my $srcHasQuestion = $src =~ /\?/;
       $args->{"resourceLink"} = $src.
    ($srcHasQuestion?'&':'?') .
    '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.
         foreach my $part (@parts) {          foreach my $part (@parts) {
Line 1558  sub render { Line 1648  sub render {
   
             # Set up some data about the parts that the cols might want              # Set up some data about the parts that the cols might want
             my $filter = $it->{FILTER};              my $filter = $it->{FILTER};
             my $stack = $it->getStack();  
             my $src = getLinkForResource($stack);  
             my $anchor='';  
             if ($src=~s/(\#.*$)//) {  
  $anchor=$1;  
     }  
             my $srcHasQuestion = $src =~ /\?/;  
             $args->{"resourceLink"} = $src.  
                 ($srcHasQuestion?'&':'?') .  
                 'symb=' . &Apache::lonnet::escape($curRes->symb()).  
  $anchor;  
               
             # Now, display each column.              # Now, display each column.
             foreach my $col (@$cols) {              foreach my $col (@$cols) {
                 my $colHTML = '';                  my $colHTML = '';
Line 1635  if (location.href.indexOf('#curloc')==-1 Line 1714  if (location.href.indexOf('#curloc')==-1
         $r->rflush();          $r->rflush();
     }      }
                   
     if ($mustCloseNavMap) { $navmap->untieHashes(); }       return $result;
   }
   
   sub add_linkitem {
       my ($linkitems,$name,$cmd,$text)=@_;
       $$linkitems{$name}{'cmd'}=$cmd;
       $$linkitems{$name}{'text'}=&mt($text);
   }
   
   sub show_linkitems {
       my ($linkitems)=@_;
       my @linkorder = ("blank","launchnav","closenav","firsthomework",
        "everything","uncompleted","changefolder","clearbubbles");
       
       my $result .= (<<ENDBLOCK);
                 <td align="left">
   <script type="text/javascript">
       function changeNavDisplay () {
    var navchoice = document.linkitems.toplink[document.linkitems.toplink.selectedIndex].value;
   ENDBLOCK
       foreach my $link (@linkorder) {
    $result.= "if (navchoice == '$link') {".
       $linkitems->{$link}{'cmd'}."}\n";
       }
       $result.='}
                 </script>
                      <form name="linkitems" method="post">
                          <nobr><select name="toplink">'."\n";
       foreach my $link (@linkorder) {
    if (defined($linkitems->{$link})) {
       if ($linkitems->{$link}{'text'} ne '') {
    $result .= ' <option value="'.$link.'">'.
       $linkitems->{$link}{'text'}."</option>\n";
       }
    }
       }
       $result .= '</select>&nbsp;<input type="button" name="chgnav"
                      value="Go" onClick="javascript:changeNavDisplay()" />
                   </nobr></form></td>'."\n";
   
     return $result;      return $result;
 }  }
   
Line 1659  In order of increasing complexity and po Line 1776  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 1697  successful, or B<undef> if not. Line 1815  successful, or B<undef> if not.
   
 =back  =back
   
 When you are done with the $navmap object, you I<must> call   
 $navmap->untieHashes(), or you'll prevent the current user from using that   
 course until the web server is restarted. (!)  
   
 =head2 Methods  =head2 Methods
   
 =over 4  =over 4
Line 1713  See iterator documentation below. Line 1827  See iterator documentation below.
   
 use strict;  use strict;
 use GDBM_File;  use GDBM_File;
   use Apache::lonnet;
   use LONCAPA;
   
 sub new {  sub new {
     # magic invocation to create a class instance      # magic invocation to create a class instance
Line 1732  sub new { Line 1848  sub new {
   
     my %navmaphash;      my %navmaphash;
     my %parmhash;      my %parmhash;
     my $courseFn = $ENV{"request.course.fn"};      my $courseFn = $env{"request.course.fn"};
     if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db",      if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db",
               &GDBM_READER(), 0640))) {                &GDBM_READER(), 0640))) {
         return undef;          return undef;
Line 1758  sub generate_course_user_opt { Line 1874  sub generate_course_user_opt {
     my $self = shift;      my $self = shift;
     if ($self->{COURSE_USER_OPT_GENERATED}) { return; }      if ($self->{COURSE_USER_OPT_GENERATED}) { return; }
   
     my $uname=$ENV{'user.name'};      my $uname=$env{'user.name'};
     my $udom=$ENV{'user.domain'};      my $udom=$env{'user.domain'};
     my $uhome=$ENV{'user.home'};      my $cid=$env{'request.course.id'};
     my $cid=$ENV{'request.course.id'};      my $cdom=$env{'course.'.$cid.'.domain'};
     my $chome=$ENV{'course.'.$cid.'.home'};      my $cnum=$env{'course.'.$cid.'.num'};
     my ($cdom,$cnum)=split(/\_/,$cid);  
       
     my $userprefix=$uname.'_'.$udom.'_';  
           
     my %courserdatas; my %useropt; my %courseopt; my %userrdatas;  
     unless ($uhome eq 'no_host') {   
 # ------------------------------------------------- Get coursedata (if present)  # ------------------------------------------------- Get coursedata (if present)
  unless ((time-$courserdatas{$cid.'.last_cache'})<240) {      my $courseopt=&Apache::lonnet::get_courseresdata($cnum,$cdom);
     my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.      # Check for network failure
      ':resourcedata',$chome);      if (!ref($courseopt)) {
     # Check for network failure   if ( $courseopt =~ /no.such.host/i || $courseopt =~ /con_lost/i) {
     if ( $reply =~ /no.such.host/i || $reply =~ /con_lost/i) {      $self->{NETWORK_FAILURE} = 1;
  $self->{NETWORK_FAILURE} = 1;  
     } elsif ($reply!~/^error\:/) {  
  $courserdatas{$cid}=$reply;  
  $courserdatas{$cid.'.last_cache'}=time;  
     }  
  }  
  foreach (split(/\&/,$courserdatas{$cid})) {  
     my ($name,$value)=split(/\=/,$_);  
     $courseopt{$userprefix.&Apache::lonnet::unescape($name)}=  
  &Apache::lonnet::unescape($value);  
  }   }
    undef($courseopt);
       }
   
 # --------------------------------------------------- Get userdata (if present)  # --------------------------------------------------- Get userdata (if present)
  unless ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) {  
     my $reply=&Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome);      my $useropt=&Apache::lonnet::get_userresdata($uname,$udom);
     if ($reply!~/^error\:/) {      # Check for network failure
  $userrdatas{$uname.'___'.$udom}=$reply;      if (!ref($useropt)) {
  $userrdatas{$uname.'___'.$udom.'.last_cache'}=time;   if ( $useropt =~ /no.such.host/i || $useropt =~ /con_lost/i) {
     }      $self->{NETWORK_FAILURE} = 1;
     # check to see if network failed  
     elsif ( $reply=~/no.such.host/i || $reply=~/con.*lost/i )  
     {  
  $self->{NETWORK_FAILURE} = 1;  
     }  
  }  
  foreach (split(/\&/,$userrdatas{$uname.'___'.$udom})) {  
     my ($name,$value)=split(/\=/,$_);  
     $useropt{$userprefix.&Apache::lonnet::unescape($name)}=  
  &Apache::lonnet::unescape($value);  
  }   }
  $self->{COURSE_OPT} = \%courseopt;   undef($useropt);
  $self->{USER_OPT} = \%useropt;  
     }      }
   
       $self->{COURSE_OPT} = $courseopt;
       $self->{USER_OPT} = $useropt;
   
     $self->{COURSE_USER_OPT_GENERATED} = 1;      $self->{COURSE_USER_OPT_GENERATED} = 1;
           
     return;      return;
Line 1818  sub generate_email_discuss_status { Line 1914  sub generate_email_discuss_status {
     my $symb = shift;      my $symb = shift;
     if ($self->{EMAIL_DISCUSS_GENERATED}) { return; }      if ($self->{EMAIL_DISCUSS_GENERATED}) { return; }
   
     my $cid=$ENV{'request.course.id'};      my $cid=$env{'request.course.id'};
     my ($cdom,$cnum)=split(/\_/,$cid);      my $cdom=$env{'course.'.$cid.'.domain'};
       my $cnum=$env{'course.'.$cid.'.num'};
           
     my %emailstatus = &Apache::lonnet::dump('email_status');      my %emailstatus = &Apache::lonnet::dump('email_status');
     my $logoutTime = $emailstatus{'logout'};      my $logoutTime = $emailstatus{'logout'};
     my $courseLeaveTime = $emailstatus{'logout_'.$ENV{'request.course.id'}};      my $courseLeaveTime = $emailstatus{'logout_'.$env{'request.course.id'}};
     $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?      $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?
    $courseLeaveTime : $logoutTime);     $courseLeaveTime : $logoutTime);
     my %discussiontime = &Apache::lonnet::dump('discussiontimes',       my %discussiontime = &Apache::lonnet::dump('discussiontimes', 
        $cdom, $cnum);         $cdom, $cnum);
     my %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=();
     my %error=();      my %error=();
     my $keys = &Apache::lonnet::reply('keys:'.      my @keys = &Apache::lonnet::getkeys('nohist_email',$env{'user.domain'},
       $ENV{'user.domain'}.':'.   $env{'user.name'});
       $ENV{'user.name'}.':nohist_email',  
       $ENV{'user.home'});  
           
     foreach my $msgid (split(/\&/, $keys)) {      foreach my $msgid (@keys) {
  $msgid=&Apache::lonnet::unescape($msgid);   if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {
  my $plain=&Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid));              my ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$fromcid,
  if ($plain=~/(Error|Feedback) \[([^\]]+)\]/) {                  $symb,$error) = &Apache::lonmsg::unpackmsgid($msgid);
     my ($what,$url)=($1,$2);              &Apache::lonenc::check_decrypt(\$symb); 
     my %status=              if (($fromcid ne '') && ($fromcid ne $cid)) {
  &Apache::lonnet::get('email_status',[$msgid]);                  next;
     if ($status{$msgid}=~/^error\:/) {               }
  $status{$msgid}='';               if (defined($symb)) {
     }                  if (defined($error) && $error == 1) {
                           $error{$symb}.=','.$msgid;
     if (($status{$msgid} eq 'new') ||                   } else {
  (!$status{$msgid})) {                       $feedback{$symb}.=','.$msgid;
  if ($what eq 'Error') {                  }
     $error{$url}.=','.$msgid;               } else {
  } else {                  my $plain=
     $feedback{$url}.=','.$msgid;                      &LONCAPA::unescape(&LONCAPA::unescape($msgid));
  }                  if ($plain=~/ \[([^\]]+)\]\:/) {
     }                      my $url=$1;
                       if ($plain=~/\:Error \[/) {
                           $error{$url}.=','.$msgid;
                       } else {
                           $feedback{$url}.=','.$msgid;
                       }
                   }
               }
  }   }
     }      }
           
       #symbs of resources that have feedbacks (will be urls pre-2.3)
     $self->{FEEDBACK} = \%feedback;      $self->{FEEDBACK} = \%feedback;
     $self->{ERROR_MSG} = \%error; # what is this? JB      #or errors (will be urls pre 2.3)
       $self->{ERROR_MSG} = \%error;
     $self->{DISCUSSION_TIME} = \%discussiontime;      $self->{DISCUSSION_TIME} = \%discussiontime;
     $self->{EMAIL_STATUS} = \%emailstatus;      $self->{EMAIL_STATUS} = \%emailstatus;
     $self->{LAST_READ} = \%lastreadtime;      $self->{LAST_READ} = \%lastreadtime;
Line 1880  sub get_user_data { Line 1984  sub get_user_data {
     if ($self->{RETRIEVED_USER_DATA}) { return; }      if ($self->{RETRIEVED_USER_DATA}) { return; }
   
     # Retrieve performance data on problems      # Retrieve performance data on problems
     my %student_data = Apache::lonnet::currentdump($ENV{'request.course.id'},      my %student_data = Apache::lonnet::currentdump($env{'request.course.id'},
    $ENV{'user.domain'},     $env{'user.domain'},
    $ENV{'user.name'});     $env{'user.name'});
     $self->{STUDENT_DATA} = \%student_data;      $self->{STUDENT_DATA} = \%student_data;
   
     $self->{RETRIEVED_USER_DATA} = 1;      $self->{RETRIEVED_USER_DATA} = 1;
 }  }
   
   sub get_discussion_data {
       my $self = shift;
       if ($self->{RETRIEVED_DISCUSSION_DATA}) {
    return $self->{DISCUSSION_DATA};
       }
   
       $self->generate_email_discuss_status();    
   
       my $cid=$env{'request.course.id'};
       my $cdom=$env{'course.'.$cid.'.domain'};
       my $cnum=$env{'course.'.$cid.'.num'};
       # Retrieve discussion data for resources in course
       my %discussion_data = &Apache::lonnet::dumpstore($cid,$cdom,$cnum);
   
   
       $self->{DISCUSSION_DATA} = \%discussion_data;
       $self->{RETRIEVED_DISCUSSION_DATA} = 1;
       return $self->{DISCUSSION_DATA};
   }
   
   
 # Internal function: Takes a key to look up in the nav hash and implements internal  # Internal function: Takes a key to look up in the nav hash and implements internal
 # memory caching of that key.  # memory caching of that key.
 sub navhash {  sub navhash {
Line 1907  sub navhash { Line 2032  sub navhash {
 # Checks to see if coursemap is defined, matching test in old lonnavmaps  # Checks to see if coursemap is defined, matching test in old lonnavmaps
 sub courseMapDefined {  sub courseMapDefined {
     my $self = shift;      my $self = shift;
     my $uri = &Apache::lonnet::clutter($ENV{'request.course.uri'});      my $uri = &Apache::lonnet::clutter($env{'request.course.uri'});
   
     my $firstres = $self->navhash("map_start_$uri");      my $firstres = $self->navhash("map_start_$uri");
     my $lastres = $self->navhash("map_finish_$uri");      my $lastres = $self->navhash("map_finish_$uri");
Line 1917  sub courseMapDefined { Line 2042  sub courseMapDefined {
 sub getIterator {  sub getIterator {
     my $self = shift;      my $self = shift;
     my $iterator = Apache::lonnavmaps::iterator->new($self, shift, shift,      my $iterator = Apache::lonnavmaps::iterator->new($self, shift, shift,
                                                      shift, undef, shift);                                                       shift, undef, shift,
        shift, shift);
     return $iterator;      return $iterator;
 }  }
   
 # unties the hash when done  
 sub untieHashes {  
     my $self = shift;  
     untie %{$self->{NAV_HASH}};  
     untie %{$self->{PARM_HASH}};  
 }  
   
 # Private method: Does the given resource (as a symb string) have  # Private method: Does the given resource (as a symb string) have
 # current discussion? Returns 0 if chat/mail data not extracted.  # current discussion? Returns 0 if chat/mail data not extracted.
 sub hasDiscussion {  sub hasDiscussion {
     my $self = shift;      my $self = shift;
     my $symb = shift;      my $symb = shift;
       
     $self->generate_email_discuss_status();      $self->generate_email_discuss_status();
   
     if (!defined($self->{DISCUSSION_TIME})) { return 0; }      if (!defined($self->{DISCUSSION_TIME})) { return 0; }
   
     #return defined($self->{DISCUSSION_TIME}->{$symb});      #return defined($self->{DISCUSSION_TIME}->{$symb});
   
 # backward compatibility (bulletin boards used to be 'wrapped')      # backward compatibility (bulletin boards used to be 'wrapped')
     my $ressymb = $symb;      my $ressymb = $self->wrap_symb($symb);
     if ($ressymb =~ m|adm/(\w+)/(\w+)/(\d+)/bulletinboard$|) {  
         unless ($ressymb =~ m|bulletin___\d+___adm/wrapper|) {  
             $ressymb = 'bulletin___'.$3.'___adm/wrapper/adm/'.$1.'/'.$2.'/'.$3.'/bulletinboard';  
         }  
     }  
   
     if ( defined ( $self->{LAST_READ}->{$ressymb} ) ) {      if ( defined ( $self->{LAST_READ}->{$ressymb} ) ) {
         return $self->{DISCUSSION_TIME}->{$ressymb} > $self->{LAST_READ}->{$ressymb};          return $self->{DISCUSSION_TIME}->{$ressymb} > $self->{LAST_READ}->{$ressymb};
     } else {      } else {
         return $self->{DISCUSSION_TIME}->{$ressymb} >  $self->{LAST_CHECK};  #        return $self->{DISCUSSION_TIME}->{$ressymb} >  $self->{LAST_CHECK}; # v.1.1 behavior 
           return $self->{DISCUSSION_TIME}->{$ressymb} >  0; # in 1.2 will display speech bubble icons for all items with posts until marked as read (even if read in v 1.1).
     }      }
 }  }
   
   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 {
       my $self = shift;
       my $symb = shift;
       if ($symb =~ m-___(adm/[^/]+/[^/]+/)(\d+)(/bulletinboard)$-) {
           unless ($symb =~ m|adm/wrapper/adm|) {
               $symb = 'bulletin___'.$2.'___adm/wrapper/'.$1.$2.$3;
           }
       }
       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.
   
 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 2003  the given map. This is one of the proper Line 2224  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 2023  sub getById { Line 2244  sub getById {
 sub getBySymb {  sub getBySymb {
     my $self = shift;      my $self = shift;
     my $symb = shift;      my $symb = shift;
   
     my ($mapUrl, $id, $filename) = &Apache::lonnet::decode_symb($symb);      my ($mapUrl, $id, $filename) = &Apache::lonnet::decode_symb($symb);
     my $map = $self->getResourceByUrl($mapUrl);      my $map = $self->getResourceByUrl($mapUrl);
     return $self->getById($map->map_pc() . '.' . $id);      my $returnvalue = undef;
       if (ref($map)) {
           $returnvalue = $self->getById($map->map_pc() .'.'.$id);
       }
       return $returnvalue;
 }  }
   
 sub getByMapPc {  sub getByMapPc {
Line 2048  resource in the navmap. Line 2274  resource in the navmap.
 sub firstResource {  sub firstResource {
     my $self = shift;      my $self = shift;
     my $firstResource = $self->navhash('map_start_' .      my $firstResource = $self->navhash('map_start_' .
                      &Apache::lonnet::clutter($ENV{'request.course.uri'}));                       &Apache::lonnet::clutter($env{'request.course.uri'}));
     return $self->getById($firstResource);      return $self->getById($firstResource);
 }  }
   
Line 2064  in the navmap. Line 2290  in the navmap.
 sub finishResource {  sub finishResource {
     my $self = shift;      my $self = shift;
     my $firstResource = $self->navhash('map_finish_' .      my $firstResource = $self->navhash('map_finish_' .
                      &Apache::lonnet::clutter($ENV{'request.course.uri'}));                       &Apache::lonnet::clutter($env{'request.course.uri'}));
     return $self->getById($firstResource);      return $self->getById($firstResource);
 }  }
   
Line 2072  sub finishResource { Line 2298  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 (ref($self->{PARM_CACHE}->{$hashkey}) eq 'ARRAY') { 
               if (defined($self->{PARM_CACHE}->{$hashkey}->[0])) {
                   if (wantarray) {
                       return @{$self->{PARM_CACHE}->{$hashkey}};
                   } else {
                       return $self->{PARM_CACHE}->{$hashkey}->[0];
                   }
               }
           } else {
               return $self->{PARM_CACHE}->{$hashkey};
           }
     }      }
       my $result = $self->parmval_real($what, $symb, $recurse);
     my $result = $self->parmval_real($what, $symb);  
     $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 2091  sub parmval_real { Line 2329  sub parmval_real {
     # Make sure the {USER_OPT} and {COURSE_OPT} hashes are populated      # Make sure the {USER_OPT} and {COURSE_OPT} hashes are populated
     $self->generate_course_user_opt();      $self->generate_course_user_opt();
   
     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 $uname=$ENV{'user.name'};      my $cgroup='';
     my $udom=$ENV{'user.domain'};      my @cgrps=split(/:/,$env{'request.course.groups'});
       if (@cgrps > 0) {
           @cgrps = sort(@cgrps);
           $cgroup = $cgrps[0];
       } 
       my $uname=$env{'user.name'};
       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 2108  sub parmval_real { Line 2352  sub parmval_real {
   
     my $symbparm=$symb.'.'.$what;      my $symbparm=$symb.'.'.$what;
     my $mapparm=$mapname.'___(all).'.$what;      my $mapparm=$mapname.'___(all).'.$what;
     my $usercourseprefix=$uname.'_'.$udom.'_'.$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;
Line 2124  sub parmval_real { Line 2372  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']; }
         if (defined($$courseopt{$courselevelm})) { return $$courseopt{$courselevelm}; }  
         if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; }  
     }      }
   
 # ----------------------------------------------------- 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 , cascade up parts      if (defined($courseopt)) {
           if (defined($$courseopt{$courselevelm})) { return [$$courseopt{$courselevelm},'map']; }
           if (defined($$courseopt{$courselevel})) {
              my $ret = [$$courseopt{$courselevel},'course'];
              return $ret;
          }
       }
   # --------------------------------------------------- sixth , cascade up parts
   
     my ($space,@qualifier)=split(/\./,$rwhat);      my ($space,@qualifier)=split(/\./,$rwhat);
     my $qualifier=join('.',@qualifier);      my $qualifier=join('.',@qualifier);
Line 2165  sub parmval_real { Line 2424  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
   
 =item * B<getResourceByUrl>(url):  =item * B<getResourceByUrl>(url,multiple):
   
 Retrieves a resource object by URL of the resource. If passed a  Retrieves a resource object by URL of the resource, unless the optional
 resource object, it will simply return it, so it is safe to use this  multiple parameter is included in which case an array of resource 
 method in code like "$res = $navmap->getResourceByUrl($res)", if  objects is returned. If passed a resource object, it will simply return  
 you're not sure if $res is already an object, or just a URL. If the  it, so it is safe to use this method in code like
 resource appears multiple times in the course, only the first instance  "$res = $navmap->getResourceByUrl($res)"
 will be returned. As a result, this is probably useful only for maps.  if you're not sure if $res is already an object, or just a URL. If the
   resource appears multiple times in the course, only the first instance 
   will be returned (useful for maps), unless the multiple parameter has
   been included, in which case all instances are returned in an array.
   
 =item * B<retrieveResources>(map, filterFunc, recursive, bailout):  =item * B<retrieveResources>(map, filterFunc, recursive, bailout, showall):
   
 The map is a specification of a map to retreive the resources from,  The map is a specification of a map to retreive the resources from,
 either as a url or as an object. The filterFunc is a reference to a  either as a url or as an object. The filterFunc is a reference to a
Line 2193  function that takes a resource object as Line 2455  function that takes a resource object as
 true if the resource should be included, or false if it should not  true if the resource should be included, or false if it should not
 be. If recursive is true, the map will be recursively examined,  be. If recursive is true, the map will be recursively examined,
 otherwise it will not be. If bailout is true, the function will return  otherwise it will not be. If bailout is true, the function will return
 as soon as it finds a resource, if false it will finish. By default,  as soon as it finds a resource, if false it will finish. If showall is
 the map is the top-level map of the course, filterFunc is a function  true it will not hide maps that contain nothing but one other map. By
 that always returns 1, recursive is true, bailout is false. The  default, the map is the top-level map of the course, filterFunc is a
 resources will be returned in a list containing the resource objects  function that always returns 1, recursive is true, bailout is false,
 for the corresponding resources, with B<no structure information> in  showall is false. The resources will be returned in a list containing
 the list; regardless of branching, recursion, etc., it will be a flat  the resource objects for the corresponding resources, with B<no
 list.  structure information> in the list; regardless of branching,
   recursion, etc., it will be a flat list.
   
 Thus, this is suitable for cases where you don't want the structure,  Thus, this is suitable for cases where you don't want the structure,
 just a list of all resources. It is also suitable for finding out how  just a list of all resources. It is also suitable for finding out how
Line 2208  want to know is if I<any> resources matc Line 2471  want to know is if I<any> resources matc
 parameter will allow you to avoid potentially expensive enumeration of  parameter will allow you to avoid potentially expensive enumeration of
 all matching resources.  all matching resources.
   
 =item * B<hasResource>(map, filterFunc, recursive):  =item * B<hasResource>(map, filterFunc, recursive, showall):
   
 Convience method for  Convenience method for
   
  scalar(retrieveResources($map, $filterFunc, $recursive, 1)) > 0   scalar(retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0
   
 which will tell whether the map has resources matching the description  which will tell whether the map has resources matching the description
 in the filter function.  in the filter function.
   
   =item * B<usedVersion>(url):
   
   Retrieves version infomation for a url. Returns the version (a number, or 
   the string "mostrecent") for resources which have version information in  
   the big hash.
       
 =cut  =cut
   
   
 sub getResourceByUrl {  sub getResourceByUrl {
     my $self = shift;      my $self = shift;
     my $resUrl = shift;      my $resUrl = shift;
       my $multiple = shift;
   
     if (ref($resUrl)) { return $resUrl; }      if (ref($resUrl)) { return $resUrl; }
   
     $resUrl = &Apache::lonnet::clutter($resUrl);      $resUrl = &Apache::lonnet::clutter($resUrl);
     my $resId = $self->{NAV_HASH}->{'ids_' . $resUrl};      my $resId = $self->{NAV_HASH}->{'ids_' . $resUrl};
     if ($resId =~ /,/) {  
         $resId = (split (/,/, $resId))[0];  
     }  
     if (!$resId) { return ''; }      if (!$resId) { return ''; }
     return $self->getById($resId);      if ($multiple) {
           my @resources = ();
           my @resIds = split (/,/, $resId);
           foreach my $id (@resIds) {
               my $resourceId = $self->getById($id);
               if ($resourceId) { 
                   push(@resources,$resourceId);
               }
           }
           return @resources;
       } else {
           if ($resId =~ /,/) {
               $resId = (split (/,/, $resId))[0];
           }
           return $self->getById($resId);
       }
 }  }
   
 sub retrieveResources {  sub retrieveResources {
Line 2245  sub retrieveResources { Line 2528  sub retrieveResources {
     if (!defined($recursive)) { $recursive = 1; }      if (!defined($recursive)) { $recursive = 1; }
     my $bailout = shift;      my $bailout = shift;
     if (!defined($bailout)) { $bailout = 0; }      if (!defined($bailout)) { $bailout = 0; }
       my $showall = shift;
     # Create the necessary iterator.      # Create the necessary iterator.
     if (!ref($map)) { # assume it's a url of a map.      if (!ref($map)) { # assume it's a url of a map.
         $map = $self->getResourceByUrl($map);          $map = $self->getResourceByUrl($map);
Line 2264  sub retrieveResources { Line 2547  sub retrieveResources {
   
     # Get an iterator.      # Get an iterator.
     my $it = $self->getIterator($map->map_start(), $map->map_finish(),      my $it = $self->getIterator($map->map_start(), $map->map_finish(),
                                 undef, $recursive);                                  undef, $recursive, $showall);
   
     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 2277  sub retrieveResources { Line 2564  sub retrieveResources {
                 next;                  next;
             }              }
   
             push @resources, $curRes;              push(@resources, $curRes);
   
             if ($bailout) {              if ($bailout) {
                 return @resources;                  return @resources;
Line 2294  sub hasResource { Line 2581  sub hasResource {
     my $map = shift;      my $map = shift;
     my $filterFunc = shift;      my $filterFunc = shift;
     my $recursive = shift;      my $recursive = shift;
       my $showall = shift;
           
     return scalar($self->retrieveResources($map, $filterFunc, $recursive, 1)) > 0;      return scalar($self->retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0;
   }
   
   sub usedVersion {
       my $self = shift;
       my $linkurl = shift;
       return $self->navhash("version_$linkurl");
 }  }
   
 1;  1;
   
 package Apache::lonnavmaps::iterator;  package Apache::lonnavmaps::iterator;
   use Scalar::Util qw(weaken);
   use Apache::lonnet;
   
 =pod  =pod
   
Line 2415  be the tokens described above. Line 2711  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 2441  sub new { Line 2737  sub new {
     my $class = ref($proto) || $proto;      my $class = ref($proto) || $proto;
     my $self = {};      my $self = {};
   
     $self->{NAV_MAP} = shift;      weaken($self->{NAV_MAP} = shift);
     return undef unless ($self->{NAV_MAP});      return undef unless ($self->{NAV_MAP});
   
     # Handle the parameters      # Handle the parameters
Line 2561  sub new { Line 2857  sub new {
             Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,              Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
                                               $finishResource, $self->{FILTER},                                                $finishResource, $self->{FILTER},
                                               $self->{ALREADY_SEEN},                                                 $self->{ALREADY_SEEN}, 
                                               $self->{CONDITION}, 0);                                                $self->{CONDITION},
         $self->{FORCE_TOP});
                   
     }      }
   
Line 2598  sub next { Line 2895  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 2699  sub next { Line 3000  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 2724  sub next { Line 3025  sub next {
         $self->{RECURSIVE_ITERATOR} =           $self->{RECURSIVE_ITERATOR} = 
             Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,              Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
                                               $finishResource, $self->{FILTER},                                                $finishResource, $self->{FILTER},
                                               $self->{ALREADY_SEEN}, $self->{CONDITION});                                                $self->{ALREADY_SEEN},
         $self->{CONDITION},
         $self->{FORCE_TOP});
     }      }
   
     # If this is a blank resource, don't actually return it.      # If this is a blank resource, don't actually return it.
Line 2777  sub populateStack { Line 3080  sub populateStack {
 1;  1;
   
 package Apache::lonnavmaps::DFSiterator;  package Apache::lonnavmaps::DFSiterator;
   use Scalar::Util qw(weaken);
   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
 #  through the nav map and presents the resources in a depth-first search  #  through the nav map and presents the resources in a depth-first search
Line 2805  sub new { Line 3110  sub new {
     my $class = ref($proto) || $proto;      my $class = ref($proto) || $proto;
     my $self = {};      my $self = {};
   
     $self->{NAV_MAP} = shift;      weaken($self->{NAV_MAP} = shift);
     return undef unless ($self->{NAV_MAP});      return undef unless ($self->{NAV_MAP});
   
     $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();      $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();
Line 2904  sub next { Line 3209  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 2959  sub populateStack { Line 3264  sub populateStack {
 1;  1;
   
 package Apache::lonnavmaps::resource;  package Apache::lonnavmaps::resource;
   use Scalar::Util qw(weaken);
 use Apache::lonnet;  use Apache::lonnet;
   
 =pod  =pod
Line 3031  X<symb> X<resource, symb> Line 3336  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 3041  sub new { Line 3346  sub new {
     my $class = ref($proto) || $proto;      my $class = ref($proto) || $proto;
     my $self = {};      my $self = {};
   
     $self->{NAV_MAP} = shift;      weaken($self->{NAV_MAP} = shift);
     $self->{ID} = shift;      $self->{ID} = shift;
   
     # Store this new resource in the parent nav map's cache.      # Store this new resource in the parent nav map's cache.
Line 3107  false. Line 3412  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 3129  Returns the title of the resource. Line 3439  Returns the title of the resource.
 # These info functions can be used directly, as they don't return  # These info functions can be used directly, as they don't return
 # resource information.  # resource information.
 sub comesfrom { my $self=shift; return $self->navHash("comesfrom_", 1); }  sub comesfrom { my $self=shift; return $self->navHash("comesfrom_", 1); }
   sub encrypted { my $self=shift; return $self->navHash("encrypted_", 1); }
 sub ext { my $self=shift; return $self->navHash("ext_", 1) eq 'true:'; }  sub ext { my $self=shift; return $self->navHash("ext_", 1) eq 'true:'; }
 sub from { my $self=shift; return $self->navHash("from_", 1); }  sub from { my $self=shift; return $self->navHash("from_", 1); }
 # considered private and undocumented  # considered private and undocumented
Line 3137  sub kind { my $self=shift; return $self- Line 3448  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 .      my $randompick = $self->parmval('randompick');
                                                '.0.parameter_randompick'};      return $randompick;
   }
   sub randomorder { 
       my $self = shift;
       my $randomorder = $self->parmval('randomorder');
       return ($randomorder =~ /^yes$/i);
   }
   sub link {
       my $self=shift;
       if ($self->encrypted()) { return &Apache::lonenc::encrypted($self->src); }
       return $self->src;
 }  }
 sub src {   sub src { 
     my $self=shift;      my $self=shift;
     return $self->navHash("src_", 1);      return $self->navHash("src_", 1);
 }  }
   sub shown_symb {
       my $self=shift;
       if ($self->encrypted()) {return &Apache::lonenc::encrypted($self->symb());}
       return $self->symb();
   }
   sub id {
       my $self=shift;
       return $self->{ID};
   }
   sub enclosing_map_src {
       my $self=shift;
       (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;
       return $self->navHash('map_id_'.$first);
   }
 sub symb {  sub symb {
     my $self=shift;      my $self=shift;
     (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;      (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;
Line 3152  sub symb { Line 3487  sub symb {
         . '___' . $second . '___' . $symbSrc;          . '___' . $second . '___' . $symbSrc;
     return &Apache::lonnet::symbclean($symb);      return &Apache::lonnet::symbclean($symb);
 }  }
   sub wrap_symb {
       my $self = shift;
       return $self->{NAV_MAP}->wrap_symb($self->symb());
   }
 sub title {   sub title { 
     my $self=shift;       my $self=shift; 
     if ($self->{ID} eq '0.0') {      if ($self->{ID} eq '0.0') {
  # If this is the top-level map, return the title of the course   # If this is the top-level map, return the title of the course
  # since this map can not be titled otherwise.   # since this map can not be titled otherwise.
  return $ENV{'course.'.$ENV{'request.course.id'}.'.description'};   return $env{'course.'.$env{'request.course.id'}.'.description'};
     }      }
     return $self->navHash("title_", 1); }      return $self->navHash("title_", 1); }
 # considered private and undocumented  # considered private and undocumented
 sub to { my $self=shift; return $self->navHash("to_", 1); }  sub to { my $self=shift; return $self->navHash("to_", 1); }
   sub condition {
       my $self=shift;
       my $undercond=$self->navHash("undercond_", 1);
       if (!defined($undercond)) { return 1; };
       my $condid=$self->navHash("condid_$undercond");
       if (!defined($condid)) { return 1; };
       my $condition=&Apache::lonnet::directcondval($condid);
       return $condition;
   }
   sub condval {
       my $self=shift;
       my ($pathname,$filename) = 
    &Apache::lonnet::split_uri_for_cond($self->src());
   
       my $match=($env{'acc.res.'.$env{'request.course.id'}.'.'.$pathname}=~
          /\&\Q$filename\E\:([\d\|]+)\&/);
       if ($match) {
    return &Apache::lonnet::condval($1);
       }
       return 0;
   }
 sub compTitle {  sub compTitle {
     my $self = shift;      my $self = shift;
     my $title = $self->title();      my $title = $self->title();
Line 3172  sub compTitle { Line 3532  sub compTitle {
     }      }
     return $title;      return $title;
 }  }
   
 =pod  =pod
   
 B<Predicate Testing the Resource>  B<Predicate Testing the Resource>
Line 3212  sub retrieveResources { Line 3573  sub retrieveResources {
    return $self->{NAV_MAP}->retrieveResources(@_);     return $self->{NAV_MAP}->retrieveResources(@_);
 }  }
   
   sub is_exam {
       my ($self,$part) = @_;
       my $type = $self->parmval('type',$part);
       if ($type 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 3224  sub is_page { Line 3596  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) = @_;
       my $type = $self->parmval('type',$part);
       if ($type 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)$/)      if ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) {
    return !($self->is_practice());
       }
       return 0;
   }
   sub is_raw_problem {
       my $self=shift;
       my $src = $self->src();
       if ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) {
           return 1;
       }
       return 0;
 }  }
   
 sub contains_problem {  sub contains_problem {
     my $self=shift;      my $self=shift;
     if ($self->is_page()) {      if ($self->is_page()) {
Line 3237  sub contains_problem { Line 3630  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';
 }  }
 sub is_survey {  sub is_survey {
     my $self = shift();      my $self = shift();
     my $part = shift();      my $part = shift();
     if ($self->src() =~ /\.(survey)$/) {      my $type = $self->parmval('type',$part);
       if ($type eq 'survey') {
         return 1;          return 1;
     }      }
     if ($self->parmval('type',$part) eq 'survey') {      if ($self->src() =~ /\.(survey)$/) {
         return 1;          return 1;
     }      }
     return 0;      return 0;
 }  }
   sub is_task {
       my $self=shift;
       my $src = $self->src();
       return ($src =~ /\.(task)$/)
   }
   
   sub is_empty_sequence {
       my $self=shift;
       my $src = $self->src();
       return !$self->is_page() && $self->navHash("is_map_", 1) && !$self->navHash("map_type_" . $self->map_pc());
   }
   
 # Private method: Shells out to the parmval in the nav map, handler parts.  # Private method: Shells out to the parmval in the nav map, handler parts.
 sub parmval {  sub parmval {
Line 3303  Returns a string with the type of the ma Line 3716  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 3316  sub map_pc { Line 3729  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 3333  sub map_type { Line 3746  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 3411  Get the weight for the problem. Line 3824  Get the weight for the problem.
   
 sub acc {  sub acc {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("acc", $part);      my $acc = $self->parmval("acc", $part);
       return $acc;
 }  }
 sub answerdate {  sub answerdate {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     # Handle intervals      # Handle intervals
     if ($self->parmval("answerdate.type", $part) eq 'date_interval') {      my $answerdatetype = $self->parmval("answerdate.type", $part);
         return $self->duedate($part) +       my $answerdate = $self->parmval("answerdate", $part);
             $self->parmval("answerdate", $part);      my $duedate = $self->parmval("duedate", $part);
       if ($answerdatetype eq 'date_interval') {
           $answerdate = $duedate + $answerdate; 
     }      }
     return $self->parmval("answerdate", $part);      return $answerdate;
 }  }
 sub awarded {   sub awarded { 
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
Line 3428  sub awarded { Line 3844  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 $date;
   }
   sub handgrade {
       (my $self, my $part) = @_;
       my @response_ids = $self->responseIds($part);
       if (@response_ids) {
    foreach my $response_id (@response_ids) {
               my $handgrade = $self->parmval("handgrade",$part.'_'.$response_id);
       if (lc($handgrade) eq 'yes') {
    return 'yes';
       }
    }
     }      }
     return $self->parmval("duedate", $part);      my $handgrade = $self->parmval("handgrade", $part);
       return $handgrade;
 }  }
 sub maxtries {  sub maxtries {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("maxtries", $part);      my $maxtries = $self->parmval("maxtries", $part);
       return $maxtries;
 }  }
 sub opendate {  sub opendate {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     if ($self->parmval("opendate.type", $part) eq 'date_interval') {      my $opendatetype = $self->parmval("opendate.type", $part);
         return $self->duedate($part) -      my $opendate = $self->parmval("opendate", $part); 
             $self->parmval("opendate", $part);      if ($opendatetype eq 'date_interval') {
           my $duedate = $self->duedate($part);
           $opendate = $duedate - $opendate; 
     }      }
     return $self->parmval("opendate");      return $opendate;
 }  }
 sub problemstatus {  sub problemstatus {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return lc $self->parmval("problemstatus", $part);      my $problemstatus = $self->parmval("problemstatus", $part);
       return lc($problemstatus);
 }  }
 sub sig {  sub sig {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("sig", $part);      my $sig = $self->parmval("sig", $part);
       return $sig;
 }  }
 sub tol {  sub tol {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("tol", $part);      my $tol = $self->parmval("tol", $part);
       return $tol;
 }  }
 sub tries {   sub tries {
     my $self = shift;       my $self = shift; 
     my $tries = $self->queryRestoreHash('tries', shift);      my $tries = $self->queryRestoreHash('tries', shift);
     if (!defined($tries)) { return '0';}      if (!defined($tries)) { return '0';}
Line 3469  sub tries { Line 3917  sub tries {
 }  }
 sub type {  sub type {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("type", $part);      my $type = $self->parmval("type", $part);
       return $type;
 }  }
 sub weight {   sub weight { 
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
     if (!defined($part)) { $part = '0'; }      if (!defined($part)) { $part = '0'; }
     return &Apache::lonnet::EXT('resource.'.$part.'.weight',      my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight',
  $self->symb(), $ENV{'user.domain'},                                  $self->symb(), $env{'user.domain'},
  $ENV{'user.name'},                                   $env{'user.name'},
  $ENV{'request.course.sec'});                                  $env{'request.course.sec'});
       return $weight;
   }
   sub part_display {
       my $self= shift(); my $partID = shift();
       if (! defined($partID)) { $partID = '0'; }
       my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',
                                        $self->symb);
       if (! defined($display) || $display eq '') {
           $display = $partID;
       }
       return $display;
 }  }
   
 # Multiple things need this  # Multiple things need this
Line 3519  Returns a false value if there has been Line 3978  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 3526  for the resource, or the null string if Line 4000  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 3539  sub hasDiscussion { Line 4013  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 3568  Returns the number of parts of the probl Line 4054  Returns the number of parts of the probl
 for single part problems, returns 1. For multipart, it returns the  for single part problems, returns 1. For multipart, it returns the
 number of parts in the problem, not including psuedo-part 0.   number of parts in the problem, not including psuedo-part 0. 
   
   =item * B<countResponses>():
   
   Returns the total number of responses in the problem a student can answer.
   
   =item * B<responseTypes>():
   
   Returns a hash whose keys are the response types.  The values are the number 
   of times each response type is used.  This is for the I<entire> problem, not 
   just a single part.
   
 =item * B<multipart>():  =item * B<multipart>():
   
 Returns true if the problem is multipart, false otherwise. Use this instead  Returns true if the problem is multipart, false otherwise. Use this instead
Line 3614  sub countParts { Line 4110  sub countParts {
     return scalar(@{$parts}); # + $delta;      return scalar(@{$parts}); # + $delta;
 }  }
   
   sub countResponses {
       my $self = shift;
       my $count;
       foreach my $part (@{$self->parts()}) {
           $count+= scalar($self->responseIds($part));
       }
       return $count;
   }
   
   sub responseTypes {
       my $self = shift;
       my %responses;
       foreach my $part (@{$self->parts()}) {
           foreach my $responsetype ($self->responseType($part)) {
               $responses{$responsetype}++ if (defined($responsetype));
           }
       }
       return %responses;
   }
   
 sub multipart {  sub multipart {
     my $self = shift;      my $self = shift;
     return $self->countParts() > 1;      return $self->countParts() > 1;
Line 3681  sub extractParts { Line 4197  sub extractParts {
  $self->{PART_TYPE} = {};   $self->{PART_TYPE} = {};
  return;   return;
     }      }
     foreach (split(/\,/,$metadata)) {      foreach my $entry (split(/\,/,$metadata)) {
  if ($_ =~ /^part_(.*)$/) {   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 3701  sub extractParts { Line 4217  sub extractParts {
         }          }
                   
   
           # These hashes probably do not need names that end with "Hash"....
         my %responseIdHash;          my %responseIdHash;
         my %responseTypeHash;          my %responseTypeHash;
   
   
         # 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 3716  sub extractParts { Line 4233  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 3729  sub extractParts { Line 4247  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);
                     }                      }
                 }                  }
             }              }
         }          }
    my $resorder = &Apache::lonnet::metadata($self->src(),'responseorder');
           #
           # Reorder the arrays in the %responseIdHash and %responseTypeHash
    if ($resorder) {
       my @resorder=split(/,/,$resorder);
       foreach my $part (keys(%responseIdHash)) {
    my $i=0;
    my %resids = map { ($_,$i++) } @{ $responseIdHash{$part} };
    my @neworder;
    foreach my $possibleid (@resorder) {
       if (exists($resids{$possibleid})) {
    push(@neworder,$resids{$possibleid});
       }
    }
    my @ids;
    my @type;
    foreach my $element (@neworder) {
       push (@ids,$responseIdHash{$part}->[$element]);
       push (@type,$responseTypeHash{$part}->[$element]);
    }
    $responseIdHash{$part}=\@ids;
    $responseTypeHash{$part}=\@type;
       }
    }
         $self->{RESPONSE_IDS} = \%responseIdHash;          $self->{RESPONSE_IDS} = \%responseIdHash;
         $self->{RESPONSE_TYPES} = \%responseTypeHash;          $self->{RESPONSE_TYPES} = \%responseTypeHash;
     }      }
Line 3755  the completion information. Line 4304  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 3922  sub ATTEMPTED             { return 16; } Line 4471  sub ATTEMPTED             { return 16; }
   
 sub getCompletionStatus {  sub getCompletionStatus {
     my $self = shift;      my $self = shift;
       my $part = shift;
     return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE});      return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE});
   
     my $status = $self->queryRestoreHash('solved', shift);      my $status = $self->queryRestoreHash('solved', $part);
   
     # Left as separate if statements in case we ever do more with this      # Left as separate if statements in case we ever do more with this
     if ($status eq 'correct_by_student') {return $self->CORRECT;}      if ($status eq 'correct_by_student') {return $self->CORRECT;}
     if ($status eq 'correct_by_override') {return $self->CORRECT_BY_OVERRIDE; }      if ($status eq 'correct_by_scantron') {return $self->CORRECT;}
       if ($status eq 'correct_by_override') {
    return $self->CORRECT_BY_OVERRIDE;
       }
     if ($status eq 'incorrect_attempted') {return $self->INCORRECT; }      if ($status eq 'incorrect_attempted') {return $self->INCORRECT; }
     if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; }      if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; }
     if ($status eq 'excused') {return $self->EXCUSED; }      if ($status eq 'excused') {return $self->EXCUSED; }
Line 4032  An answer has been submitted, but the st Line 4585  An answer has been submitted, but the st
   
 sub TRIES_LEFT       { return 20; }  sub TRIES_LEFT       { return 20; }
 sub ANSWER_SUBMITTED { return 21; }  sub ANSWER_SUBMITTED { return 21; }
   sub PARTIALLY_CORRECT{ return 22; }
   
 sub status {  sub status {
     my $self = shift;      my $self = shift;
Line 4047  sub status { Line 4601  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->answerdate($part) && $self->answerdate($part) < time()) {      if ($self->duedate($part) && $self->duedate($part) < time() &&
    $self->answerdate($part) && $self->answerdate($part) < time()) {
  $suppressFeedback = 0;   $suppressFeedback = 0;
     }      }
   
     # There are a few whole rows we can dispose of:      # There are a few whole rows we can dispose of:
     if ($completionStatus == CORRECT ||      if ($completionStatus == CORRECT ||
         $completionStatus == CORRECT_BY_OVERRIDE ) {          $completionStatus == CORRECT_BY_OVERRIDE ) {
         return $suppressFeedback? ANSWER_SUBMITTED : CORRECT;    if ( $suppressFeedback ) { return ANSWER_SUBMITTED }
    my $awarded=$self->awarded($part);
    if ($awarded < 1 && $awarded > 0) {
               return PARTIALLY_CORRECT;
    } elsif ($awarded<1) {
       return INCORRECT;
    }
    return CORRECT; 
       }
   
       # If it's WRONG... and not open
       if ( ($completionStatus == INCORRECT || 
     $completionStatus == INCORRECT_BY_OVERRIDE)
    && (!$self->opendate($part) ||  $self->opendate($part) > time()) ) {
    return INCORRECT;
     }      }
   
     if ($completionStatus == ATTEMPTED) {      if ($completionStatus == ATTEMPTED) {
Line 4078  sub status { Line 4651  sub status {
   
     if ($dateStatus == PAST_DUE_ANSWER_LATER ||      if ($dateStatus == PAST_DUE_ANSWER_LATER ||
         $dateStatus == PAST_DUE_NO_ANSWER ) {          $dateStatus == PAST_DUE_NO_ANSWER ) {
         return $dateStatus;           return $suppressFeedback ? ANSWER_SUBMITTED : $dateStatus; 
     }      }
   
     if ($dateStatus == ANSWER_OPEN) {      if ($dateStatus == ANSWER_OPEN) {
Line 4144  my %compositeToSimple = Line 4717  my %compositeToSimple =
       NETWORK_FAILURE()       => ERROR,        NETWORK_FAILURE()       => ERROR,
       NOTHING_SET()           => CLOSED,        NOTHING_SET()           => CLOSED,
       CORRECT()               => CORRECT,        CORRECT()               => CORRECT,
         PARTIALLY_CORRECT()     => PARTIALLY_CORRECT,
       EXCUSED()               => CORRECT,        EXCUSED()               => CORRECT,
       PAST_DUE_NO_ANSWER()    => INCORRECT,        PAST_DUE_NO_ANSWER()    => INCORRECT,
       PAST_DUE_ANSWER_LATER() => INCORRECT,        PAST_DUE_ANSWER_LATER() => INCORRECT,
Line 4264  sub getNext { Line 4838  sub getNext {
     my $to = $self->to();      my $to = $self->to();
     foreach my $branch ( split(/,/, $to) ) {      foreach my $branch ( split(/,/, $to) ) {
         my $choice = $self->{NAV_MAP}->getById($branch);          my $choice = $self->{NAV_MAP}->getById($branch);
           #if (!$choice->condition()) { next; }
         my $next = $choice->goesto();          my $next = $choice->goesto();
         $next = $self->{NAV_MAP}->getById($next);          $next = $self->{NAV_MAP}->getById($next);
   
Line 4292  sub browsePriv { Line 4867  sub browsePriv {
         return $self->{BROWSE_PRIV};          return $self->{BROWSE_PRIV};
     }      }
   
     $self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre', $self->src());      $self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre',$self->src(),
       $self->symb());
 }  }
   
 =pod  =pod

Removed from v.1.262  
changed lines
  Added in v.1.414


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