Diff for /loncom/interface/lonnavmaps.pm between versions 1.241 and 1.366

version 1.241, 2003/11/06 15:24:13 version 1.366, 2006/03/04 05:55:43
Line 25 Line 25
 #  #
 # http://www.lon-capa.org/  # http://www.lon-capa.org/
 #  #
 # (Page Handler  ###
 #  
 # (TeX Content Handler  
 #  
 # 05/29/00,05/30 Gerd Kortemeyer)  
 # 08/30,08/31,09/06,09/14,09/15,09/16,09/19,09/20,09/21,09/23,  
 # 10/02,10/10,10/14,10/16,10/18,10/19,10/31,11/6,11/14,11/16 Gerd Kortemeyer)  
 #  
 # 3/1/1,6/1,17/1,29/1,30/1,2/8,9/21,9/24,9/25 Gerd Kortemeyer  
 # YEAR=2002  
 # 1/1 Gerd Kortemeyer  
 # Oct-Nov Jeremy Bowers  
 # YEAR=2003  
 # Jeremy Bowers ... lots of days  
   
 package Apache::lonnavmaps;  package Apache::lonnavmaps;
   
 use strict;  use strict;
   use GDBM_File;
 use Apache::Constants qw(:common :http);  use Apache::Constants qw(:common :http);
 use Apache::loncommon();  use Apache::loncommon();
 use Apache::lonmenu();  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 Data::Dumper; # for debugging, not always 
   use Time::HiRes qw( gettimeofday tv_interval );
   
 # symbolic constants  # symbolic constants
 sub SYMB { return 1; }  sub SYMB { return 1; }
Line 66  my %statusIconMap = Line 57  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 90  my %colormap = Line 82  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 then 24 hours
 my $hurryUpColor = "#FF0000";  my $hurryUpColor = "#FF0000";
   
   sub launch_win {
       my ($mode,$script,$toplinkitems,$firsttime)=@_;
       my $result;
       if ($script ne 'no') {
    $result.='<script type="text/javascript">';
       }
       if ($firsttime) {
    $result.='function launch_navmapwin() {
                    newWindow=open(\'/adm/navmaps?launchExternalRoles\',\'loncapanav\',\'width=400,height=600,scrollbars=1\');
                  }';
       } else {
    $result.='function launch_navmapwin() {
                    newWindow=open(\'/adm/navmaps?launchExternal\',\'loncapanav\',\'width=400,height=600,scrollbars=1\');
                  }';
       }
       if ($mode eq 'now') {
    $result.="\nlaunch_navmapwin();\n";
       }
       if ($script ne 'no') {
    $result.='</script>';
       }
       if ($mode eq 'link') {
    &add_linkitem($toplinkitems,'launchnav','launch_navmapwin()',
         "Launch navigation window");
       }
       return $result;
   }
   
   sub close {
       if ($env{'environment.remotenavmap'} ne 'on') { return ''; }
       return(<<ENDCLOSE);
   <script type="text/javascript">
   window.status='Accessing Nav Control';
   menu=window.open("/adm/rat/empty.html","loncapanav",
                    "height=600,width=400,scrollbars=1");
   window.status='Closing Nav Control';
   menu.close();
   window.status='Done.';
   </script>
   ENDCLOSE
   }
   
   sub update {
       if ($env{'environment.remotenavmap'} ne 'on') { return ''; }
       if (!$env{'request.course.id'}) { return ''; }
       if ($ENV{'REQUEST_URI'}=~m|^/adm/navmaps|) { return ''; }
       return(<<ENDUPDATE);
   <form name="navform"></form>
   <script type="text/javascript">
   this.document.navform.action='/adm/navmaps#curloc';
   this.document.navform.target='loncapanav';
   this.document.navform.submit();
   </script>
   ENDUPDATE
   }
   
 sub handler {  sub handler {
     my $r = shift;      my $r = shift;
     real_handler($r);      real_handler($r);
Line 103  sub handler { Line 152  sub handler {
   
 sub real_handler {  sub real_handler {
     my $r = shift;      my $r = shift;
       #my $t0=[&gettimeofday()];
     # Handle header-only request      # Handle header-only request
     if ($r->header_only) {      if ($r->header_only) {
         if ($ENV{'browser.mathml'}) {          if ($env{'browser.mathml'}) {
             &Apache::loncommon::content_type($r,'text/xml');              &Apache::loncommon::content_type($r,'text/xml');
         } else {          } else {
             &Apache::loncommon::content_type($r,'text/html');              &Apache::loncommon::content_type($r,'text/html');
Line 116  sub real_handler { Line 165  sub real_handler {
     }      }
   
     # Send header, don't cache this page      # Send header, don't cache this page
     if ($ENV{'browser.mathml'}) {      if ($env{'browser.mathml'}) {
         &Apache::loncommon::content_type($r,'text/xml');          &Apache::loncommon::content_type($r,'text/xml');
     } else {      } else {
         &Apache::loncommon::content_type($r,'text/html');          &Apache::loncommon::content_type($r,'text/html');
     }      }
     &Apache::loncommon::no_cache($r);      &Apache::loncommon::no_cache($r);
     $r->send_http_header;  
       my %toplinkitems=();
       &add_linkitem(\%toplinkitems,'blank','',"Select Action");
       if ($ENV{QUERY_STRING} eq 'collapseExternal') {
    &Apache::lonnet::put('environment',{'remotenavmap' => 'off'});
    &Apache::lonnet::appenv('environment.remotenavmap' => 'off');
    my $menu=&Apache::lonmenu::reopenmenu();
    my $navstatus=&Apache::lonmenu::get_nav_status();
    if ($menu) {
       $menu=(<<MENU)
                swmenu=$menu
                swmenu.clearTimeout(swmenu.menucltim);
        $navstatus
   MENU
           } else {
       my $nothing = &Apache::lonhtmlcommon::javascript_nothing();
       my $mainwindow='window.open('.$nothing.',"loncapaclient","",false);';
       $menu=(<<MENU)
                swmenu=$mainwindow
        $navstatus
   MENU
    }
           $r->send_http_header;
    my $html=&Apache::lonxml::xmlbegin();
    $r->print(<<"ENDSUBM");
    $html
           <head>
     <script type="text/javascript">
        function submitthis() {
       $menu
       self.close();
       }
   
      </script>
           </head>
    <body bgcolor="#FFFFFF" onLoad="submitthis()"></body>
           </html>
   ENDSUBM
           return OK;
       }
       if ($ENV{QUERY_STRING} =~ /^launchExternal/) {
    &Apache::lonnet::put('environment',{'remotenavmap' => 'on'});
    &Apache::lonnet::appenv('environment.remotenavmap' => 'on');
     my $menu=&Apache::lonmenu::reopenmenu();
    my $navstatus=&Apache::lonmenu::get_nav_status();
    if ($menu) {
       $r->print(<<MENU);
                <script type="text/javascript">
                swmenu=$menu
                swmenu.clearTimeout(swmenu.menucltim);
        $navstatus
                </script>
   MENU
           }
      }
       if ($ENV{QUERY_STRING} eq 'turningOffExternal') {
    $env{'environment.remotenavmap'}='off';
       }
   
     # Create the nav map      # Create the nav map
     my $navmap = Apache::lonnavmaps::navmap->new();      my $navmap = Apache::lonnavmaps::navmap->new();
   
     if (!defined($navmap)) {      if (!defined($navmap)) {
         my $requrl = $r->uri;          my $requrl = $r->uri;
         $ENV{'user.error.msg'} = "$requrl:bre:0:0:Course not initialized";          $env{'user.error.msg'} = "$requrl:bre:0:0:Course not initialized";
         return HTTP_NOT_ACCEPTABLE;          return HTTP_NOT_ACCEPTABLE;
     }      }
       $r->send_http_header;
     $r->print("<html><head>\n");      my $html=&Apache::lonxml::xmlbegin();
       $r->print("$html<head>\n");
     $r->print("<title>".&mt('Navigate Course Contents')."</title>");      $r->print("<title>".&mt('Navigate Course Contents')."</title>");
 # ------------------------------------------------------------ Get query string  # ------------------------------------------------------------ Get query string
     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register']);      &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register','sort','showOnlyHomework','postsymb']);
           
 # ----------------------------------------------------- Force menu registration  # ----------------------------------------------------- Force menu registration
     my $addentries='';      my $addentries='';
     if ($ENV{'form.register'}) {      my $more_unload;
        $addentries=' onLoad="'.&Apache::lonmenu::loadevents().      my $body_only='';
    '" onUnload="'.&Apache::lonmenu::unloadevents().'"';      if ($env{'environment.remotenavmap'} eq 'on') {
        $r->print(&Apache::lonmenu::registerurl(1));   $r->print('<script type="text/javascript">
                         function collapse() {
                            this.document.location="/adm/navmaps?collapseExternal";
                         }
                      </script>');
   # FIXME need to be smarter to only catch window close events
   # $more_unload="collapse()"
    $body_only=1;
       }
       if ($env{'form.register'}) {
    $addentries=' onLoad="'.&Apache::lonmenu::loadevents().
       '" onUnload="'.&Apache::lonmenu::unloadevents().';'.
       $more_unload.'"';
    $r->print(&Apache::lonmenu::registerurl(1));
       } else {
    $addentries=' onUnload="'.$more_unload.'"';
     }      }
   
     # Header      # Header
     $r->print('</head>'.      $r->print('</head>'.
               &Apache::loncommon::bodytag('Navigate Course Contents','',                &Apache::loncommon::bodytag('Navigate Course Contents','',
                                     $addentries,'','',$ENV{'form.register'}));    $addentries,$body_only,'',
     $env{'form.register'}));
     $r->print('<script>window.focus();</script>');      $r->print('<script>window.focus();</script>');
        
     $r->rflush();      $r->rflush();
   
     # Check that it's defined      # Check that it's defined
     if (!($navmap->courseMapDefined())) {      if (!($navmap->courseMapDefined())) {
    $r->print(&Apache::loncommon::help_open_menu('','Navigation Screen','Navigation_Screen','',undef,'RAT'));
         $r->print('<font size="+2" color="red">Coursemap undefined.</font>' .          $r->print('<font size="+2" color="red">Coursemap undefined.</font>' .
                   '</body></html>');                    '</body></html>');
         return OK;          return OK;
Line 179  sub real_handler { Line 303  sub real_handler {
         if ($sequenceCount == 1) {          if ($sequenceCount == 1) {
             # The automatic iterator creation in the render call               # The automatic iterator creation in the render call 
             # will pick this up. We know the condition because              # will pick this up. We know the condition because
             # the defined($ENV{'form.filter'}) also ensures this              # the defined($env{'form.filter'}) also ensures this
             # is a fresh call.              # is a fresh call.
             $ENV{'form.filter'} = "$sequenceId";              $env{'form.filter'} = "$sequenceId";
         }          }
     }      }
   
       if ($ENV{QUERY_STRING} eq 'launchExternal') {
    $r->print('
             <form name="returnwin" action="/adm/flip?postdata=navlaunch%3a" 
                   method="post" target="loncapaclient">
             </form>');
    $r->print('
             <script type="text/javascript">
                 this.document.returnwin.submit();
             </script>');
       }
   
       if ($env{'environment.remotenavmap'} ne 'on') {
    $r->print(&launch_win('link','yes',\%toplinkitems));
       } 
       if ($env{'environment.remotenavmap'} eq 'on') {
    &add_linkitem(\%toplinkitems,'closenav','collapse()',
         "Close navigation window");
       } 
   
     my $jumpToFirstHomework = 0;      my $jumpToFirstHomework = 0;
     # Check to see if the student is jumping to next open, do-able problem      # Check to see if the student is jumping to next open, do-able problem
     if ($ENV{QUERY_STRING} eq 'jumpToFirstHomework') {      if ($ENV{QUERY_STRING} =~ /^jumpToFirstHomework/) {
         $jumpToFirstHomework = 1;          $jumpToFirstHomework = 1;
         # Find the next homework problem that they can do.          # Find the next homework problem that they can do.
         my $iterator = $navmap->getIterator(undef, undef, undef, 1);          my $iterator = $navmap->getIterator(undef, undef, undef, 1);
Line 207  sub real_handler { Line 350  sub real_handler {
                     pop @$stack; # last resource in the stack is the problem                      pop @$stack; # last resource in the stack is the problem
                                  # itself, which we don't need in the map stack                                   # itself, which we don't need in the map stack
                     my @mapPcs = map {$_->map_pc()} @$stack;                      my @mapPcs = map {$_->map_pc()} @$stack;
                     $ENV{'form.filter'} = join(',', @mapPcs);                      $env{'form.filter'} = join(',', @mapPcs);
   
                     # Mark as both "here" and "jump"                      # Mark as both "here" and "jump"
                     $ENV{'form.postsymb'} = $curRes->symb();                      $env{'form.postsymb'} = $curRes->symb();
                 }                  }
             }              }
         }          }
Line 220  sub real_handler { Line 363  sub real_handler {
             $r->print("<font size='+2'>All homework assignments have been completed.</font><br /><br />");              $r->print("<font size='+2'>All homework assignments have been completed.</font><br /><br />");
         }          }
     } else {      } else {
         $r->print("<a href='navmaps?jumpToFirstHomework'>" .   &add_linkitem(\%toplinkitems,'firsthomework',
        &mt("Go To My First Homework Problem")."</a>&nbsp;&nbsp;&nbsp;&nbsp;");        'location.href="navmaps?jumpToFirstHomework"',
         "Show Me My First Homework Problem");
     }      }
   
     my $suppressEmptySequences = 0;      my $suppressEmptySequences = 0;
Line 230  sub real_handler { Line 374  sub real_handler {
   
     # Display only due homework.      # Display only due homework.
     my $showOnlyHomework = 0;      my $showOnlyHomework = 0;
     if ($ENV{QUERY_STRING} eq 'showOnlyHomework') {      if ($env{'form.showOnlyHomework'} eq "1") {
         $showOnlyHomework = 1;          $showOnlyHomework = 1;
         $suppressEmptySequences = 1;          $suppressEmptySequences = 1;
         $filterFunc = sub { my $res = shift;           $filterFunc = sub { my $res = shift; 
                             return $res->completable() || $res->is_map();                              return $res->completable() || $res->is_map();
                         };                          };
    &add_linkitem(\%toplinkitems,'everything',
        'location.href="navmaps?sort='.$env{'form.sort'}.'"',
         "Show Everything");
         $r->print("<p><font size='+2'>".&mt("Uncompleted Homework")."</font></p>");          $r->print("<p><font size='+2'>".&mt("Uncompleted Homework")."</font></p>");
         $ENV{'form.filter'} = '';          $env{'form.filter'} = '';
         $ENV{'form.condition'} = 1;          $env{'form.condition'} = 1;
  $resource_no_folder_link = 1;   $resource_no_folder_link = 1;
     } else {      } else {
         $r->print("<a href='navmaps?showOnlyHomework'>" .   &add_linkitem(\%toplinkitems,'uncompleted',
        &mt("Show Only Uncompleted Homework")."</a>&nbsp;&nbsp;&nbsp;&nbsp;");        'location.href="navmaps?sort='.$env{'form.sort'}.
     }            '&showOnlyHomework=1"',
         "Show Only Uncompleted Homework");
       }
   
       my %selected=($env{'form.sort'} => 'selected=on');
       my $sort_html=("<form>
                    <nobr>
                       <input type=\"hidden\" name=\"showOnlyHomework\" value=\"".$env{'form.showOnlyHomework'}."\" />
                       <input type=\"submit\" value=\"".&mt('Sort by:')."\" />
                       <select name=\"sort\">
                          <option value=\"default\" $selected{'default'}>".&mt('Default')."</option>
                          <option value=\"title\"   $selected{'title'}  >".&mt('Title')."</option>
                          <option value=\"duedate\" $selected{'duedate'}>".&mt('Duedate')."</option>
                          <option value=\"discussion\" $selected{'discussion'}>".&mt('Has New Discussion')."</option>
                       </select>
                    </nobr>
                  </form>");
     # renderer call      # renderer call
     my $renderArgs = { 'cols' => [0,1,2,3],      my $renderArgs = { 'cols' => [0,1,2,3],
          'sort' => $env{'form.sort'},
                        'url' => '/adm/navmaps',                         'url' => '/adm/navmaps',
                        'navmap' => $navmap,                         'navmap' => $navmap,
                        'suppressNavmap' => 1,                         'suppressNavmap' => 1,
                        'suppressEmptySequences' => $suppressEmptySequences,                         'suppressEmptySequences' => $suppressEmptySequences,
                        'filterFunc' => $filterFunc,                         'filterFunc' => $filterFunc,
        'resource_no_folder_link' => $resource_no_folder_link,         'resource_no_folder_link' => $resource_no_folder_link,
                        'r' => $r};         'sort_html'=> $sort_html,
                          'r' => $r,
                          'caller' => 'navmapsdisplay',
                          'linkitems' => \%toplinkitems};
     my $render = render($renderArgs);      my $render = render($renderArgs);
     $navmap->untieHashes();  
   
     # If no resources were printed, print a reassuring message so the      # If no resources were printed, print a reassuring message so the
     # user knows there was no error.      # user knows there was no error.
Line 266  sub real_handler { Line 431  sub real_handler {
             $r->print("<p><font size='+1'>This course is empty.</font></p>");              $r->print("<p><font size='+1'>This course is empty.</font></p>");
         }          }
     }      }
       #my $td=&tv_interval($t0);
       #$r->print("<br />$td");
   
     $r->print("</body></html>");      $r->print("</body></html>");
     $r->rflush();      $r->rflush();
Line 296  sub removeFromFilter { Line 463  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;
   
     # 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) && $res->is_page()) {          if (defined($res)) {
             return $res->src();      my $anchor;
       if ($res->is_page()) {
    foreach (@$stack) { if (defined($_)) { $anchor = $_; }  }
    $anchor=&Apache::lonnet::escape($anchor->shown_symb());
    return ($res->link(),$res->shown_symb(),$anchor);
       }
               # in case folder was skipped over as "only sequence"
       my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb());
       if ($map=~/\.page$/) {
    my $url=&Apache::lonnet::clutter($map);
    $anchor=&Apache::lonnet::escape($src->shown_symb());
    return ($url,$res->shown_symb(),$anchor);
       }
         }          }
     }      }
   
Line 316  sub getLinkForResource { Line 494  sub getLinkForResource {
         if (defined($_)) { $res = $_; }          if (defined($_)) { $res = $_; }
     }      }
   
     return $res->src();      return ($res->link(),$res->shown_symb());
 }  }
   
 # Convenience function: This seperates the logic of how to create  # Convenience function: This separates the logic of how to create
 # the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned",  # the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned",
 # etc.) into a seperate function. It takes a resource object as the  # etc.) into a separate function. It takes a resource object as the
 # first parameter, and the part number of the resource as the second.  # first parameter, and the part number of the resource as the second.
 # It's basically a big switch statement on the status of the resource.  # It's basically a big switch statement on the status of the resource.
   
Line 337  sub getDescription { Line 515  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 "Open " . timeToHumanString($res->opendate($part),'start');
     }      }
     if ($status == $res->OPEN) {      if ($status == $res->OPEN) {
         if ($res->duedate($part)) {          if ($res->duedate($part)) {
             return &mt("Due")."  " .timeToHumanString($res->duedate($part));              return &mt("Due")."  " .timeToHumanString($res->duedate($part),'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($res->answerdate($part),'start');
     }      }
     if ($status == $res->PAST_DUE_NO_ANSWER) {      if ($status == $res->PAST_DUE_NO_ANSWER) {
         return &mt("Was due")." " . timeToHumanString($res->duedate($part));          return &mt("Was due")." " . timeToHumanString($res->duedate($part),'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 371  sub getDescription { Line 550  sub getDescription {
                 $triesString = "<b>$triesString</b>";                  $triesString = "<b>$triesString</b>";
             }              }
         }          }
         if ($res->duedate()) {          if ($res->duedate($part)) {
             return &mt("Due")." " . timeToHumanString($res->duedate($part)) .              return &mt("Due")." " . timeToHumanString($res->duedate($part),'end') .
                 " $triesString";                  " $triesString";
         } else {          } else {
             return &mt("No due date")." $triesString";              return &mt("No due date")." $triesString";
Line 386  sub getDescription { Line 565  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 then
 # 24 hours, and still can be done?  # 24 hours, and still can be done?
   
 sub dueInLessThen24Hours {  sub dueInLessThan24Hours {
     my $res = shift;      my $res = shift;
     my $part = shift;      my $part = shift;
     my $status = $res->status($part);      my $status = $res->status($part);
   
     return ($status == $res->OPEN() ||      return ($status == $res->OPEN() ||
             $status == $res->TRIES_LEFT()) &&              $status == $res->TRIES_LEFT()) &&
            $res->duedate() && $res->duedate() < time()+(24*60*60) &&      $res->duedate($part) && $res->duedate($part) < time()+(24*60*60) &&
            $res->duedate() > time();      $res->duedate($part) > time();
 }  }
   
 # 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
Line 406  sub lastTry { Line 585  sub lastTry {
     my $tries = $res->tries($part);      my $tries = $res->tries($part);
     my $maxtries = $res->maxtries($part);      my $maxtries = $res->maxtries($part);
     return $tries && $maxtries && $maxtries > 1 &&      return $tries && $maxtries && $maxtries > 1 &&
         $maxtries - $tries == 1 && $res->duedate() &&          $maxtries - $tries == 1 && $res->duedate($part) &&
         $res->duedate() > 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 425  sub advancedUser { Line 604  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 497  sub timeToHumanString { Line 679  sub timeToHumanString {
             return "$prefix$hourString$minuteString$tense";              return "$prefix$hourString$minuteString$tense";
         }          }
   
    # If there's a caller supplied format, use it.
   
    if($format ne '') {
       my $timeStr = strftime($format, localtime($time));
       return $timeStr.&Apache::lonlocal::gettimezone();
    }
   
         # Less then 5 days away, display day of the week and          # Less then 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 = strftime("%A, %b %e at %I:%M %P", localtime($time));
             $timeStr =~ s/12:00 am/00:00/;              $timeStr =~ s/12:00 am/00:00/;
             $timeStr =~ s/12:00 pm/noon/;              $timeStr =~ s/12:00 pm/noon/;
             return ($inPast ? "last " : "next ") .              return ($inPast ? "last " : "this ") .
                 $timeStr;                  $timeStr.&Apache::lonlocal::gettimezone();
         }          }
                   
    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]) {          if ( $time[5] == $now[5]) {
             # 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 = strftime("$conjunction %A, %b %e at %I:%M %P", localtime($time));
             $timeStr =~ s/12:00 am/00:00/;              $timeStr =~ s/12:00 am/00:00/;
             $timeStr =~ s/12:00 pm/noon/;              $timeStr =~ s/12:00 pm/noon/;
             return $timeStr;              return $timeStr.&Apache::lonlocal::gettimezone();
         }          }
   
         # 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 = strftime("$conjunction %A, %b %e %Y at %I:%M %P", localtime($time));
         $timeStr =~ s/12:00 am/00:00/;          $timeStr =~ s/12:00 am/00:00/;
         $timeStr =~ s/12:00 pm/noon/;          $timeStr =~ s/12:00 pm/noon/;
         return $timeStr;          return $timeStr.&Apache::lonlocal::gettimezone();
     }      }
 }  }
   
Line 716  automatically. Line 912  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 732  instruct the renderer to render only a p Line 928  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<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 762  then only one line will be displayed for Line 958  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 795  are allowing the user to open and close Line 991  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 864  sub render_resource { Line 1060  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};
   
     my $title = $resource->compTitle();      my $title = $resource->compTitle();
     if ($src =~ /^\/uploaded\//) {  
         $nonLinkedText=$title;  
         $title = '';  
     }  
     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.'/problem.gif" alt="'.&mt('Problem').'" border="0" />';
         } else {          } else {
             $icon = $params->{'indentString'};              $icon = $params->{'indentString'};
         }          }
     } else {      } else {
  my $curfext= (split (/\./,$resource->src))[-1];   $icon = "<img src='".&Apache::loncommon::icon($resource->src)."' alt='&nbsp;&nbsp;' border='0' />";
  my $embstyle = &Apache::loncommon::fileembstyle($curfext);  
  # The unless conditional that follows is a bit of overkill  
  if (!(!defined($embstyle) || $embstyle eq 'unk' || $embstyle eq 'hdn')) {  
     $icon = "<img src='/adm/lonIcons/$curfext.gif' alt='' border='0' />";  
  }  
     }      }
   
     # Display the correct map icon to open or shut map      # Display the correct map icon to open or shut map
Line 912  sub render_resource { Line 1112  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'} . '&filter=';
             $linkopen .= ($nowOpen xor $it->{CONDITION}) ?              $linkopen .= ($nowOpen xor $it->{CONDITION}) ?
                 addToFilter($filter, $mapId) :                  addToFilter($filter, $mapId) :
Line 927  sub render_resource { Line 1129  sub render_resource {
                 &Apache::lonnet::escape($params->{'here'}) .                   &Apache::lonnet::escape($params->{'here'}) . 
                 '&jump=' .                  '&jump=' .
                 &Apache::lonnet::escape($resource->symb()) .                   &Apache::lonnet::escape($resource->symb()) . 
                 "&folderManip=1'>";                  "&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 940  sub render_resource { Line 1144  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> ';
     }      }
           
     # We're done preparing and finally ready to start the rendering      # We're done preparing and finally ready to start the rendering
Line 955  sub render_resource { Line 1162  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 963  sub render_resource { Line 1171  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'}) {
         $partLabel = " (Part $part)";   my $displaypart=$resource->part_display($part);
           $partLabel = " (".&mt('Part: [_1]', $displaypart).")";
    if ($link!~/\#/) { $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'} && $src !~ /^\/uploaded\// &&      my $target;
  !$resource->is_sequence()) {      if ($env{'environment.remotenavmap'} eq 'on') {
         $result .= "  $curMarkerBegin<a href='$link'>$title$partLabel</a>$curMarkerEnd $nonLinkedText</td>";   $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 993  sub render_communication_status { Line 1206  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 border="0" src="'.$location.'/chat.gif" />' .
             $linkclose;              $linkclose;
     }      }
           
Line 1006  sub render_communication_status { Line 1223  sub render_communication_status {
         my $feedback = $resource->getFeedback();          my $feedback = $resource->getFeedback();
         foreach (split(/\,/, $feedback)) {          foreach (split(/\,/, $feedback)) {
             if ($_) {              if ($_) {
                 $feedbackHTML .= '&nbsp;<a href="/adm/email?display='                  $feedbackHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='
                     . &Apache::lonnet::escape($_) . '">'                      . &Apache::lonnet::escape($_) . '">'
                     . '<img src="/adm/lonMisc/feedback.gif" '                      . '<img src="'.$location.'/feedback.gif" '
                     . 'border="0" /></a>';                      . 'border="0" /></a>';
             }              }
         }          }
Line 1016  sub render_communication_status { Line 1233  sub render_communication_status {
           
     if ($resource->getErrors()) {      if ($resource->getErrors()) {
         my $errors = $resource->getErrors();          my $errors = $resource->getErrors();
           my $errorcount = 0;
         foreach (split(/,/, $errors)) {          foreach (split(/,/, $errors)) {
               last if ($errorcount>=10); # Only output 10 bombs maximum
             if ($_) {              if ($_) {
                 $errorHTML .= '&nbsp;<a href="/adm/email?display='                  $errorcount++;
                   $errorHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='
                     . &Apache::lonnet::escape($_) . '">'                      . &Apache::lonnet::escape($_) . '">'
                     . '<img src="/adm/lonMisc/bomb.gif" '                      . '<img src="'.$location.'/bomb.gif" '
                     . 'border="0" /></a>';                      . 'border="0" /></a>';
             }              }
         }          }
Line 1040  sub render_quick_status { Line 1260  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 1049  sub render_quick_status { Line 1273  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 width='30' valign='center' 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 1069  sub render_long_status { Line 1295  sub render_long_status {
     if ($resource->is_problem()) {      if ($resource->is_problem()) {
         $color = $colormap{$resource->status};          $color = $colormap{$resource->status};
                   
         if (dueInLessThen24Hours($resource, $part) ||          if (dueInLessThan24Hours($resource, $part) ||
             lastTry($resource, $part)) {              lastTry($resource, $part)) {
             $color = $hurryUpColor;              $color = $hurryUpColor;
         }          }
Line 1121  my @statuses = ($resObj->CORRECT, $resOb Line 1347  my @statuses = ($resObj->CORRECT, $resOb
 use Data::Dumper;  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()) { return '<td></td>'; }      if (!$resource->is_problem() && !$resource->contains_problem) { return '<td></td>'; }
     if ($params->{showParts}) {       if ($params->{showParts}) { 
  return '<td></td>';   return '<td></td>';
     }      }
   
     my $td = "<td align='right'>\n";      my $td = "<td align='right'>\n";
     my $endtd = "</td>\n";      my $endtd = "</td>\n";
       my @probs;
   
     # If there is a single part, just show the simple status      if ($resource->contains_problem) {
     if ($resource->singlepart()) {   @probs=$resource->retrieveResources($resource,sub { $_[0]->is_problem() },1,0);
  my $status = $resource->simpleStatus('0');      } else {
  return $td . "<font color='" . $statusColors{$status} . "'>"   @probs=($resource);
     . $statusStrings{$status} . "</font>" . $endtd;      }
     }      my $return;
       my %overallstatus;
     # Now we can be sure the $part doesn't really matter.      my $totalParts;
     my $statusCount = $resource->simpleStatusCount();      foreach my $resource (@probs) {
     my @counts;   # If there is a single part, just show the simple status
     foreach my $status(@statuses) {   if ($resource->singlepart()) {
  # decouple display order from the simpleStatusCount order      my $status = $resource->simpleStatus(${$resource->parts}[0]);
  my $slot = Apache::lonnavmaps::resource::statusToSlot($status);      $overallstatus{$status}++;
  if ($statusCount->[$slot]) {      $totalParts++;
     push @counts, "<font color='" . $statusColors{$status} .      next;
  "'>" . $statusCount->[$slot] . ' '   }
    # Now we can be sure the $part doesn't really matter.
    my $statusCount = $resource->simpleStatusCount();
    my @counts;
    foreach my $status (@statuses) {
       # decouple display order from the simpleStatusCount order
       my $slot = Apache::lonnavmaps::resource::statusToSlot($status);
       if ($statusCount->[$slot]) {
    $overallstatus{$status}+=$statusCount->[$slot];
    $totalParts+=$statusCount->[$slot];
       }
    }
       }
       $return.= $td . $totalParts . ' parts: ';
       foreach my $status (@statuses) {
    if ($overallstatus{$status}) {
       $return.="<font color='" . $statusColors{$status} .
    "'>" . $overallstatus{$status} . ' '
  . $statusStrings{$status} . "</font>";   . $statusStrings{$status} . "</font>";
  }   }
     }      }
       $return.= $endtd;
     return $td . $resource->countParts() . ' parts: ' . join (', ', @counts) . $endtd;      return $return;
 }  }
   
 my @preparedColumns = (\&render_resource, \&render_communication_status,  my @preparedColumns = (\&render_resource, \&render_communication_status,
Line 1162  sub setDefault { Line 1406  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 1184  sub render { Line 1433  sub render {
     my $jump = $args->{'jump'};      my $jump = $args->{'jump'};
     my $here = $args->{'here'};      my $here = $args->{'here'};
     my $suppressNavmap = setDefault($args->{'suppressNavmap'}, 0);      my $suppressNavmap = setDefault($args->{'suppressNavmap'}, 0);
       my $closeAllPages = setDefault($args->{'closeAllPages'}, 0);
     my $currentJumpDelta = 2; # change this to change how many resources are displayed      my $currentJumpDelta = 2; # change this to change how many resources are displayed
                              # before the current resource when using #current                               # before the current resource when using #current
   
Line 1193  sub render { Line 1443  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 (split(/\,/, $env{"form.filter"})) {
         if ($_) {          if ($_) {
             $filterHash->{$_} = "1";              $filterHash->{$_} = "1";
         }          }
Line 1213  sub render { Line 1463  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 '<font color="red">'.&mt('No course selected').'</font><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 1246  sub render { Line 1509  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 1263  sub render { Line 1526  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 1292  sub render { Line 1554  sub render {
             $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition);              $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition);
         }          }
     }      }
       
     # (re-)Locate the jump point, if any      # (re-)Locate the jump point, if any
     # Note this does not take filtering or hidden into account... need      # Note this does not take filtering or hidden into account... need
     # to be fixed?      # to be fixed?
Line 1322  sub render { Line 1584  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;          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 1346  sub render { Line 1609  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&filter=&'.$queryString.
                 "&here=" . Apache::lonnet::escape($here) .   '&here='.&Apache::lonnet::escape($here).'"';
                 "\">".&mt('Close All Folders')."</a>";      $text='Close All Folders';
           } else {
       $link='"navmaps?condition=1&filter=&'.$queryString.
    '&here='.&Apache::lonnet::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 1379  sub render { Line 1705  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 1429  sub render { Line 1757  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()) {      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 1479  sub render { Line 1860  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 1532  sub render { Line 1909  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=' . &Apache::lonnet::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 1543  sub render { Line 1939  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 $srcHasQuestion = $src =~ /\?/;  
             $args->{"resourceLink"} = $src.  
                 ($srcHasQuestion?'&':'?') .  
                 'symb=' . &Apache::lonnet::escape($curRes->symb());  
               
             # Now, display each column.              # Now, display each column.
             foreach my $col (@$cols) {              foreach my $col (@$cols) {
                 my $colHTML = '';                  my $colHTML = '';
Line 1600  sub render { Line 1989  sub render {
     # it's quite likely this might fix other browsers, too, and       # it's quite likely this might fix other browsers, too, and 
     # certainly won't hurt anything.      # certainly won't hurt anything.
     if ($displayedJumpMarker) {      if ($displayedJumpMarker) {
         $result .= "<script>setTimeout(\"location += '#curloc';\", 0)</script>\n";          $result .= "
   <script>
   if (location.href.indexOf('#curloc')==-1) {
       setTimeout(\"location += '#curloc';\", 0)
   }
   </script>";
     }      }
   
     $result .= "</table>";      $result .= "</table>";
Line 1611  sub render { Line 2005  sub render {
         $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 1635  In order of increasing complexity and po Line 2067  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 1673  successful, or B<undef> if not. Line 2106  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 1689  See iterator documentation below. Line 2118  See iterator documentation below.
   
 use strict;  use strict;
 use GDBM_File;  use GDBM_File;
   use Apache::lonnet;
   
 sub new {  sub new {
     # magic invocation to create a class instance      # magic invocation to create a class instance
Line 1708  sub new { Line 2138  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 1734  sub generate_course_user_opt { Line 2164  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 1791  sub generate_course_user_opt { Line 2201  sub generate_course_user_opt {
   
 sub generate_email_discuss_status {  sub generate_email_discuss_status {
     my $self = shift;      my $self = 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',
                                           $env{'user.domain'},$env{'user.name'},'lastread');
       my %lastreadtime = ();
       foreach (keys %lastread) {
           my $key = $_;
           $key =~ s/_lastread$//;
           $lastreadtime{$key} = $lastread{$_};
       }
   
     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 $plain=
  if ($plain=~/(Error|Feedback) \[([^\]]+)\]/) {   &Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid));
     my ($what,$url)=($1,$2);      if ($plain=~/ \[([^\]]+)\]\:/) {
     my %status=   my $url=$1;
  &Apache::lonnet::get('email_status',[$msgid]);   if ($plain=~/\:Error \[/) {
     if ($status{$msgid}=~/^error\:/) {   
  $status{$msgid}='';   
     }  
       
     if (($status{$msgid} eq 'new') ||   
  (!$status{$msgid})) {   
  if ($what eq 'Error') {  
     $error{$url}.=','.$msgid;       $error{$url}.=','.$msgid; 
  } else {   } else {
     $feedback{$url}.=','.$msgid;      $feedback{$url}.=','.$msgid;
Line 1832  sub generate_email_discuss_status { Line 2244  sub generate_email_discuss_status {
  }   }
     }      }
           
       #url's of resources that have feedbacks
     $self->{FEEDBACK} = \%feedback;      $self->{FEEDBACK} = \%feedback;
     $self->{ERROR_MSG} = \%error; # what is this? JB      #or errors
       $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->{EMAIL_DISCUSS_GENERATED} = 1;      $self->{EMAIL_DISCUSS_GENERATED} = 1;
 }  }
Line 1845  sub get_user_data { Line 2260  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::dump($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 1872  sub navhash { Line 2308  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 1882  sub courseMapDefined { Line 2318  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});
     return $self->{DISCUSSION_TIME}->{$symb} >  
            $self->{LAST_CHECK};      # backward compatibility (bulletin boards used to be 'wrapped')
       my $ressymb = $self->wrap_symb($symb);
       if ( defined ( $self->{LAST_READ}->{$ressymb} ) ) {
           return $self->{DISCUSSION_TIME}->{$ressymb} > $self->{LAST_READ}->{$ressymb};
       } else {
   #        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 unread_discussion {
       my $self = shift;
       my $symb = shift;
   
       $self->get_discussion_data();
       
       my $ressymb = $self->wrap_symb($symb);
   
       my $version = $self->{DISCUSSION_DATA}{'version:'.$ressymb};
       if (!$version) { return; }
   
       my $prevread = $self->{LAST_READ}{$ressymb};
   
       my $unreadcount = 0;
       my $hiddenflag = 0;
       my $deletedflag = 0;
       my ($hidden,$deleted);
   
       my %subjects;
   
       for (my $id=$version; $id>0; $id--) {
    my $vkeys=$self->{DISCUSSION_DATA}{$id.':keys:'.$ressymb};
    my @keys=split(/:/,$vkeys);
    if (grep(/^hidden$/ ,@keys)) {
       if (!$hiddenflag) {
    $hidden = $self->{DISCUSSION_DATA}{$id.':'.$ressymb.':hidden'};
    $hiddenflag = 1;
       }
    } elsif (grep(/^deleted$/,@keys)) {
       if (!$deletedflag) {
    $deleted = $self->{DISCUSSION_DATA}{$id.':'.$ressymb.':deleted'};
    $deletedflag = 1;
       }
    } else {
       if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./)
    && $prevread < $self->{DISCUSSION_DATA}{$id.':'.$ressymb.':timestamp'}) {
       $unreadcount++;
       $subjects{$unreadcount}=
    $id.': '.$self->{DISCUSSION_DATA}{$id.':'.$ressymb.':subject'};
    }
    }
       }
       if (wantarray) {
    return ($unreadcount,\%subjects);
       }
       return $unreadcount
   }
   
   sub wrap_symb {
       my $self = shift;
       my $symb = shift;
       if ($symb =~ m-___(adm/\w+/\w+/)(\d+)(/bulletinboard)$-) {
           unless ($symb =~ m|adm/wrapper/adm|) {
               $symb = 'bulletin___'.$2.'___adm/wrapper/'.$1.$2.$3;
           }
       }
       return $symb;
 }  }
   
 # 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;
Line 1976  sub getById { Line 2479  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 2001  resource in the navmap. Line 2509  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 2017  in the navmap. Line 2525  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 2044  sub parmval_real { Line 2552  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='';
Line 2061  sub parmval_real { Line 2575  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 2083  sub parmval_real { Line 2601  sub parmval_real {
     }      }
   
 # ------------------------------------------------------- second, check course  # ------------------------------------------------------- second, check course
       if ($cgroup ne '' and defined($courseopt)) {
           if (defined($$courseopt{$grplevelr})) { return $$courseopt{$grplevelr}; }
           if (defined($$courseopt{$grplevelm})) { return $$courseopt{$grplevelm}; }
           if (defined($$courseopt{$grplevel})) { return $$courseopt{$grplevel}; }
       }
   
     if ($csec and defined($courseopt)) {      if ($csec and defined($courseopt)) {
         if (defined($$courseopt{$seclevelr})) { return $$courseopt{$seclevelr}; }          if (defined($$courseopt{$seclevelr})) { return $$courseopt{$seclevelr}; }
         if (defined($$courseopt{$seclevelm})) { return $$courseopt{$seclevelm}; }          if (defined($$courseopt{$seclevelm})) { return $$courseopt{$seclevelm}; }
Line 2091  sub parmval_real { Line 2615  sub parmval_real {
   
     if (defined($courseopt)) {      if (defined($courseopt)) {
         if (defined($$courseopt{$courselevelr})) { return $$courseopt{$courselevelr}; }          if (defined($$courseopt{$courselevelr})) { return $$courseopt{$courselevelr}; }
         if (defined($$courseopt{$courselevelm})) { return $$courseopt{$courselevelm}; }  
         if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; }  
     }      }
   
 # ----------------------------------------------------- third, check map parms  # ----------------------------------------------------- third, check map parms
Line 2102  sub parmval_real { Line 2624  sub parmval_real {
   
 # ----------------------------------------------------- fourth , check default  # ----------------------------------------------------- fourth , check default
   
     my $default=&Apache::lonnet::metadata($fn,$rwhat.'.default');      my $meta_rwhat=$rwhat;
       $meta_rwhat=~s/\./_/g;
       my $default=&Apache::lonnet::metadata($fn,$meta_rwhat);
     if (defined($default)) { return $default}      if (defined($default)) { return $default}
       $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat);
       if (defined($default)) { return $default}
   
   # --------------------------------------------------- fifth, check more course
       if (defined($courseopt)) {
           if (defined($$courseopt{$courselevelm})) { return $$courseopt{$courselevelm}; }
           if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; }
       }
   
 # --------------------------------------------------- fifth , cascade up parts  # --------------------------------------------------- sixth , cascade up parts
   
     my ($space,@qualifier)=split(/\./,$rwhat);      my ($space,@qualifier)=split(/\./,$rwhat);
     my $qualifier=join('.',@qualifier);      my $qualifier=join('.',@qualifier);
Line 2125  sub parmval_real { Line 2657  sub parmval_real {
   
 =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 wahich caes 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 2142  function that takes a resource object as Line 2677  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 2157  want to know is if I<any> resources matc Line 2693  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<hasResources>(map, filterFunc, recursive):  =item * B<hasResource>(map, filterFunc, recursive, showall):
   
 Convience method for  Convience 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 2194  sub retrieveResources { Line 2750  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 2213  sub retrieveResources { Line 2769  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 = ();
   
Line 2243  sub hasResource { Line 2799  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 2345  consisting entirely of empty resources e Line 2910  consisting entirely of empty resources e
 ending resource, will cause a lot of BRANCH_STARTs and BRANCH_ENDs,  ending resource, will cause a lot of BRANCH_STARTs and BRANCH_ENDs,
 but only one resource will be returned.  but only one resource will be returned.
   
   =back
   
 =head2 Normal Usage  =head2 Normal Usage
   
 Normal usage of the iterator object is to do the following:  Normal usage of the iterator object is to do the following:
Line 2365  the depth of the iterator to see when it Line 2932  the depth of the iterator to see when it
 code. It is difficult to get right and harder to understand then  code. It is difficult to get right and harder to understand then
 this. They should be migrated to this new style.  this. They should be migrated to this new style.
   
 =back  
   
 =cut  =cut
   
 # Here are the tokens for the iterator:  # Here are the tokens for the iterator:
Line 2390  sub new { Line 2955  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 2503  sub new { Line 3068  sub new {
     }      }
   
     # Check: Was this only one resource, a map?      # Check: Was this only one resource, a map?
     if ($resourceCount == 1 && $resource->is_map() && !$self->{FORCE_TOP}) {       if ($resourceCount == 1 && $resource->is_sequence() && !$self->{FORCE_TOP}) { 
         my $firstResource = $resource->map_start();          my $firstResource = $resource->map_start();
         my $finishResource = $resource->map_finish();          my $finishResource = $resource->map_finish();
         return           return 
             Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,              Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
                                               $finishResource, $self->{FILTER},                                                $finishResource, $self->{FILTER},
                                               $self->{ALREADY_SEEN},                                                 $self->{ALREADY_SEEN}, 
                                               $self->{CONDITION}, 0);                                                $self->{CONDITION},
         $self->{FORCE_TOP});
                   
     }      }
   
Line 2536  sub new { Line 3102  sub new {
   
 sub next {  sub next {
     my $self = shift;      my $self = shift;
       my $closeAllPages=shift;
     if ($self->{FINISHED}) {      if ($self->{FINISHED}) {
  return END_ITERATOR();   return END_ITERATOR();
     }      }
Line 2550  sub next { Line 3116  sub next {
   
     if ($self->{RECURSIVE_ITERATOR_FLAG}) {      if ($self->{RECURSIVE_ITERATOR_FLAG}) {
         # grab the next from the recursive iterator           # grab the next from the recursive iterator 
         my $next = $self->{RECURSIVE_ITERATOR}->next();          my $next = $self->{RECURSIVE_ITERATOR}->next($closeAllPages);
   
         # is it a begin or end map? If so, update the depth          # is it a begin or end map? If so, update the depth
         if ($next == BEGIN_MAP() ) { $self->{RECURSIVE_DEPTH}++; }          if ($next == BEGIN_MAP() ) { $self->{RECURSIVE_DEPTH}++; }
Line 2664  sub next { Line 3230  sub next {
   
     # That ends the main iterator logic. Now, do we want to recurse      # That ends the main iterator logic. Now, do we want to recurse
     # down this map (if this resource is a map)?      # down this map (if this resource is a map)?
     if ($self->{HERE}->is_map() &&      if ( ($self->{HERE}->is_sequence() || (!$closeAllPages && $self->{HERE}->is_page())) &&
         (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) {          (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) {
         $self->{RECURSIVE_ITERATOR_FLAG} = 1;          $self->{RECURSIVE_ITERATOR_FLAG} = 1;
         my $firstResource = $self->{HERE}->map_start();          my $firstResource = $self->{HERE}->map_start();
Line 2673  sub next { Line 3239  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 2682  sub next { Line 3250  sub next {
     my $browsePriv = $self->{HERE}->browsePriv();      my $browsePriv = $self->{HERE}->browsePriv();
     if (!$self->{HERE}->src() ||       if (!$self->{HERE}->src() || 
         (!($browsePriv eq 'F') && !($browsePriv eq '2')) ) {          (!($browsePriv eq 'F') && !($browsePriv eq '2')) ) {
         return $self->next();          return $self->next($closeAllPages);
     }      }
   
     return $self->{HERE};      return $self->{HERE};
Line 2726  sub populateStack { Line 3294  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 2734  package Apache::lonnavmaps::DFSiterator; Line 3304  package Apache::lonnavmaps::DFSiterator;
 #  useful for pre-processing of some kind, and is in fact used by the main  #  useful for pre-processing of some kind, and is in fact used by the main
 #  iterator that way, but that's about it.  #  iterator that way, but that's about it.
 # One could imagine merging this into the init routine of the main iterator,  # One could imagine merging this into the init routine of the main iterator,
 #  but this might as well be left seperate, since it is possible some other  #  but this might as well be left separate, since it is possible some other
 #  use might be found for it. - Jeremy  #  use might be found for it. - Jeremy
   
 # Unlike the main iterator, this DOES return all resources, even blank ones.  # Unlike the main iterator, this DOES return all resources, even blank ones.
Line 2754  sub new { Line 3324  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 2908  sub populateStack { Line 3478  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 2990  sub new { Line 3560  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 3078  Returns the title of the resource. Line 3648  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 3089  sub randompick { Line 3660  sub randompick {
     return $self->{NAV_MAP}->{PARM_HASH}->{$self->symb .      return $self->{NAV_MAP}->{PARM_HASH}->{$self->symb .
                                                '.0.parameter_randompick'};                                                 '.0.parameter_randompick'};
 }  }
   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 3101  sub symb { Line 3691  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 3151  Returns true if the resource is a sequen Line 3766  Returns true if the resource is a sequen
   
 =cut  =cut
   
   sub hasResource {
      my $self = shift;
      return $self->{NAV_MAP}->hasResource(@_);
   }
   
   sub retrieveResources {
      my $self = shift;
      return $self->{NAV_MAP}->retrieveResources(@_);
   }
   
 sub is_html {  sub is_html {
     my $self=shift;      my $self=shift;
Line 3164  sub is_page { Line 3788  sub is_page {
     return $self->navHash("is_map_", 1) &&       return $self->navHash("is_map_", 1) && 
  $self->navHash("map_type_" . $self->map_pc()) eq 'page';   $self->navHash("map_type_" . $self->map_pc()) eq 'page';
 }  }
   sub is_practice {
       my $self=shift;
       my ($part) = @_;
       if ($self->parmval('type',$part) eq 'practice') {
           return 1;
       }
       return 0;
   }
 sub is_problem {  sub is_problem {
     my $self=shift;      my $self=shift;
     my $src = $self->src();      my $src = $self->src();
     return ($src =~ /problem$/);      if ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) {
    return !($self->is_practice());
       }
       return 0;
   }
   sub contains_problem {
       my $self=shift;
       if ($self->is_page()) {
    my $hasProblem=$self->hasResource($self,sub { $_[0]->is_problem() },1);
    return $hasProblem;
       }
       return 0;
 }  }
 sub is_sequence {  sub is_sequence {
     my $self=shift;      my $self=shift;
Line 3175  sub is_sequence { Line 3818  sub is_sequence {
     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 {
       my $self = shift();
       my $part = shift();
       if ($self->parmval('type',$part) eq 'survey') {
           return 1;
       }
       if ($self->src() =~ /\.(survey)$/) {
           return 1;
       }
       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 3351  sub awarded { Line 4016  sub awarded {
 }  }
 sub duedate {  sub duedate {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
       my $interval=$self->parmval("interval", $part);
       if ($interval) {
    my $first_access=&Apache::lonnet::get_first_access('map',$self->symb);
    if ($first_access) { return ($first_access+$interval); }
       }
     return $self->parmval("duedate", $part);      return $self->parmval("duedate", $part);
 }  }
   sub handgrade {
       (my $self, my $part) = @_;
       return $self->parmval("handgrade", $part);
   }
 sub maxtries {  sub maxtries {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("maxtries", $part);      return $self->parmval("maxtries", $part);
Line 3391  sub weight { Line 4065  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',      return &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'});
   }
   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 3435  Returns a false value if there has been Line 4118  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<unread_discussion>:
   
   returns in scalar context the count of the number of unread discussion
   postings
   
   returns in list context both the count of postings and a hash ref
   containing the subjects of all 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 3455  sub hasDiscussion { Line 4151  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 unread_discussion {
       my $self = shift;
       return $self->{NAV_MAP}->unread_discussion($self->symb());
   }
   
 sub getFeedback {  sub getFeedback {
     my $self = shift;      my $self = shift;
     my $source = $self->src();      my $source = $self->src();
Line 3484  Returns the number of parts of the probl Line 4190  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 3530  sub countParts { Line 4246  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 3598  sub extractParts { Line 4334  sub extractParts {
  return;   return;
     }      }
     foreach (split(/\,/,$metadata)) {      foreach (split(/\,/,$metadata)) {
  if ($_ =~ /^part_(.*)$/) {   if ($_ =~ /^(?: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 3617  sub extractParts { Line 4353  sub extractParts {
         }          }
                   
   
           # These hashes probably do not need names that end with "Hash"....
         my %responseIdHash;          my %responseIdHash;
         my %responseTypeHash;          my %responseTypeHash;
   
Line 3633  sub extractParts { Line 4370  sub extractParts {
         # 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 (split /,/, $metadata) {
             if ($_ =~ /^([a-zA-Z]+)response_(.*)/) {              if ($_ =~ /^([a-zA-Z]+)response_(.*)/
    || $_ =~ /^(Task)_(.*)/) {
                 my $responseType = $1;                  my $responseType = $1;
                 my $partStuff = $2;                  my $partStuff = $2;
                 my $partIdSoFar = '';                  my $partIdSoFar = '';
Line 3651  sub extractParts { Line 4389  sub extractParts {
                 }                  }
             }              }
         }          }
    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 3838  sub ATTEMPTED             { return 16; } Line 4600  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 seperate 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 3948  An answer has been submitted, but the st Line 4714  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 3966  sub status { Line 4733  sub status {
     my $suppressFeedback = $self->problemstatus($part) eq 'no';      my $suppressFeedback = $self->problemstatus($part) eq 'no';
     # 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 3994  sub status { Line 4776  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 4060  my %compositeToSimple = Line 4842  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 4180  sub getNext { Line 4963  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 4208  sub browsePriv { Line 4992  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.241  
changed lines
  Added in v.1.366


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